点赞再看,养成习惯,微信搜一搜【一角钱小助手】关注更多原创技术文章。 本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章。
Zookeeper 系列文章最近会继续输出,上一篇我们主要讲了Zookeeper的主要概念和特性,介绍单机部署和客户端主要命令操作以及对节点说明。接下来会对Zookeeper 客户端 API的使用、集群部署与特性、典型使用场景实践以及ZAB一致性协议核心源码剖析分几块来继续讲一讲。
Zookeeper 提供了 Java 和 C 两种语言的客户端。本文学习的是 Java 客户端。引入最新的maven依赖:
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.2</version> </dependency>知识点:
初始连接创建、查看节点监听节点设置节点权限第三方客户端ZkClient常规的客户端类是 org.apache.zookeeper.ZooKeeper,实例化该类之后将会自动与集群建立连接。构造参数说明如下:
通过org.apache.zookeeper.ZooKeeper#create()即可创建节点,其参数说明如下:
通过org.apache.zookeeper.ZooKeeper#getData()即可创建节点,其参数说明如下:
通过org.apache.zookeeper.ZooKeeper#getChildren()即可获取子节点,其参数说明如下:
在getData() 与getChildren()两个方法中可分别设置监听数据变化和子节点变化。通过设置watch为true,当前事件触发时会调用zookeeper()构建函数中Watcher.process()方法。也可以添加watcher参数来实现自定义监听。一般采用后者。
注:所有的监听都是一次性的,如果要持续监听需要触发后在添加一次监听。
ACL包括结构为 scheme:``id:``permission(有关ACL的介绍参照上一篇《Zookeeper特性与节点说明》)
客户端中由org.apache.zookeeper.data.ACL 类表示,类结构如下:
ACLIdscheme // 对应权限模式schemeid // 对应模式中的id值perms // 对应权限位permission关于权限位的表示方式:
每个权限位都是一个唯一数字,将其合时通过或运行生成一个全新的数字即可
@InterfaceAudience.Public public interface Perms { int READ = 1 << 0; int WRITE = 1 << 1; int CREATE = 1 << 2; int DELETE = 1 << 3; int ADMIN = 1 << 4; int ALL = READ | WRITE | CREATE | DELETE | ADMIN; }zkClient 是在Zookeeper客户端基础之上封装的,使用上更加友好。主要变化如下:
可以设置持久监听,或删除某个监听可以插入JAVA对象,自动进行序列化和反序列化简化了基本的增删改查操作。 <!-- https://mvnrepository.com/artifact/com.101tec/zkclient --> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.11</version> </dependency>知识点:
集群部署集群角色说明选举机制数据同步机制四字运维命令Zookeeper集群的目的是为了保证系统的性能承载更多的客户端连接设专门提供的机制。通过集群可以实现以下功能:
读写分离:提高承载,为更多的客户端提供连接,并保障性能。主从自动切换:提高服务容错性,部分节点故障不会影响整个服务集群。半数以上运行机制说明:
集群至少需要三台服务器,并且强烈建议使用奇数个服务器。因为zookeeper 通过判断大多数节点的存活来判断整个服务是否可用。比如3个节点,挂掉了2个表示整个集群挂掉,而用偶数4个,挂掉了2个也表示其并不是大部分存活,因此也会挂掉。
配置文件示例:
tickTime=2000 dataDir=/var/lib/zookeeper/ clientPort=2181 initLimit=5 syncLimit=2 #以下为集群配置,必须配置在所有节点的zoo.cfg文件中 server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888conf/zoo1.cfg
tickTime=2000 initLimit=10 syncLimit=5 dataDir=data/1 clientPort=2181 #集群配置 server.1=127.0.0.1:2887:3887 server.2=127.0.0.1:2888:3888 server.3=127.0.0.1:2889:3889conf/zoo2.cfg
tickTime=2000 initLimit=10 syncLimit=5 dataDir=data/2 clientPort=2182 #集群配置 server.1=127.0.0.1:2887:3887 server.2=127.0.0.1:2888:3888 server.3=127.0.0.1:2889:3889conf/zoo3.cfg
tickTime=2000 initLimit=10 syncLimit=5 dataDir=data/3 clientPort=2183 #集群配置 server.1=127.0.0.1:2887:3887 server.2=127.0.0.1:2888:3888 server.3=127.0.0.1:2889:38894.分别启动
./bin/zkServer.sh start conf/zoo1.cfg ./bin/zkServer.sh start conf/zoo2.cfg ./bin/zkServer.sh start conf/zoo3.cfg5.分别查看状态
./bin/zkServer.sh status conf/zoo1.cfg Mode: follower ./bin/zkServer.sh status conf/zoo2.cfg Mode: leader ./bin/zkServer.sh status conf/zoo3.cfg Mode: followerzkCli.sh 后加参数-server 表示连接指定IP与端口。
./bin/zkCli.sh -server 127.0.0.1:2181 ./bin/zkCli.sh -server 127.0.0.1:2182 ./bin/zkCli.sh -server 127.0.0.1:2183 任意节点中创建数据,查看其它节点已经同步成功。注意: -server参数后同时连接多个服务节点,并用逗号隔开 127.0.0.1:2181,127.0.0.1:2182
zookeeper 集群中总共有三种角色,分别是leader(主节点)follower(子节点) observer(次级子节点)
observer配置: 只要在集群配置中加上observer后缀即可,示例如下:
server.3=127.0.0.1:2889:3889:observer通过 ./bin/zkServer.sh status <zoo配置文件> 命令可以查看到节点状态
./bin/zkServer.sh status conf/zoo1.cfg Mode: follower ./bin/zkServer.sh status conf/zoo2.cfg Mode: leader ./bin/zkServer.sh status conf/zoo3.cfg Mode: follower可以发现中间的2182 是leader状态.其选举机制如下图:
Zookeeper默认采用了FastLeaderElection算法,且投票数大于半数则胜出的机制;
当集群中的服务器出现以下两种情况时会进行Leader的选举
服务节点初始化启动(全新集群选举)半数以上的节点无法和Leader建立连接当节点初始起动时会在集群中寻找Leader节点,如果找到则与Leader建立连接,其自身状态变化follower或observer。如果没有找到Leader,当前节点状态将变化LOOKING,进入选举流程。
在集群运行期间如果有follower或observer节点宕机只要不超过半数并不会影响整个集群服务的正常运行。但如果leader宕机,将暂停对外服务,所有follower将进入LOOKING 状态,进入选举流程。
全新集群选举:
全新集群选举是新搭建起来的,没有数据ID和逻辑时钟数据影响集群的选举;
假设当前集群有5台服务器,它们的编号为 1 – 5,按编号依次启动 Zookeeper 服务:
服务器1启动,首先会给自己投1票,由于其他机器还没有启动,所以它还无法接收到投票信息的反馈;因此服务器1一直处于 LOOKING 状态;服务器2启动,首先给自己投1票;发起投票对比,这是它会与服务器1交换结果,由于服务器2的编号大,所以服务器2胜出,此时服务器1将票投给服务器2,服务器2的票数没有大于集群半数,所以服务器1、2两个的状态都是 LOOKING;服务器3启动,首先给自己投1票;与之前的服务器1、2交换结果,由于服务器3的编号最大所以服务器3胜出,那么服务器1、2都会将票投给服务器3,此时服务器3的票数为3,正好大于半数(5/2);所以服务器3成为 leader,服务1、2成为 follower服务器4启动,首先给自己投1票;与之前的服务器1、2、3交换结果,发现已经产生了 leader,则服务器4直接为 follower;服务器5启动和服务器4一样,也成为 follower;非全新集群选举
对于正常运行的 Zookeeper 集群,一旦中途有服务器宕机(leader),则需要重新选举时,选举的时候就要引入服务器ID、数据ID(zxid)和逻辑时钟(投票次数,每一轮投票晚上,投票次数都会增加)。
首先统计逻辑时钟是否相同,逻辑时钟小,则说明途中存在宕机问题。因此该数据不完整,那么选举结果被忽略,重新投票选举;统一逻辑时钟之后,对比数据ID值,数据ID反映了数据的新旧程度,因此数据ID大的节点胜出;如果逻辑时钟和数据ID值都相同,那么就去比较数据ID,数据ID大的胜出;简单的讲,非全新集群选举时是优中选优,保证 Leader 是 Zookeeper 集群中数据最完整、最可靠的一台服务器。
在集群运行期间如果有follower或observer节点宕机,只要不超过半数并不会影响整个集群服务的正常运行。但如果leader宕机,将暂停对外服务,所有follower将进入LOOKING 状态,进入选举流程。
Zookeeper 的数据同步是为了保证各节点中数据的一致至性,同步时涉及两个流程,一个是正常的客户端数据提交,另一个是集群某个节点宕机在恢复后的数据同步。
写入请求的大致流程是,收leader接收客户端写请求,并同步给各个子节点。如下图:
但实际情况要复杂的多,比如client 它并不知道哪个节点是leader 有可能写的请求会发给follower ,由follower在转发给leader进行同步处理
客户端写入流程说明:
client向zk中的server发送写请求,如果该server不是leader,则会将该写请求转发给leader server,leader将请求事务以proposal形式分发给follower;当follower收到收到leader的proposal时,根据接收的先后顺序处理proposal;当Leader收到follower针对某个proposal过半的ack后,则发起事务提交,重新发起一个commit的proposalFollower收到commit的proposal后,记录事务提交,并把数据更新到内存数据库;当写成功后,反馈给client。在集群运行过程当中如果有一个follower节点宕机,由于宕机节点没过半,集群仍然能正常服务。当leader 收到新的客户端请求,此时无法同步给宕机的节点。造成数据不一致。为了解决这个问题,当节点启动时,第一件事情就是找当前的Leader,比对数据是否一致。不一致则开始同步,同步完成之后在进行对外提供服务。
如何比对Leader的数据版本呢?这里通过ZXID事物ID来确认。比Leader就需要同步。
ZXID是一个长度64位的数字,其中低32位是按照数字递增,任何数据的变更都会导致,低32位的数字简单加1。高32位是leader周期编号,每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的周期编号,进行加1,再将低32位的全部设置为0。这样就保证了每次新选举的leader后,保证了ZXID的唯一性而且是保证递增的。
思考题: 如果leader 节点宕机,在恢复后它还能被选为leader吗?
ZooKeeper响应少量命令。每个命令由四个字母组成。可通过telnet或nc向ZooKeeper发出命令。
这些命令默认是关闭的,需要配置4lw.commands.whitelist来打开,可打开部分或全部示例如下:
#打开指定命令 4lw.commands.whitelist=stat, ruok, conf, isro #打开全部 4lw.commands.whitelist=*注: mac os 10.13 后就没有内置telnet函数,要新安装:
brew install telnetZookeeper 系列文章,敬请关注,下篇讲讲典型使用场景实践以及ZAB一致性协议!
部分图片来源于网络,版权归原作者,侵删。