目录
一、分布式锁有哪些应用场景?
二、分布式锁的实现方案
三、zookeeper实现分布式锁
一直不释放锁怎么办?
如何避免分布式锁羊群效应问题?
四、redis实现分布式锁
一、分布式锁有哪些应用场景?
1、定时任务
2、秒杀抢购,防止库存超卖的问题
3、双写一致性协议
比如我们为了高可用性搭建了服务集群,分别是8080和8081,我们在项目中设立定时任务,目的是每天晚上定时拉取用户数据,给每个人发送一些推荐短信。那么这会出现什么问题呢?8080和8081都有定时任务,到半夜2点同时查询数据库,同时调用阿里云接口发短信,那么肯定会重复,使用了分布式锁,8080抢到锁执行定时任务,那么8081就会阻塞不会执行。
那么肯定会有人问,为什么不用synchronized锁呢?
如果我们是单个项目,用synchronized锁可以实现,但我们用的是集群,synchronized是无法跨jvm的。
二、分布式锁的实现方案
(1)基于数据库实现——mysql行锁
(2)基于zookeeper CP模式
(3)基于redis setnx实现 AP模式
(4)Redis框架 Redisson、RedisLock
要求:
- 保证一致性:zookeeper 实现分布式锁
- 保证可用性:redis实现分布式锁
三、zookeeper实现分布式锁
zookeeper有个节点路径的概念,节点路径不能重复,保证了唯一性。
如图,我有4个springboot项目,首先jvm1先抢到了资源,设置了zk的节点路径/lockPath,这个操作就相当于获取到了锁,这时其余三个jvm获取锁失败进行阻塞状态。当jvm1执行任务完毕,调用close()关闭连接,zk自动删除节点路径释放锁,zk通知其余3个jvm节点,它们3个开始竞争锁。
一直不释放锁怎么办?
我们上面说的是正常理想情况,那么问题来了,如果jvm1一直不释放锁,该怎么办?
可以采用续命设计(设置超时时间),续命多次如果业务还是没有执行完毕的情况下,则认为该锁超时应该主动释放该锁,再将所有业务代码回滚,防止其它jvm一直阻塞等待。
如何避免分布式锁羊群效应问题?
如图可以看出,当我们有100个jvm的时候,如果jvm1抢到了锁,执行完业务释放了锁,zk就要唤醒其余99个jvm,那唤醒这个操作成本是很高的。
如何解决呢?
采用zk的临时顺序节点。
我们现在有三个jvm,分别创建了三个临时顺序节点路径,谁最小就获取锁成功,首先jvm1最小获取锁成功,jvm2和jvm3就阻塞,jvm2创建的临时节点就去订阅最小的/lockPath1,当jvm1执行完毕释放锁并删除/lockPath1节点,那么现在/lockPath2就是最小的节点,获取锁成功。
其实就相当于synchronized的公平锁,jvm1、jvm2、jvm3依次按顺序执行,这样我们就不用唤醒所有,jvm1节点消失,我只需要唤醒jvm2节点。
四、redis实现分布式锁
如果不存在值,则返回1,如果存在,则返回0。
那也就是说,我jvm1先setnx返回1抢到了锁,这时jvm2也setnx发现返回0,那就无法执行业务。
当我们执行业务完成后,删除此key就起到了释放锁的作用。
那么问题来了,一个老生常谈的话题,如果jvm1一直不释放锁怎么办?
答:先拿setnx来争抢锁,抢到之后,再用expire命令给锁加一个过期时间防止锁忘记了释放。
但这样还有问题,如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那该怎么解决?
答:我们可以使用lua脚本来使setnx+expire成为原子操作。