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