![8cc5dd16933336cd3fb6e9aa705c0333.png](https://i-blog.csdnimg.cn/blog_migrate/0356256e674eb30061a7bef7576215e5.png)
前言
这篇文章介绍下如何实现redis来实现分布式锁及原理简介
原理简介
redis 获取分布式锁使用lua脚本的命令
- setnx
- pexpire(提供了毫秒的过期时间,expire提供了基于秒的过期时间)
- lua脚本(保证脚本中的命令被一起执行 不间断)
redis删除锁使用lua脚本的命令
- 先执行get
- 判断获取的值是否是自己设置的
- 如果是的话 则执行del操作
- lua脚本(保证脚本中的命令被一起执行 不间断)
流程图说明
![2cdadc39a64acaa2ab67aebb00a47e9c.png](https://i-blog.csdnimg.cn/blog_migrate/1ab3901105f8fa1b53adb1193e604fa9.png)
代码实现
pom依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
建立redis连接
![dcffa851210aec204509565b6311671c.png](https://i-blog.csdnimg.cn/blog_migrate/ed69da989742ec09aa2564d5f7dc9ae1.png)
获取锁实现
- 获取redis锁的lua脚本
![78e61bd6f0cc4b930351261b685827ed.png](https://i-blog.csdnimg.cn/blog_migrate/2e1cca6e694d36d011e33f6194d42cbe.png)
执行setnx(key,value)命令,如果返回1 然后设置过期时间pexpire
- 对应的java代码
![0939e60953d54cb1dbc7aaeffcd0a66d.png](https://i-blog.csdnimg.cn/blog_migrate/4a4c917387a35ed2618a07b6507e0fef.png)
过期处理机制
由于redis没有像zookeeper一样的会话机制来保证业务运行期间一直持有锁,从而使用redis的key的过期时间来保证业务运行期间一直持有锁
- 租期续约的lua脚本
![7fd40e692806db19030674a535fad579.png](https://i-blog.csdnimg.cn/blog_migrate/cd0cf6c9e5833a2ca75486764b4d8e88.png)
先获取到get(key),然后设置一个新的过期时间pexpire
- 租期续约对应的java代码
人为的启用一个任务来获取锁并延长过期时间,以此达到和zookeeper分布式锁同样的效果
![26f8f821fcd52cb97d4256a1adbd83f7.png](https://i-blog.csdnimg.cn/blog_migrate/ff152faa2f8829765ff3cb05fc63d44c.png)
- 缺点
1⃣️ 性能问题
这样的续期方式使得性能有所下降,如果同一个应用中若是有很多线程去获取锁,那么就会启动很多的timer线程,会增加系统开销
2⃣️ 严重依赖
续租时间严重依赖锁过期时间 如果锁过期时间很短 某一时间客户端和redis服务器之间出现网络抖动了就可能出现该业务没执行完(业务执行时间稍微大于锁过期时间) 锁过期导致被删除 前一个获取锁的线程就会在无锁的状态下运行
释放锁的实现
- 释放锁的lua脚本
![70ee739ac77af833c130aaed52f8eaa5.png](https://i-blog.csdnimg.cn/blog_migrate/265cb0d27694234d539eea90a56e3c11.png)
先获取get(key) 若获取成功才del
该脚本保证只能获取锁的线程才能释放锁
- 对应的java代码
![88775ca640bf58bc04fefc132bc2dbfd.png](https://i-blog.csdnimg.cn/blog_migrate/2c10fbcf5eb1f80e0d12b4ebf3316948.png)
源码分析
socket连接
代码跟踪
Object result = client.evalsha(unlockScriptSHA, 1, lockData.key, lockData.owner)
到
Connection sendCommand(Command cmd, byte[]... args)
可以看到每次client和redis服务端交互都会建立一个socket连接
![0d8ab559140da59744c088264731c1a3.png](https://i-blog.csdnimg.cn/blog_migrate/3a0adceabc5cacebbb88ad3eeae41878.png)
调用client.close()方法就会关掉socket连接
![e4e8f489ca7bdd476b8564f7bf4622bb.png](https://i-blog.csdnimg.cn/blog_migrate/b728cbe67812106f6a67fb20959518a6.png)
瓶颈
由此可见 redis客户端每次和服务器交互都会建立一个socket连接 如果这个服务中使用redis量很大 那么就会是一个瓶颈 这时可以使用jedisPools(连接池来优化)
虽然客户端和服务端有建立连接 但redis服务不会根据连接的有效性给该连接重新设置key的过期时间 因此redis的分布式锁需要客户端自己去延长过期时间或者在最开始的时候 设置一个足够长的时间来满足业务直到执行完这期间都会持有锁
redis特性
一致性
redis集群中leader和slave之间的数据复制是采用 异步的方式 (因为需要满足高性能的要求) 即leader将客户端发送的写请求记录下来之后 就给客户端返回了响应 后续该leader的slave节点就会从该leader节点复制数据 那么就会存在一种可能性 : leader接收了客户端的写请求,也给客户端响应了,但是该数据还没来得及复制到它对应的slave节点中,leader就crash了,从slave节点中重新选举出来的leader也不包含之前leader最后写的数据了,这时,客户端来获取同样的锁就可以获取到,这样就会在同一时刻,两个客户端持有锁
CAP
redis的初衷是提供一个高性能的内存存储,对客户端的请求需要很快速的作出响应,因此,高性能是一个重要目标,如果要保证leader和slave之间的数据同步一致,就会牺牲性能。setinel和cluster都实现了高可用,也保证了P,因此redis保证了CAP中的AP。
公平竞争
上述实现的redis分布式锁不具有获取失败排队等待的情况,因此不具有偏向性。任意时刻,都是竞争获取。
总结
redis分布式锁具有高并发、高可用的特性,但是,在极端情况下,存在一定的问题。redis官网提供的redlock在redisson中实现了,由于它需要在大多数节点中都获取同样的锁,因此相较于单节点的锁获取,性能会有所降低。
源码
https://gitee.com/pingfanrenbiji/distributed-lock/tree/master/redis
参考文章
https://my.oschina.net/yangjianzhou/blog/1930493