基于zookeeper 实现分布式锁
上篇文章讲到了redis实现分布式锁的方法,也对分布式锁中锁产生的不一致性的的问题简单的讲解。这篇也简单说下zk实现分布式锁。
实现原理
临时节点
让多个进程一同去创建临时节点,这些进程必然只会有一个去创建临时节点,创建临时节段的进程就会抢占到这个锁。同时如果释放锁,就去删除这个znode。如果这是时候客户端宕机了,或者挂了,zk的watch机制也会检测到与客户端没有通信。也会删除这个临时znode,这时候其他的进程监测这个znode,一旦发生这种情况,会有其他的线程继续创建临时znode。
这种机制有效避免了客户端与zk发生死锁的情况,但是也会大大增加服务端压力,一个临时节点的释放,会有多个线程监听并抢占一个锁,这就是zk分布式锁的羊群效应,如何有效避免这种情况。并且这种锁不是公平锁,就会发生有的线程会一直抢不到锁。这时候就要进行有序的临时节点
有序的临时节点
1.每个进程都会去临时节点下创建锁,这时候创建的是有序的递增临时节点。比如znode000000000001,第二个创建的就是znode000000000002。
2.这时候会获取都所有节点的的值,判断自己是不是最小值。
3.如果是最小值,则通过watch高速客户端获取了这把锁。
这个有序锁解决了公平锁的问题,但是没有解决羊群效应所带来的zk的瞬间的压力
读写锁
读写锁用的也是用的有序的临时节点。
这时候读写算不用去取关注自己是不是最小节点。只需要关注自己的前一个节点有没有释放,如果钱一个节点释放了,就获取了这把锁。
读写锁有效的解决了公平锁的问题和羊群效应
Apache Curator
Apache Curator 是 ZooKeeper 的 Java 客户端,它基于临时有序节点方案实现了分布式锁、分布式读写锁等功能
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
基本使用:
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.0.105:2181")
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("mySpace").build();
client.start();
// 1. 创建分布式锁
InterProcessMutex lock = new InterProcessMutex(client, "/distributed/myLock");
// 2.尝试获取分布式锁
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
System.out.println("模拟业务耗时");
Thread.sleep(3 * 1000);
} finally {
// 3.释放锁
lock.release();
}
}
client.close();
这时候如果有多个线程来获取这把锁,那么指定路径下会创建多个有序的锁。
关于源码我就不多做介绍了
脑裂
众所周知,zookeeper集群的所有操作都是Leader操作的,如果一个集群内有多个leader的话,就会导致整个集群的数据不一致的情况。破坏了整个集群的可用性。
zk解决这个问题依靠选票机制,奇数节点和半个以上的票数的支持。这样就保证了一个集群内只有一个leader。
也就是说一个集群内至少有三个节点,且存活的节点要>2.