一.原理.
zk实现分布式锁主要是使用zk得监听机制来完成得.
这里简单介绍一下zk得Watcher监听机制.
1.首先它是ZooKeeper的一个核心功能.
2.watcher是客户端创建的,监听目录节点的数据变化和子目录的变化的
3.而一旦数据或者子目录状态发生变化,服务器就会通知所有设置在这个目录节点上的watcher.
4. 那么客户端在下一次心跳时,就可以感知到变化,从而做出相应操作.(客户端是通过心跳与zk维持链接的)
举例:有一个根节点root,下面有一个子节点node1,node2,等,比如客户端client1在root上创建一个子节点变更的watcher监听的话,
然后删除了node1,发生了变化,然后客户端在下一次心跳时就可以感知到这个变化,执行相关操作.
二.步骤.
zk分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新枷锁。
具体步骤:
- 假如有三个客户端a,b,c尝试获取锁,每个客户端需要做得就是尝试创建同样路径得临时节点(临时节点得特性是,客户端通过心跳和zk维持链接,如果客户端挡掉了,链接消失,那么对应的临时节点也会消失.)
- 因为节点 具有唯一性,所以a,b,c只能有一个创建成功,这里假如a创建成功,那么a就获取到了这个锁,b和c就需要注册一个watch监听;
- 如果a获取到锁在执行也业务逻辑得过程中挡掉了,那么因为是临时节点,所以服务端会自动删除这个临时节点,同时通知b和c,如果a正常执行完,将这个临时节点进行删除,服务端也会感知到,通知b和c进行重新抢锁.
三.简单代码
/**
* ZooKeeperSession
* @author Administrator
*
*/
public class ZooKeeperSession {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private ZooKeeper zookeeper;
private CountDownLatch latch;
public ZooKeeperSession() {
try {
this.zookeeper = new ZooKeeper(
"192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181",
50000,
new ZooKeeperWatcher());
try {
connectedSemaphore.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("ZooKeeper session established......");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取分布式锁
* @param productId
*/
public Boolean acquireDistributedLock(Long productId) {
String path = "/product-lock-" + productId;
try {
zookeeper.create(path, "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
return true;
} catch (Exception e) {
while(true) {
try {
Stat stat = zk.exists(path, true); // 相当于是给node注册一个监听器,去看看这个监听器是否存在
if(stat != null) {
this.latch = new CountDownLatch(1);
this.latch.await(waitTime, TimeUnit.MILLISECONDS);
this.latch = null;
}
zookeeper.create(path, "".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
return true;
} catch(Exception e) {
continue;
}
}
}
return true;
}
/**
* 释放掉一个分布式锁
* @param productId
*/
public void releaseDistributedLock(Long productId) {
String path = "/product-lock-" + productId;
try {
zookeeper.delete(path, -1);
System.out.println("release the lock for product[id=" + productId + "]......");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 建立zk session的watcher
* @author Administrator
*
*/
private class ZooKeeperWatcher implements Watcher {
public void process(WatchedEvent event) {
System.out.println("Receive watched event: " + event.getState());
if(KeeperState.SyncConnected == event.getState()) {
connectedSemaphore.countDown();
}
if(this.latch != null) {
this.latch.countDown();
}
}
}
/**
* 封装单例的静态内部类
* @author Administrator
*
*/
private static class Singleton {
private static ZooKeeperSession instance;
static {
instance = new ZooKeeperSession();
}
public static ZooKeeperSession getInstance() {
return instance;
}
}
/**
* 获取单例
* @return
*/
public static ZooKeeperSession getInstance() {
return Singleton.getInstance();
}
/**
* 初始化单例的便捷方法
*/
public static void init() {
getInstance();
}
}
四.redis分布式锁和zk分布式锁的对比
redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能
zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小
另外一点就是,如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁
redis分布式锁大家每发现好麻烦吗?遍历上锁,计算时间等等。。。zk的分布式锁语义清晰实现简单
所以先不分析太多的东西,就说这两点,我个人实践认为zk的分布式锁比redis的分布式锁牢靠、而且模型简单易用