简述
锁是一种同步机制,保证了多线程的有序竞争和运行,而在分布式的场景下,两个应用程序同样会有对于公共变量的访问和操作行为,对于分布式锁,常用的有三种方案:
数据库方式,使用select * from table where column = para for update加排他锁;
中间件缓存,例如redis的setnx+lua脚本或者set key value ps milliseconds nx;
zookeeper临时节点。
分布式锁要满足以下几个条件:
互斥;
不死锁;
容错;
唯一解锁。
Redis
本机环境:Windows 版Redis
IDE:IDEA 2019.1.1
Jedis
引入pom:
1 | <dependency> |
要注意的是,尽量保证加锁和释放锁时的原子操作,以及value的唯一性和value与会话的匹配:
1 | public class RedisPool { |
Redisson
使用Jedis只能满足单机redis的场景,对于redis集群,如果出现类似于主备切换等场景,可能会导致锁丢失。Redis的作者提出了Redlock的实现:
获取当前unix时间,单位为millisecond;
假如有5个redis节点,使用相同的key和具有唯一性的value获取锁;
客户端使用当前时间减去第一步里的时间就是获取锁的时间,只有当N/2+1的节点都获取到锁并且使用时间小于失效时间时表示获取成功;
如果获取锁超时或者没有获取到锁,应该在所有的节点进行解锁操作。
Redlock类似于Reetrantlock,Redisson封装了Redlock算法,使用eval执行lua脚本。
Redisson提供了几种集群模式:单机SingleServer,ClusterServer,Maste/SlaveServer,SentinelServer:
引入pom:
1 | <dependency> |
代码:
1 | Config config = new Config(); |
Zookeeper
本机环境:Zookeeper 3.4.10
IDE:IDEA 2019.1.1
首先启动zk,然后启动zkCli,创建一个父节点:
1 | [zk: localhost:2181(CONNECTED) 9] create /LOCKS 00 |
Zookeeper
引入zk原生jar包,还有辅助的lombok包:
1 | <dependency> |
zookeeper分布式锁的原理是:客户端在父节点下创建临时子节点,然后获取所有子节点,判断当前创建的临时节点是否是最小节点,如果是最小节点即表示获取锁,如果不是最小节点则监听当前节点的前一个节点,如果监听到前一节点删除则当前客户端获取到锁*。使用临时节点可以避免死锁,这里使用countDownLatch限制当前只有一个客户端连接zk:
1 | public class ZookeeperClient { |
然后需要一个监听器监听前一节点是否删除:
1 | public class LockWatcher implements Watcher { |
获取锁的代码:
1 | 4j |
测试类:
1 | public static void main(String[] args) { |
执行方法可以看到结果:
1 | 17:55:03.680 [Thread-0] INFO com.joy.lock.zookeeper.DistributedLock - 当前线程:Thread-0 创建节点,id=/LOCKS/0000000157 |
Curator
当然apache已经封装好了分布式锁的实现,需要引入Curator的jar包:
1 | <dependency> |
Java代码比较简单:
1 | 4j |
测试类如下:
1 | public static void main(String[] args) { |
日志如下:
1 | 18:04:55.467 [Thread-2] INFO com.joy.lock.zookeeper.CuratorDistributedLock - 当前线程:Thread-2获取锁 |
在程序运行中,可以在zkcli中执行
ls /LOCKS
随时看临时子节点的存在。
参考
https://blog.csdn.net/qq_26857649/article/details/82383853
阿飞的博客: