参考:https://www.cnblogs.com/leocook/p/zk_1.html

代码:https://github.com/littlecarzz/zookeeper

1. 数据模型

1.1. 只适合存储小数据

Zk维护着一个逻辑上的树形层次结构,树中的节点称为znode,个znode都有一个ACL(权限控制)。Zookeeper是被设计用来协调服务的,因此znode里存储的都是小数据,而不是大容量的数据,数据容量一般在1MB范围内。

1.2. 操作的原子性

Znode的数据读写是原子的,要么读或写了完整的数据,要么就失败,不会出现只读或写了部分数据。

1.3. Znode的路径

和Unix中的文件系统路径格式很想,但是只支持绝对路径,不支持相对路径,也不支持点号(”.”和”..”)。

1.4. 短暂的znode和持久的znode

Znode有两种类型:短暂的和持久的。短暂的znode生命周期仅限创建它的客户端与服务器端之间的连接没有断开,客户端断开连接后,znode将会被删除。

1.5. 顺序znode

名称中包含Zookeeper指定顺序号的znode。若在创建znode时设置了顺序标识,那么该znode被创建后,名字的后边将会附加一串数字,该数字是由一个单调递增的计数器来生成的。例如,创建节点时传入的path是”/aa/bb”,创建后的则可能是”/aa/bb0002”,再次创建后是”/aa/bb0003”。

Znode的创建模式CreateMode有四种,分别是:EPHEMERAL(短暂的znode)、EPHEMERAL_SEQUENTIAL(短暂的顺序znode)、PERSISTENT(持久的znode)和PERSISTENT_SEQUENTIAL(持久的顺序znode)。如果您已经看过了上篇博文,那么这里的api调用应该是很好理解的,见:http://www.cnblogs.com/leocook/p/zk_0.html。

1.6. 观察

这部分在上篇博文中已经做了详细的说明,包括连接的观察和znode的观察,这部分在构建一个稳定的zookeeper应用中有着很重要的作用,具体会在下边说到。

2.  ACL

即:Access Control List(访问控制列表)。Znode被创建时带有一个ACL列表,zk提供了下边三种身份验证模式

  • digest

用户名+密码验证。

  • host

客户端主机名hostname验证。

  • ip

客户端的IP验证。

  • auth

使用sessionID验证

  • world

无验证,默认是无任何权限。该模式较为特殊,在zk连接添加ACL中会说到

ACL权限对应如下表:

在设置ACL时,可以给zk客户端和服务器端的连接设置ACL,也可以在创建znode时,给znode设置ACL,在创建了znode后,如果有zk客户端来操作znode,只有满足权限要求时,才能完成相对应的操作:

2.1. 给ZK连接添加ACL

可使用zk对象的addAuthInfo()方法来添加验证模式,如使用digest模式进行身份验证:zk.addAuthInfo(“digest”,”username:passwd”.getBytes());

在zookeeper对象被创建时,初始化会被添加world验证模式。world身份验证模式的验证id是”anyone”。

若该连接创建了znode,那么他将会被添加auth身份验证模式的验证id是””,即空字符串,这里将使用sessionID进行验证。

2.2. 给znode设置ACL

  • 自己创建ACL

创建ACL对象时,可用ACL类的构造方法ACL(int perms, Id id)

其中参数perms表示权限,在接口org.apache.zookeeper.ZooDefs.Perms中有相关的常量:READ、WRITE、CREATE、DELETE、ALL和ADMIN,它们值如下表:

Id参数是验证模式,可用构造方法Id(String scheme, String id)来创建。参数scheme是验证模式,digest、host或ip,id是对应的验证,digest对应用户名和密码对,如“user:passwd”;host对应主机名,如”localhost”;ip对应ip地址,如”192.168.1.120”。

  • 使用api中预设的ACL

在创建znode时可以设置该znode的ACL列表。接口org.apache.zookeeper.ZooDefs.Ids中有一些已经设置好的权限常量,例如:

(1)、OPEN_ACL_UNSAFE:完全开放。事实上这里是采用了world验证模式,由于每个zk连接都有world验证模式,所以znode在设置了OPEN_ACL_UNSAFE时,是对所有的连接开放。

(2)、CREATOR_ALL_ACL:给创建该znode连接所有权限。事实上这里是采用了auth验证模式,使用sessionID做验证。所以设置了CREATOR_ALL_ACL时,创建该znode的连接可以对该znode做任何修改。

(3)、READ_ACL_UNSAFE:所有的客户端都可读。事实上这里是采用了world验证模式,由于每个zk连接都有world验证模式,所以znode在设置了READ_ACL_UNSAFE时,所有的连接都可以读该znode。

注:红色部分是本人阅读源码的一些研究,auth和world的相关描述经供参考。

3. 运行模式

Zookeeper有两种运行模式:独立模式(standalone mode)和复制模式(replicated mode)。

3.1. 独立模式

只有一个zookeeper服务实例,不可保证高可靠性和恢复性,可在测试环境中使用,生产环境不建议使用。

3.2. 复制模式

复制模式也就是集群模式,有多个zookeeper实例在运行,建议多个zk实例是在不同的服务器上。集群中不同zookeeper实例之间数据不停的同步。有半数以上的实例保持正常运行,zk服务就能正常运行,例如:有5个zk实例,挂了2个,还剩3个,依然可以正常工作;如有6个zk实例,挂了3个,则不能正常工作。

每个znode的修改都会被复制到超过半数的机器上,这样就会保证至少有一台机器会保存最新的状态,其余的副本最终都会跟新到这个状态。Zookeeper为实现这个功能,使用了Zab协议,该协议有两个可以无限重复的阶段:

  • 选举领导

集群中所有的zk实例会选举出来一个“领导实例”(leader),其它实例称之为“随从实例”(follower)。如果leader出现故障,其余的实例会选出一台leader,并一起提供服务,若之前的leader恢复正常,便成为follower。选举follower是一个很快的过程,性能影响不明显。

Leader主要功能是协调所有实例实现写操作的原子性,即:所有的写操作都会转发给leader,然后leader会将更新广播给所有的follower,当半数以上的实例都完成写操作后,leader才会提交这个写操作,随后客户端会收到写操作执行成功的响应。

  • 原子广播

上边已经说到:所有的写操作都会转发给leader,然后leader会将更新广播给所有的follower,当半数以上的实例都完成写操作后,leader才会提交这个写操作,随后客户端会收到写操作执行成功的响应。这么来的话,就实现了客户端的写操作的原子性,每个写操作要么成功要么失败。逻辑和数据库的两阶段提交协议很像。

3.3. 复制模式下的数据一致性

Znode的每次写操作都相当于数据库里的一次事务提交,每个写操作都有个全局唯一的ID,称为:zxid(ZooKeeper Transaction)。ZooKeeper会根据写操作的zxid大小来对操作进行排序,zxid小的操作会先执行。zk下边的这些特性保证了它的数据一致性:

  • 顺序一致性

任意客户端的写操作都会按其发送的顺序被提交。如果一个客户端把某znode的值改为a,然后又把值改为b(后面没有其它任何修改),那么任何客户端在读到值为b之后都不会再读到a。

  • 原子性

这一点再前面已经说了,写操作只有成功和失败两种状态,不存在只写了百分之多少这么一说。

  • 单一系统映像

客户端只会连接host列表中状态最新的那些实例。如果正在连接到的实例挂了,客户端会尝试重新连接到集群中的其他实例,那么此时滞后于故障实例的其它实例都不会接收该连接请求,只有和故障实例版本相同或更新的实例才接收该连接请求。

  • 持久性

写操作完成之后将会被持久化存储,不受服务器故障影响。

  • 及时性

在对某个znode进行读操作时,应该先执行sync方法,使得读操作的连接所连的zk实例能与leader进行同步,从而能读到最新的类容。

注意:sync调用是异步的,无需等待调用的返回,zk服务器会保证所有后续的操作会在sync操作完成之后才执行,哪怕这些操作是在执行sync之前被提交的。

4. 提高ZooKeeper应用的容错

分布式环境是很复杂的,网络的不可靠、单点故障等问题都是经常发生的。那么在构建一个分布式应用程序时,这些问题都是需要慎重考虑的。因此,如何构建一个可复原的分布式应用将成为一个值得讨论的话题。Java api中每个异常都对应一类故障模式,下边我们将会以Java api中的异常为例来讨论ZooKeeper应用程序中可能会出现的一些故障。

4.1. Java API中的一些常见异常

  • InterruptedException异常

若客户端的某操作被中断,则会抛出InterruptedException异常。抛出该异常时,不一定是出现故障,只能表明某个zookeeper操作被中断而已。

  • KeeperException异常

服务器发出错误信号或是服务器存在通信故障。该类现在共有21个子类, 分为3大类:

 (1)、状态异常

当一个客户端对zk的某操作失败时,就会出现状态异常。例如:更新数据时所指定的版本号不正确就会抛出异常BadVersionException、若在短暂的znode下创建子节点则会抛出异常NoChildrenForEphemeralsException。

(2)、可恢复的异常

那些在zk会话中可以恢复的异常叫可恢复的异常。当丢失zk连接时就会抛出异常ConnectionLossException,这时zk会自动尝试重新连接,以确保会话的完整性。Zk无法判断ConnectionLossException异常相关的操作是否成功执行,有可能出现只完成部分,那么是否重新执行刚才的操作就得知道该操作是否是等幂的。

等幂操作是指一次或多次执行都会产生相同结果的操作;非等幂操作是指一次或多次执行会产生不相同结果的操作。非等幂操作就不能盲目操作了。

写操作里有创建、删除、修改。在一个分布式环境中,删除zk里的znode或是修改znode的数据是等幂的,只有创建znode可能不是等幂的,创建顺序znode就是一个非等幂的操作。

那么怎么样才能避免创建顺序znode不会出现重复创建呢?下面我来展开讨论:

假设的场景:

客户端:客户端任务是在连接到zk服务端时会只创建一个顺序znode;

ConnectionLossException抛出ConnectionLossException异常重新连接后会话没有失效, 但是zk无法判断创建znode的操作是否成功。

我们知道顺序znode的节点名称格式是形如”znodeName<sequentialNumber>”,zk客户端和服务器端的会话有个全局唯一的sessionID,我们可以把sessionID加入znode的名称中,形如:” znodeName<sessionID><sequentialNumber>”,sequentialNumber是相对于父znode唯一的。这样我们在创建某个znode之前先判断一下父znode下有无名称形如” znodeName<sessionID>“这样字符开头的子znode,就能确保每个客户端连接只创建一个znode。

这种场景会在什么时候会遇到呢?在我们要实现一个分布式锁的时候,核心思想之一就在这里。那么问题来了,什么是分布式锁呢?后面会有独立的博文来讲解关于它的代码实现。

(3)、不可恢复的异常

不可恢复的异常发生时,所有的短暂znode都将会丢失,只有程序中显示的重建zk连接,并重建znode的状态。例如:会话过期会抛出异常SessionExpiredException,身份验证失败会抛出异常AuthFailedException。

(4)、异常捕捉处理

每个子类对应一种异常状态,且每个子类都对应一个关于错误类型的信息代码,可以通过code方法拿到。 处理该种异常有两种办法:

1、通过检测错误代码(可调用code方法老获取)来确定是哪种异常,再决定应该采取何种补救措施;

2、通过追捕等价的KeeperException异常,然后再每段捕捉代码中执行相应的操作。

4.2. 构建可靠的zookeeper应用

上面说到了zk服务器端可能出现一些网络故障或单点故障登,那么怎么编写一个可靠的zk客户端程序来应对可能不稳定的zk实例呢?这里我们向一个znode写数据为例,来实现它:

 * 显示配置
* @throws KeeperException 服务器发出错误信号或是服务器存在通信故障。该类现在共有21个子类,
* 分为3大类:<br/>
* 1、状态异常(如:BadVersionException、NoChildrenForEphemeralsException);
* 2、可恢复的异常(如:ConnectionLossException);
* 3、不可恢复的异常(如:SessionExpiredException、AuthFailedException)。
* 每个子类对应一种异常状态,且每个子类都对应一个关于错误类型的信息代码,可以通过code方法拿到。
* 处理该种异常有两种办法:<br/>
* 1、通过<b>检测错误代码</b>来决定应该采取何种补救措施;<br/>
* 2、通过<b>追捕等价的KeeperException异常</b>,然后再每段捕捉代码中执行相应的操作。
* @throws InterruptedException zookeeper操作被中断。<b>并不一定就是出现故障,只能表明相对应的操作被取消</b>。
*/
public static void write(String path, String value) throws KeeperException, InterruptedException {
int retries = 0; while (true) {
try {
Stat stat = zk.exists(path, false);
if(stat == null){
zk.create(path, value.getBytes(CHARSET), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}else {
zk.setData(path, value.getBytes(CHARSET), -1);
}
break;
} catch(KeeperException.SessionExpiredException e){
//TODO 此处会话过期,抛出异常,由上层调用来重新创建zookeeper对象
throw e;
}catch(KeeperException.AuthFailedException e){
//TODO 此处身份验证时,抛出异常,由上层来终止程序运行
throw e;
}catch (KeeperException e) {
//检查有没有超出尝试的次数
if(retries == MAXRETRIES){
throw e;
}
retries++;
TimeUnit.SECONDS.sleep(RETRY_PERIOD_SECONDS);
}
}
}

如果您是一名Java开发人员,那么我觉得上面的这些代码没什么好解释的了。下边看上层调用是怎么处理的:


while (true) {
try {
write(path, value);
break;
} catch (KeeperException.SessionExpiredException e) {
// TODO: 重新创建、开始一个新的会话
e.printStackTrace();
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
} catch (KeeperException e) {
// TODO 尝试了多次,还是出错,只有退出了
e.printStackTrace();
flag = 1;
break;
}catch(KeeperException.AuthFailedException e){
//TODO 此处身份验证时,终止程序运行
e.printStackTrace();
flag = 1;
break;
} catch (IOException e) {
// TODO 创建zookeeper对象失败,无法连接到zk集群
e.printStackTrace();
flag = 1;
break;
}
} System.exit(flag);

关于编写一个可恢复的zookeeper应用,这一块理解了,其它地方应该就是触类旁通了。

后边就是应用zookeeper开发,例如分布式配置系统、分布式锁的实现。

参考地址:http://zookeeper.apache.org/doc/r3.4.6/

参考书籍:《hadoop权威指南》

zookeeper原理浅析(二)的更多相关文章

  1. zookeeper原理浅析(一)

    参考:https://www.cnblogs.com/leocook/p/zk_0.html 代码:https://github.com/littlecarzz/zookeeper 1. 什么是Zoo ...

  2. 【Flutter学习】之动画实现原理浅析(二)

    1. 介绍 本文会从代码层面去介绍Flutter动画,因此不会涉及到Flutter动画的具体使用. 1.1 Animation库 Flutter的animation库只依赖两个库,Dart库以及phy ...

  3. 沉淀,再出发:docker的原理浅析

    沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...

  4. Zookeeper(三) Zookeeper原理与应用

    一.zookeeper原理解析 1.进群角色描述 2.Paxos 算法概述( ZAB 协议)    分布式一致性算法 3.Zookeeper 的选主(恢复模式) 以一个简单的例子来说明整个选举的过程. ...

  5. 消息队列——ActiveMQ使用及原理浅析

    文章目录 引言 正文 一.ActiveMQ是如何产生的? 产生背景 JMS规范 基本概念 JMS体系结构 二.如何使用? 基本功能 消息传递 P2P pub/sub 持久订阅 消息传递的可靠性 事务型 ...

  6. 关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析

    关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析 如下代码,当我们在使用 ReentrantLock 进行加锁和解锁时,底层到底是如何帮助我们进行控制的啦 ...

  7. Apache ZooKeeper原理剖析及分布式理论名企高频面试v3.7.0

    概述 **本人博客网站 **IT小神 www.itxiaoshen.com 定义 Apache ZooKeeper官网 https://zookeeper.apache.org/ 最新版本3.7.0 ...

  8. Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之ORACLE集群概念和原理(二)

    ORACLE集群概念和原理(二) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总.然后形成体 ...

  9. HTTP长连接和短连接原理浅析

    原文出自:HTTP长连接和短连接原理浅析

随机推荐

  1. vue同胞组件通讯解决方案(以下为一种另外可用vuex解决)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. 洛谷 P1163 银行贷款

    题目描述 当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款.这个问题要求计算出贷款者向银行支付的利率.假设利率按月累计. 输入输出格式 输入格式: 输入文件仅一行包含三个用空格 ...

  3. DROP USER - 删除一个数据库用户帐号

    SYNOPSIS DROP USER name DESCRIPTION 描述 DROP USER 从数据库中删除指定的用户. 它不删除数据库里此用户所有的表,视图或其他对象. 如果该用户拥有任何数据库 ...

  4. DROP TYPE - 删除一个用户定义数据类型

    SYNOPSIS DROP TYPE name [, ...] [ CASCADE | RESTRICT ] DESCRIPTION 描述 DROP TYPE 将从系统表里删除用户定义的类型. 只有类 ...

  5. zabbix4.2学习笔记系列

    写在前面:对zabbix的接触始于对监控的了解,网上比较多zabbix相关博客,比较多基于3系列甚至2系列,最新开发版zabbix4.2版本已经出来,本博客基于4.2版本学习,参考官网4.2版本和网上 ...

  6. HDU4300 Clairewd’s message(拓展kmp)

    Problem Description Clairewd is a member of FBI. After several years concealing in BUPT, she interce ...

  7. 2. CHARACTER_SETS

    2. CHARACTER_SETS CHARACTER_SETS表提供有关可用字符集的信息. 下表中的SHOW Name值对应于SHOW CHARACTER SET语句的列名. INFORMATION ...

  8. 条款9:绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruction)

    NOTE:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)

  9. @ApiParam @RequestParam @PathVariable 用法

    文章来源:https://www.cnblogs.com/hello-tl/p/9204279.html 1.@ApiParam ,是注解api的参数 ,也就是用于swagger提供开发者文档 ,文档 ...

  10. tornado框架基础02-输入和输出

    01 输出 write bytes类型 class IndexHandler(tornado.web.RequestHandler): def get(self): self.write(b'Torn ...