redis作为客户端连接redis实现分布式锁机制
1、利用 jedis.set(String key, String value, String nxxx, String expx, int time)
利用set直接设置过期时间2s,执行job后,在finally执行del解锁。
2、利用jedis.setnx()和jedis.expire()组合实现加锁
第一步利用setnx加锁,如果锁已存在,则返回结果为0,如果成功加锁,设置过期时间2s,最后finally,执行del解锁。
Jedis set & setnx命令封装的分布式锁的不足
1)、不具备可重入性
当遇到业务上需要在多个地方用到同样一个锁的时候,很显然使用不具有可重入的锁会很容易发生死锁的现象。
Java并发工具包里的Lock对象和sychronized语块都具有可重入性,对于经常使用这些工具的人来说,往往会很容易忽略setnx的这个缺陷。
2)、不支持续约
setnx的设计缺乏一个延续有效期的续约机制,无法保证业务能够先工作做完再解锁,也不能确保在某个程序宕机或业务节点挂掉的时候,其它节点能够很快的恢复业务处理能力。
3)、不具备阻塞的能力
锁都具备两个共性:一是互斥性,二是阻塞性。
阻塞性是指的在有竞争的情况下,未获取到资源的线程会停止继续操作,直到成功获取到资源或取消操作。
setnx命令只提供了互斥的特性,却没有提供阻塞的能力。
Redisson作为redis连接客户端的分布式锁机制
jedis set 或者setnx 的三点不足,redisson都满足,且在此基础上还增加了线程安全的特点。
利用redisson实现分布式锁
执行加锁的并发测试
测试代码:
锁后执行job代码:
结果:
(源码地址:https://github.com/qingqiangqiang/redlock.git)
redisson分布式锁的实现原理
1、加锁
利用Redis的Hash结构作为储存单元,将业务指定的名称作为key,将随机UUID和线程ID作为field,最后将加锁的次数作为value来储存。同时UUID作为锁的实例变量保存在客户端。将UUID和线程ID作为标签在运行多个线程同时使用同一个锁的实例时,仍然保证了操作的独立性,满足了线程安全的要求。
加锁时通过Lua脚本先检查锁是否存在,如不存在则创建hash相关字段并设定过期时间后返回,这表示加锁成功。
如果该hash字段已经存在,再检查随机字段和线程id是否一致。如果一致则递增value的值并重新更新过期时间后返回,此时表示同一节点同一线程再次成功加锁,从而保证了可重入性。如果hash存在且字段不一致,说明其他节点或线程已经拥有了这个锁。因此Lua脚本返回这个hash的当前有效期。当结果返回到在客户端后,如果加锁成功,则通过线程池依照设定好的参数定时执行续约,最后通知请求线程继续后续操作。如果加锁没有成功,则监听一个以这个key为后缀的pubsub频道,直到收到解锁消息后再次重试。
具体lua脚本实现逻辑如下:
2、解锁
解锁时通过Lua脚本先检查锁是否存在,如果已经不存在则直接发布解锁消息并返回。
如果任然存在则检查标签是否存在,如果不存在则表示这个锁并不为本线程所拥有,这种情况请求线程将收到报错。
如果存在则表示该锁正是被该线程所拥有。在这种情况下,递减标签字段后判断,如果返回的加锁数量仍然大于0,说明当前的锁仍然有效,仅仅只是重入次数减少了。相反这表示锁已经完全解开,则立即删除该锁并发布解锁信息。
扩展
红锁(RedLock: https://redis.io/topics/distlock)
分布式联锁 RedissonMultiLock
基于多个节点的高可用分布式锁的算法
说明:本人自己在项目中的应用,了解的可能不是很好,大家多多指正,谢谢!