引言
分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰保证一致性。redisson 基于缓存实现了 redis 分布式锁,简便易用。
分布式锁的三种实现
- 基于数据库
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中主要包含方法名等字段,并在方法名字段上创建唯一索引。想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
优势:实现简单,唯一机制由数据库保证。
劣势:可能存在分布式数据库同步问题,需要记录机器、线程信息才能实现可重入获取锁,需要定时任务清除失效锁,需要额外实现锁阻塞重新获取的逻辑。
- 基于缓存
基于缓存的实现方式主要依赖 redis,高性能且命令友好,使用 SETNX 排他地设置锁,使用 expire 设置锁超时释放时间,使用 delete 删除锁。
- 基于ZooKeeper
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。
基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
优势:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
劣势:因为需要频繁的创建和删除节点,性能上不如Redis方式。
redisson 实现 redis 分布式锁
基于《redis 分布式缓存 springboot + redisson》中对 redisson 的依赖引入和初始化,在需要加锁的方法类中注入 redissonClient,用于创建锁和删除锁。
@Autowired
protected RedissonClient redissonClient;
在方法中使用锁:
public void registerTask(Param param) {
RLock registerTaskRLock = redissonClient.getLock(
RedisKeyUtils.getLockKey(LockScene.REGISTER_TASK, this.getClass().getSimpleName())
);
if (!registerTaskRLock.tryLock()) {
log.error("从分布式内存二级缓存加载数据异常");
}
try {
// 做自己的事情
} finally {
if (registerTaskRLock.isLocked() && registerTaskRLock.isHeldByCurrentThread()
&& !redissonClient.isShutdown()) {
registerTaskRLock.unlock();
}
}
}
监控锁的看门狗原理
如果负责储存这个分布式锁的 redisson 的服务节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,redisson 内部提供了一个监控锁的看门狗,它的作用是在 redisson 实例被关闭前,不断的延长锁的有效期。
Config.lockWatchdogTimeout 用于指定看门狗的检查锁的超时时间,如果没有指定默认 30 秒。只要占锁成功,就会启动一个定时任务:每隔 10 秒重新给锁设置过期的时间,过期时间为 30 秒。如果服务器宕机,锁将在 30 秒后释放,避免影响重启后的任务执行。
总结
综上,redisson 实现了简便易用的 redis 分布式锁,这种基于缓存的分布式锁,可用性由于基于数据库的分布式锁,性能优于基于ZooKeeper 的分布式锁。