前言
zookeeper用法有很多,但是针对C++的工具集和文档却很少,本文主要介绍zk的使用方法,特别是在C++上的一些用法。
简介
Zookeepe维护一个类似文件系统的数据结构:每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。 有四种类型的znode:
PERSISTENT-持久化目录节点
- 客户端与zookeeper断开连接后,该节点依旧存在
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
- 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
EPHEMERAL-
临时目录节点
- 客户端与zookeeper断开连接后,该节点被删除
EPHEMERAL_SEQUENTIAL-临时
顺序编号目录节点
- 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
zookeeper保证
根据zookeeper官方文档,zookeeper提供了如下保证:
Sequential Consistency - Updates from a client will be applied in the order that they were sent.Atomicity - Updates either succeed or fail. No partial results.Single System Image - A client will see the same view of the service regardless of the server that it connects to. i.e., a client will never see an older view of the system even if the client fails over to a different server with the same session. 如果client首先看到了新数据,再尝试重连到存有旧数据的follower,该follower会拒绝该连接(client的zxid高于follower)Reliability - Once an update has been applied, it will persist from that time forward until a client overwrites the update.Timeliness - The clients view of the system is guaranteed to be up-to-date within a certain time bound.
由此可见,zookeeper只提供顺序一致性和分区容错性
理解zookeeper的顺序一致性
ZooKeeper Programmer's Guide提到:
Sometimes developers mistakenly assume one other guarantee that ZooKeeper does not in fact make. This is: Simultaneously Conistent Cross-Client Views ZooKeeper does not guarantee that at every instance in time, two different clients will have identical views of ZooKeeper data. Due to factors like network delays, one client may perform an update before another client gets notified of the change. Consider the scenario of two clients, A and B. If client A sets the value of a znode /a from 0 to 1, then tells client B to read /a, client B may read the old value of 0, depending on which server it is connected to. If it is important that Client A and Client B read the same value, Client B should should call the sync() method from the ZooKeeper API method before it performs its read. So, ZooKeeper by itself doesn't guarantee that changes occur synchronously across all servers, but ZooKeeper primitives can be used to construct higher level functions that provide useful client synchronization.
就是说zookeeper并不保证每次从其一个server读到的值是最新的,它只保证这个server中的值是顺序更新的,如果想要读取最新的值,必须在get之前调用sync()
zookeeper 接口
zookeeper的接口十分简单,只支持以下操作:
create : creates a node at a location in the treedelete : deletes a nodeexists : tests if a node exists at a locationget data : reads the data from a nodeset data : writes data to a nodeget children : retrieves a list of children of a nodesync : waits for data to be propagated 对某个节点sync,保证拿到这个节点为最新的。(不确定是否是同步操作)
安装
入门安装指南
Step1:配置JAVA环境,检验环境:java -version, 一般就用1.8
Step2:下载并解压zookeeper
注:如果当前的java版本和zk要求的不同,可以简单的export临时变量
JAVA_HOME=/home/test/jdk1.8.0_161/
export JAVA_HOME
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export CLASSPATH
PATH=$JAVA_HOME/bin:$PATH
export PATH
cd /usr/local
wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz
tar -zxvf zookeeper-3.4.12.tar.gz
cd zookeeper-3.4.12
Step3:重命名配置文件zoo_sample.cfg
cp conf/zoo_sample.cfg conf/zoo.cfg
Step4:启动zookeeper
Step5:检测是否成功启动,用zookeeper客户端连接下服务端
bin/zkCli.sh -timeout 5000 -r -server ip:port
zoo.cfg参数详解
常用命令
bin/zkCli.sh
## 后面进入命令行
ls / # 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
create /zkPro myData # 创建一个新的 znode
get /zkPro # 查
set /zkPro myData123 # 改
delete /zkPro # 删
C API
- ZooKeeper Programmer's Guide(官方C++例程)
zookeeper C API
zookeeper只有C API,没有C++ API CAPI的代码已经五六年没更改过了,整体代码并不难,这里只记一些碰到过的坑 https://github.com/apache/zookeeper.git
int zookeeper_init(); // 这个函数会开启两个线程,一个下发IO:do_io, 一个做completion回调(watcher) do_completion 当连接expire之后,do_io里面会检查is_unrecoverable,然后直接退出do_io线程,这时候do_completion线程仍然需要在外面显式调用zookeeper_close去关闭。
int zookeeper_close(zhandle_t *zh); // 关闭之前打开的zhandle,如果引用计数不为0,则会跳过一些步骤,如果引用计数为0,则会销毁init中打开的锁,free空间。不用担心外部显式调用的close因为引用计数的关系没有完全关闭,zk内部的引用计数减一函数api_epilog会检查,如果引用计数降到0会再次调用zookeeper_close
如何在代码中使用zk C API
- 在cmake文件里加上编译好的lib文件路径,
- 如果你需要编译多线程版本客户端程序,
- 请添加编译选项 -dthreaded,
- 同时链接时应链接 zookeeper_mt 库;
- 如果你需要编译单线程客户端程序,
- 请不要添加编译选项 -dthreaded,
- 同时链接时应链接 zookeeper_st 库。
zookeeper引用计数
- 用
zookeeper_init建立的zhandle包含成员ref_count,如果引用计数不为0,则无法关闭zookeeper(zookeeper_close) - ref_count可以用api_prolog加1,常见的应用场合:
- 发送队列里有东西的时候
- 正在初始化thread的时候
do_io和do_completion都会让引用计数+1
zookeeper节点类型
- 是否持久
persistent :持久节点。需要主动删除ephemeral : 瞬时节点。与客户端session结束,自动删除; 不能有子节点
- 是否有序
persistent_sequential : 持久有序节点。
bash eg: create -s /zk/n6_ n6 Created /zk/n6_0000000006
-
ephemeral_sequential : 瞬时有序节点。
eg:create -e -s /zk/temp/t4 t4
Created /zk/temp/t40000000003
zookeeper集群
ZooKeeper典型使用场景
- ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。
zk c client 连接流程
- 客户端连接zookeeper服务时,zookeeper服务创建session,每个session会有一个64位数字作为其ID;
- 除了为session分配单独ID,zookeeper还会为每个session ID创建密码,用以会话的认证。
- 客户端与zookeeper服务会话建立之后,session ID和密码会发送到客户端。
- 当由于某种原因,比如当前连接节点的zookeeper服务因OOM被kill掉了,客户端从列表中找到下一个zookeeper节点尝试建立连接;
- session ID和密码会发送到新的zookeeper节点,认证通过后,客户端到zookeeper新节点的会话恢复成功,否则会话因认证失败无法建立
zk 状态转换
有一点很关键,zk的状态,有一些是由zkserver转换,有一些是由zkclient转换的。
C++客户端中的connecting,这是客户端的状态(也就是curator中的SUSPENDED),如果客户端准备连接到server但是还没连接成功,或者连接上之后发给zkserver的心跳没有回应(网络异常),导致client断开当前session连接,并且换一个server地址重连。client就会把自己设为connecting状态。
- 初始化链接的时候,如果连接上了,server会向client发connected事件
- 连接已经建立,但是在2/3的session timeout时间内server没有收到client的心跳,server会发送KeeperState.Disconnected并断开当前的session连接。
- 同理,client如果在没有收到server的心跳回包,也会断开当前链接,并且用
zoo_cycle_next_server(C++客户端)找下一个可用的zkserver尝试重连。 - session timeout超时后,server会向client发expired事件,当然这个事件只有等重连成功之后,client才收得到
zk状态转移图
连接中的异常
- 如果,zkserver重启之后所有的的数据都丢失了,client仍然会无限的尝试重连zkserver,导致所有之前建立的session,以及在他之上的zk client不可用。
- 该情况很容易复现:(在测试集群中)停掉所有节点上zookeeper服务 → 删除所有节点上snapshot以及transaction日志 → 启动所有节点上zookeeper服务;经过数据的清理过程,重启之后的zookeeper服务会丢失所有会话信息,但之前已经建立session的客户端因保存有session ID以及session密码,会不断尝试向不同的zookeeper节点恢复会话;
- 除非重启客户端,令客户端重新建立会话,否则客户端会进入“锲而不舍”的会话恢复悲剧之中。
应对
- Zookeeper的zxid会由于状态的变更主键递增1,为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。这在选举的时候启动至关重要。
- 但是如果把snapshot和transitionlog删除掉之后重启zookeeper, zk server的zxid编号会从0开始递增。
- 但是客户端仍然使用自己申请到的zxid重连,服务端发现客户端的id比服务端大,所以拒绝服务。
- 网上的说法都是遇到这种情况需要重启zk客户端。如果不想重启,就需要改zk库的逻辑,在重连到达一定次数之后重新建立连接
记一次线上事故
- 我司使用zk做选主+服务发现,在某天夜里,突然zk因为压力太大,被打爆了,从而引发了多个问题
- zk脑裂:
- 我们多个服务向zk同一个节点注册,注册的升级为master节点,其他的为follower
- 但是事故发生时,zk服务不可用,master节点没有感知到expired事件,因此认为自己仍是master
- 有一个follower服务感知到了master节点被删除的事件,把自己提升为master,从此,集群有了两个master,因此导致了整个集群的瘫痪。
zk hang
- zk客户端和服务端网络不通畅的时候,服务端发出的expire事件并不能及时通知到客户端,客户端如果不能及时对connecting状态做出反应,仍然向server拉节点,如果用同步API,就会造成hang,如果用异步API,operation_timeout的通知也会过很久(C客户端上拉取节点超时的时间是三分之二的zktimeout时间)才返回。
zk雪崩
- zk客户端的逻辑是:重连之后会对所有节点重新watch, 如果zk不可用导致所有的zk连接expire,那么会导致所有的节点去server watch节点,惊群效应会导致zk压力过大-->服务处理延迟过大-->导致client认为客户端连接失败-->zk客户端重连-->连接失败-->然后就会一直重连。无限循环
Zookeeper中Session Timeout的那些事
# 服务端日志
2020-04-07 17:34:28,388 [myid:0] - WARN [NIOWorkerThread-43:ZooKeeperServer@1013] - Connection request from old client /192.168.152.105:55508; will be dropped if server is in r-o mode
2020-04-07 17:34:28,388 [myid:0] - INFO [NIOWorkerThread-43:ZooKeeperServer@1032] - Refusing session request for client /192.168.152.105:55508 as it has seen zxid 0x300000001 our last zxid is 0x0 client must try another server
2020-04-07 17:34:33,397 [myid:0] - WARN [NIOWorkerThread-44:ZooKeeperServer@1013] - Connection request from old client /192.168.152.105:55514; will be dropped if server is in r-o mode
2020-04-07 17:34:33,397 [myid:0] - INFO [NIOWorkerThread-44:ZooKeeperServer@1032] - Refusing session request for client /192.168.152.105:55514 as it has seen zxid 0x300000001 our last zxid is 0x0 client must try another server
# 客户端日志
2020-04-07 17:37:18,668:13583(0x7f881b3e8700):ZOO_INFO@check_events@1728: initiated connection to server [192.168.152.105:2182]
2020-04-07 17:37:18,669:13583(0x7f881b3e8700):ZOO_ERROR@handle_socket_error_msg@1746: Socket [192.168.152.105:2182] zk retcode=-4, errno=112(Host is down): failed while receiving a server response
2020-04-07 17:37:18,670:13583(0x7f881b3e8700):ZOO_INFO@check_events@1728: initiated connection to server [192.168.152.105:2183]
2020-04-07 17:37:18,671:13583(0x7f881b3e8700):ZOO_ERROR@handle_socket_error_msg@1746: Socket [192.168.152.105:2183] zk retcode=-4, errno=112(Host is down): failed while receiving a server response
参考链接
- zookeeper系列(基础+实战)
- ZooKeeper如何模拟会话失效(Session Expired)
- Zookeeper C Client分析 推荐
- zookeeper FAQ 非常好
- ZooKeeper 笔记
- ZooKeeper Programmer's Guide 国内的简介包括保序性之类的,都是翻自这个官方文档
- ZooKeeper Recipes and Solutions 官方文档,介绍了客户端使用方法,包括主选举,队列,锁等等
- Zookeeper的关键机制的实现原理 包括会话机制,仲裁机制
- ZooKeeper session管理 zk负载均衡,讲zk各项和session state相关的改动。
- zookeeper c 客户端源码分析以及使用注意点
- zookeeper C API 的安装和使用指南 编译指南,make install 的时候可以通过增加
--prefix选项指定安装的目录(CMAKE有对应的语句CMAKE_INSTALL_PREFIX) - ServiceLatencyOverview 这篇文章是zookeeper的commiter写的,文章写了做项目的latency测试的方法,掌握其中的思想很重要
- zookeeper 集群搭建
- Zookeeper运维小结--CancelledKeyException
- Zookeeper和跨数据中心分布式协调
- 理解zookeeper选举机制
- Zookeeper集群节点数量为什么要是奇数个?
- zookeeper实现主从选举
- expired ephemeral node reappears after ZK leader change
- 深入浅析zookeeper的一致性模型及其实现讲解了为什么zookeeper的一致性和其他一致性协议有区别
- How ZooKeeper guarantees “Single System Image”?
- ZooKeeper: Wait-free coordination for Internet-scale systems yahoo的论文
- ZooKeeper FAQ 官方文档告诉你应该如何处理CONNECTION_LOSS,SESSION_EXPIRED等等,真的对zk有所了解的人都会问的问题。。。
- Tech Note 10JVM pause也有可能导致脑裂
- KIP-537: Increase default zookeeper session timeout
- SpringBoot集成Zookeeper另外还简单的讲了一些zookeeper的理论基础如ZAB
- Spring Cloud Zookeeperspring cloud如何集成zookeeper的官方教程。Spring Cloud Zookeeper uses Apache Curator behind the scenes.
- 可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章