redis作为客户端连接redis实现分布式锁机制

1、利用 jedis.set(String key, String value, String nxxx, String expx, int time)

redis 分布式锁应用
利用set直接设置过期时间2s,执行job后,在finally执行del解锁。

2、利用jedis.setnx()和jedis.expire()组合实现加锁

redis 分布式锁应用

第一步利用setnx加锁,如果锁已存在,则返回结果为0,如果成功加锁,设置过期时间2s,最后finally,执行del解锁。

Jedis set & setnx命令封装的分布式锁的不足

1)、不具备可重入性

当遇到业务上需要在多个地方用到同样一个锁的时候,很显然使用不具有可重入的锁会很容易发生死锁的现象。
Java并发工具包里的Lock对象和sychronized语块都具有可重入性,对于经常使用这些工具的人来说,往往会很容易忽略setnx的这个缺陷。

2)、不支持续约

setnx的设计缺乏一个延续有效期的续约机制,无法保证业务能够先工作做完再解锁,也不能确保在某个程序宕机或业务节点挂掉的时候,其它节点能够很快的恢复业务处理能力。

3)、不具备阻塞的能力

锁都具备两个共性:一是互斥性,二是阻塞性。
阻塞性是指的在有竞争的情况下,未获取到资源的线程会停止继续操作,直到成功获取到资源或取消操作。
setnx命令只提供了互斥的特性,却没有提供阻塞的能力。

Redisson作为redis连接客户端的分布式锁机制

jedis set 或者setnx 的三点不足,redisson都满足,且在此基础上还增加了线程安全的特点。

利用redisson实现分布式锁

redis 分布式锁应用

执行加锁的并发测试

测试代码:
redis 分布式锁应用

锁后执行job代码:
redis 分布式锁应用
结果:
redis 分布式锁应用
(源码地址: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脚本实现逻辑如下:
redis 分布式锁应用

2、解锁

        解锁时通过Lua脚本先检查锁是否存在,如果已经不存在则直接发布解锁消息并返回。
如果任然存在则检查标签是否存在,如果不存在则表示这个锁并不为本线程所拥有,这种情况请求线程将收到报错。
        如果存在则表示该锁正是被该线程所拥有。在这种情况下,递减标签字段后判断,如果返回的加锁数量仍然大于0,说明当前的锁仍然有效,仅仅只是重入次数减少了。相反这表示锁已经完全解开,则立即删除该锁并发布解锁信息。
redis 分布式锁应用

扩展

红锁(RedLock: https://redis.io/topics/distlock)
分布式联锁 RedissonMultiLock
基于多个节点的高可用分布式锁的算法

说明:本人自己在项目中的应用,了解的可能不是很好,大家多多指正,谢谢!

相关文章: