JAVA 分布式锁的5种实现方式
1、分布式锁的基本概念
分布式锁 是在分布式系统环境下,控制多个进程/服务/主机互斥访问共享资源的核心协调机制。它解决了单机锁(如Java的synchronized
、ReentrantLock
)在分布式场景下失效的问题,是构建可靠分布式应用的必备工具。
解决我们在多个jvm中最终只能有一个jvm执行,从而保证了幂等性的问题。
2、业务超时,一直不释放锁如何处理?
可以采用续命设计,续命多次,如果业务还是没有执行完毕,则认为超时,应该主动释放该锁,防止其他jvm一直阻塞等待。
(1)主动释放锁;(2)回滚当前业务逻辑;(3)主动停止该线程;(4)移除监听。
3、集群环境中保证定时任务执行的幂等性问题
幂等性:执行结果保证唯一不能够重复。
当我们定时任务服务集群的情况下,有可能会同时重复执行定时任务,
解决思路:
多个jvm集群的定时任务,在触发的时候,获取分布式锁,如果能够获取到分布式锁的jvm,就能够执行定时任务,没有获取到的就不能执行定时任务。
4、基于zookeeper实现分布式锁
(1)实现分布式锁思路:
因为Zookeeper节点路径保持唯一,不允许重复,且有临时节点特性,连接关闭后当前节点会自动消失,从而实现分布式锁。
(2)实现原理:(临时节点 + 事件通知)
多个jvm请求同时创建相同的临时节点(lockPath),只要谁能够创建成功 谁就能够获取到锁;如果创建节点的时候,突然该节点已经被其他请求创建的话则直接等待;只要能够创建节点成功,则开始进入到正常业务逻辑操作,其他没有获取到锁的jvm进行等待;正常业务逻辑流程执行完后,调用zk关闭连接方式释放锁,从而使其他的请求开始进入到获取锁的资源。
疑问:如果使用zk实现分布式锁,获取锁之后业务逻辑方法一直没有执行完毕,导致其他所有的请求等待的话如何解决?
设置Session连接超时时间,在规定的时间内获取锁后超时啦~自动回滚当前数据库业务逻辑。
5、基于数据库实现分布式锁
(1)悲观锁
就是先select … for update 锁住主键key_resource那个记录,如果为空,则可以插入一条记录,如果已有记录判断下状态和时间,是否已经超时。这里需要注意一下哈,必须要加事务哈。
(2)搞个version字段
每次更新修改,都会自增加一,然后去更新余额时,把查出来的那个版本号,带上条件去更新,如果是上次那个版本号,就更新,如果不是,表示别人并发修改过了,就继续重试。)
6、基于redis实现分布式锁
(1)采用Setnx
Redis中SetnX与Set命令的区别
Setnx 可以返回该key是否存在,存在返回0 ,不存在返回1,如果该key存在的情况下,是不能做修改的。Set 每次直接覆盖该key对应的value。
获取锁的原理:
多个redis客户端执行setNx命令,设置一个相同的redis的key,谁能创建key成功,谁就能获取锁,如果该key已经存在的情况下,则再创建的时候会返回false。
释放锁的原理:
删除该key。
7、基于redisson实现分布式锁
多个jvm同时在redis中写入一个相同的key,谁能够写入成功,谁就能获取到锁。
(1)向redis中写入key的时候使用lua脚本,不是setNx;
(2)如果写入key成功,会单独开启一个看门狗线程(续命定时任务线程),默认的情况下每隔10s时间不断续命延迟。
(3)一直不断实现续命的情况下,也会发生死锁问题,此时设定续命的次数,续命多次如果业务逻辑还没有执行完毕,主动回滚当前的mysql事务,并释放该锁。
8、基于Curator实现分布式锁(解决羊群效应问题)
核心思想:
(1)从缓存中查找是否已经创建分布式锁,如果已经创建了分布式锁,则直接复用(具有可重入性)。
(2)如果缓存中没有,则创建一个分布式锁。
(3)实现原理:
创建一个临时节点,获取当前父节点下的子节点,如果是为最小的节点,则表示获取锁成功,否则获取锁失败,阻塞等待,则监听上一个节点。
(4)当上一个节点如果释放锁之后,直接进入到获取锁的状态,唤醒使用wait notify技术。