引言
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在zookeeper-3.4.3\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
ZooKeeper简介
ZooKeeper是一个典型的分布式数据一致性的解决方案。分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。ZooKeeper可以保证如下分布式一致性特性。
一致性
从同一个客户端发起的事务请求,最终将会严格按照其发起顺序被应用到ZooKeeper中。
原子性
所有事务请求的结果在集群中所有机器上的应用情况是一致的,也就是说要么整个集群所有集群都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,而另外一部分没有应用的情况。
单一视图
无论客户端连接的是哪个ZooKeeper服务器,其看到的服务端数据模型都是一致的。
可靠性
一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有另一个事务又对其进行了变更。
实时性
通常人们看到实时性的第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。这里需要注意的是,ZooKeeper仅仅保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。
数据模型
如上图所示,ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。每个ZNode都可以通过其路径唯一标识,比如上图中第三层的第一个ZNode, 它的路径是/app1/c1。在每个ZNode上可存储少量数据(默认是1M, 可以通过配置修改, 通常不建议在ZNode上存储大量的数据),这个特性非常有用,在后面的典型应用场景中会介绍到。另外,每个ZNode上还存储了其Acl信息,这里需要注意,虽说ZNode的树形结构跟Unix文件系统很类似,但是其Acl与Unix文件系统是完全不同的,每个ZNode的Acl的独立的,子结点不会继承父结点的,关于ZooKeeper中的Acl可以参考之前写过的一篇文章《说说Zookeeper中的ACL》。
重要概念
ZNode
前文已介绍了ZNode, ZNode根据其本身的特性,可以分为下面两类:
Regular ZNode: 常规型ZNode, 用户需要显式的创建、删除
Ephemeral ZNode: 临时型ZNode, 用户创建它之后,可以显式的删除,也可以在创建它的Session结束后,由ZooKeeper Server自动删除
ZNode还有一个Sequential的特性,如果创建的时候指定的话,该ZNode的名字后面会自动Append一个不断增加的SequenceNo。
Session
Client与ZooKeeper之间的通信,需要创建一个Session,这个Session会有一个超时时间。因为ZooKeeper集群会把Client的Session信息持久化,所以在Session没超时之前,Client与ZooKeeper Server的连接可以在各个ZooKeeper Server之间透明地移动。
在实际的应用中,如果Client与Server之间的通信足够频繁,Session的维护就不需要其它额外的消息了。否则,ZooKeeper Client会每t/3 ms发一次心跳给Server,如果Client 2t/3 ms没收到来自Server的心跳回应,就会换到一个新的ZooKeeper Server上。这里t是用户配置的Session的超时时间。
Watcher
ZooKeeper支持一种Watch操作,Client可以在某个ZNode上设置一个Watcher,来Watch该ZNode上的变化。如果该ZNode上有相应的变化,就会触发这个Watcher,把相应的事件通知给设置Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即触发一次就会被取消,如果想继续Watch的话,需要客户端重新设置Watcher。这个跟epoll里的oneshot模式有点类似。
可以利用Zookeeper不能重复创建一个节点的特性来实现一个分布式锁,这看起来和redis实现分布式锁很像。
主要流程图如下:
Paste_Image.png
上面的流程很简单:
查看目标Node是否已经创建,已经创建,那么等待锁。
如果未创建,创建一个瞬时Node,表示已经占有锁。
如果创建失败,那么证明锁已经被其他线程占有了,那么同样等待锁。
当释放锁,或者当前Session超时的时候,节点被删除,唤醒之前等待锁的线程去争抢锁。
分布式锁实现. 这种实现的原理是,创建某一个任务的节点,比如 /lock/tasckname 然后获取对应的值,如果是当前的Ip,那么获得锁,如果不是,则没获得
* .如果该节点不存在,则创建该节点,并把改节点的值设置成当前的IP
*/
publicclassDistributedLock01 {
privateZKClient zkClient;
publicstaticfinalString LOCK_ROOT ="/lock";
privateString lockName;
publicDistributedLock01(String connectString,intsessionTimeout,String lockName)throwsException {
//先创建zk链接.
this.createConnection(connectString,sessionTimeout);
this.lockName = lockName;
}
publicbooleantryLock(){
String path = ZKUtil.contact(LOCK_ROOT,lockName);
String localIp = NetworkUtil.getNetworkAddress();
try{
if(zkClient.exists(path)){
String ownnerIp = zkClient.getData(path);
if(localIp.equals(ownnerIp)){
returntrue;
}
}else{
zkClient.createPathIfAbsent(path,false);
if(zkClient.exists(path)){
String ownnerIp = zkClient.getData(path);
if(localIp.equals(ownnerIp)){
returntrue;
}
}
}
}catch(Exception e) {
e.printStackTrace();
}
returnfalse;
}
/**
* 创建zk连接
*
*/
protectedvoidcreateConnection(String connectString,intsessionTimeout)throwsException {
if(zkClient !=null){
releaseConnection();
}
zkClient =newZKClient(connectString,sessionTimeout);
zkClient.createPathIfAbsent(LOCK_ROOT,true);
}
/**
* 关闭ZK连接
*/
protectedvoidreleaseConnection()throwsInterruptedException {
if(zkClient !=null) {
zkClient.close();
}
}
}
总结
以 上就是我对Java开发大型互联网基于Zookeeper的分布式锁实现之Zookeeper原理 问题及其优化总结,分享给大家,觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!
最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!
想了解学习Java方面的技术内容以及Java技术视频的内容可加群:722040762 验证码:简书(666 必过)欢迎大家的加入哟!
网友评论