1. 问题描述
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程的特点以及分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
2. 分布式锁主流的实现方案:
-
基于数据库实现分布式锁
-
基于缓存(Redis 等)
-
基于 Zookeeper
根据实现方式,分布式锁还可以分为类 CAS 自旋式分布式锁以及 event 事件类型分布式锁:
-
类 CAS 自旋式分布式锁:询问的方式,类似 java 并发编程中的线程获询问的方式尝试加锁,如 mysql、redis。
-
另外一类是 event 事件通知进程后续锁的变化,轮询向外的过程,如 zookeeper、etcd。
每一种分布式锁解决方案都有各自的优缺点:
性能:redis 最高
可靠性:zookeeper 最高
3.用java代码使用redis分布锁
用setnx 的返回值来判断是否有线程正在进行操作,只是用一个键值对作为锁,和java业务代码没有关系
线程是一个个进行排队,只有当返回值是0的时候,说明前面以及没人在操作了.
4.遇到的问题
4.1 其他线程把正在执行线程的锁给释放了
解决方法:利用UUID生成随机值,当进行释放锁操作时先对UUID的随机值进行判断,相同再释放. 这就解决了可能被其他线程释放锁的问题.
4.2 还有一种可能就是在确认过uuid,即将要释放锁时,锁到了过期时间,自动释放了. 这时候另一个线程拿到了锁,并且在进行具体操作时,锁被上一个线程给释放了.
解决方法: 使用lua脚本进行释放锁.
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1. 互斥性。在任意时刻,只有一个客户端能持有锁。
2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
4. 加锁和解锁必须具有原子性。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280990.html