分布式锁
分布式锁是分布式系统中实现互斥访问共享资源的一种机制。
Redis实现分布式锁的方式有很多种,不同的业务场景也有可能有不同的实现。但所有的实现不管怎么变化,其核心点还是redis的两个命令setNX和expire。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1. SETNX key value 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。 可用版本: >= 1.0.0 时间复杂度: O(1) 返回值: 设置成功,返回 1 。 设置失败,返回 0 。 2. EXPIRE key seconds 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。 可用版本: >= 1.0.0 时间复杂度: O(1) 返回值: 设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。 3. TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 可用版本: >= 1.0.0 时间复杂度: O(1) 返回值: 当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
这里主要讲解一种多台机器互作主备的分布式锁实现方式,这种方式源自于团队的一帮技术牛人,几经辗转现在需要我暂时维护这块儿,所以在研究代码的同时对其做一个简单的整理。(图)
整体思路:task任务通过tryLock本地锁来获得执行权;本地锁由守护线程负责同步redis锁的状态。如下图
具体分为两个部分:
(1)抢锁/续租:在没有获得锁的时候,不断尝试抢锁;在获得锁之后,不停给redis和本地的锁续租。在应用启动的时候,需要将其注册为守护进程,守护线程每隔几秒运行一次。
(2)tryLock:判断本地锁是否在续租期,如果是则进行工作,否则什么也不做。由定时task在执行任务之前调用判断。
抢锁/续租流程如下图:
整体流程如下:
(1) 应用启动时,注册分布式锁。首先阻塞调用一次抢锁,抢锁之后(不论锁是否抢成功),启动守护线程,每个几秒执行一次抢锁续租。
(2) 抢锁续租,首先判断本地锁是否已经过期。若已经过期则执行步骤3;若为未过期则执行步骤4。
(3)尝试setNX抢锁。抢锁成功,设置本地锁有效期时间为当前时间后Xs;若抢锁不成功,则进一步确认redis锁是否是自己,是自己且在TTL大于续租间隔则将redis锁的TTL赋值给本地锁,不是自己则什么也不做。
(4)尝试expire续租锁,首先读取一下远程锁,判断是否是自己(为了防止极端情况下锁正好过期或者锁被其它节点抢走,需要确认锁是自己拥有的)。如不是则什么也不做;如是则延长redis锁的有效期,延长成功则设置本地锁有效期时间为当前时间后Xs。
相关代码可参考:Stephan Gao 的 GitHub distributedLock简介
待续.......
参考文章:
[1] 分布式锁的原理及实现。
[2] 分布式锁的一点理解。
[3] Redis命令参考。