如何做技术选型是比较大的一个话题,这篇Blog通过一个简单且实用的例子分享给大家,即分布式锁。 分布式锁在集群环境中,是逃不掉一环,并且比较全面而小巧,非常合适。

明确目标 - 避免解决不存在的问题

  • 解决“内存锁”仅单节点有效,集群环境中无法协同生效的问题。
  • 统一标准使用锁的方式,从设计上避免死锁。
  • 统一命名规范,从设计上杜绝错误锁命名,以便管理(如:知道有多少处业务使用到了锁)。
  • 简化配置,方便集成使用。
  • 监控所有系统中使用锁的情况。

技术选型 - 有理有据

选型比较 Redis ZooKeeper 备注
学习成本 相对高 Redis 易调试、客户端UI友好、Telnet直接调试。ZooKeeper客户端简陋。
运维成本 相对高 公司已经使用Redis已经很熟悉了,无需考虑 ZooKeeper 高可用方案
生态环境 一般 Redis职责单一,但可定制化高也容易。ZooKeeper Java生态环境较好 Apache系很多项目集成ZooKeeper。
社区活跃 非常高 一般 Redis: Watch: 2,404 Star 27,434 Fork 10,521 (统计截止2018-02-07,主要社区维护)
ZooKeeper: Watch: 540 Star: 3,933 Fork: 2,769 (统计截止2018-02-07,主要有由Apache维护)
个人倾向 倾向 不倾向 个人同类型中间件,选型标准:
1. 功能职责单一、易用、易调试 (Redis胜出)
2. 文档完善简洁易懂(Redis胜出)
3. 尽可能减少中间件依赖,Redis已投入使用 (Redis胜出)
4. 开发语言倾向: C 优于 Java (Redis胜出)
5. 自主官网维护优于Apache 系 (Redis胜出)

从设计上防止死锁需满足条件 - 列出关键点

所需条件 解决方法
避免获取锁长时间等待 仅提供一种获取锁的方式tryLock,设置等待时间(小于等于5秒)。客户端需处理获取锁失败的情况
避免因Bug或Crash导致锁未释放 获取锁【必须】指定解锁超时时间(最多30秒)
获取锁必须是原子性操作(Atomic) Redis SETNX 配合过期失效 SET NX PX

画时序图 - 整理核心流程

代码示例 - 提供语法糖,简易使用。

@EnableDLock //启用分布式锁,自动配置
@SpringBootApplication
public class Application {
    
    //注入分布锁式组件
    @Autowired
    private LockManager lockManager;
	
    //使用demo,自动获取锁,自动释放,自动上报锁使用情况到监控
    @PostConstruct
    private void demo() {
        String category = "meeting-room";
        String business = "booking";
        String meetingRoomId = "1";
        int waitMillSeconds = 5000;
        String reuslt = lockManager.locker(category, business, meetingRoomId)
                // 获取锁成功后执行 - 业务代码
                .success(() ->  "预定会议成功。")
                //获取锁失败或等待超时后执行, 提示
                .failure(() ->  {throw new BusinessException("当前会议室正在预定中");})
                // 执行,愿意等待时长至多5秒(可选)
                .execute(waitMillSeconds);
    }	
	
	
    public static void main(String[] args) {
        SpringApplication.run(DlockDemoApplication.class, args);
    }
}

命名规范

  • 分布式锁命名由:业务系统(spring.application.name)、子业务、子业务分类、业务ID组成
  • 命名均为字符串并且小写以短杆(-)分隔,杜绝驼峰。例子:smart-office 反例:SmartOffice、smartOffice

使用规范

  • 尽可能从设计上避免使用分布式锁,如乐观锁。
  • 尽量减少锁的范围,尽快释放。
  • 尝试获取锁超时,需要考虑正确反馈,如:“会议室预定繁忙,请稍后再试”。
  • 禁止交叉锁,避免导致短暂的死锁,如:methodA: { a.tryLock(); b.tryLock(); } methodB:{ b.tryLock(); a.tryLock(); }
  • 解锁超时时间至多30秒,超过考虑换设计方案。
  • 等待获取锁时间至多5秒,超过则需要重试,或考虑其他设计方案。