分布式锁
什么用
多机场景下对一个资源加锁,单机锁已经没办法控制了
怎么加
分布式锁一般要考虑的就是
一般还是采用
try {
加锁(key, lockId, 超时时间)
} finally {
解锁(key, lockId)
}
原子操作问题
比如加锁和设置超时时间必须要原子操作
解锁和判断是否是自己加的锁也需要有原子性
判断是否是自己加的锁常用的方案就是进入代码就创建一个UUID(lockId)
解锁的时候先判断是自己加的锁再解锁
锁续期问题
如果所设置的超时时间结束了还是没有操作完业务
还是会出现问题,所以就需要考虑锁的续期问题
Redisson实现
原子操作
Lua脚本实现
好处:
- 减少网络开销
- 原子性:脚本执行中间不会执行其他命令
- 事务功能
锁续期
启动后台线程每隔10s检查是否还持有锁
如果有则延长锁时间
没有获取锁自旋
Lua脚本获取锁剩余时间
while(true)
刷新剩余时间
然后 信号量等待剩余时间
源码
引入redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
加锁
是否存在lockKey
不存在则:设置Hash,key:lockKey,filedKey:UUID+threadId,value:1
并设置超时时间
最后返回null
默认超时时间为:
获取锁线程续期
进入方法后延时10s执行
根据lockKey,UUID+threadId判断锁是否存在
如果存在则续期后返回1(Java:true)重新调用续期方法
未获取锁线程
返回锁的ttl(剩余有效时间)
获取Semaphore进入超时时间的等待
单纯的等待其实是不行的,就是虽然超时时间还没到但是锁住的线程已经处理完了
那这个时候等待的线程应该被唤醒
redis的订阅功能
订阅:SUBSCRIBE channel
发布:PUBLISH channel message
那什么时候唤醒呢?其实就是解锁的时候发布
那拿到发布的消息后怎么消费呢?
其实就在创建的时候设置消费处理
解锁逻辑
我们可以看到如果还存在key和key+filedKey
则删除key
然后给订阅者发布解锁消息(内容为:0)
主从失效问题
主接点加锁时但是没有同步给从节点的时候主节点挂了
此时锁失效
性能优化
缩小锁的粒度
分段:大的资源拆分成小的资源后加锁