zookeeper简介以及C客户端用法

zk

前言

zookeeper用法有很多,但是针对C++的工具集和文档却很少,本文主要介绍zk的使用方法,特别是在C++上的一些用法。

简介

Zookeepe维护一个类似文件系统的数据结构:每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
有四种类型的znode:

  1. PERSISTENT-持久化目录节点
    • 客户端与zookeeper断开连接后,该节点依旧存在
  2. PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
    • 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  3. EPHEMERAL-临时目录节点
    • 客户端与zookeeper断开连接后,该节点被删除
  4. 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 tree
  • delete : deletes a node
  • exists : tests if a node exists at a location
  • get data : reads the data from a node
  • set data : writes data to a node
  • get children : retrieves a list of children of a node
  • sync : 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

bin/zkServer.sh start

Step5:检测是否成功启动,用zookeeper客户端连接下服务端

bin/zkCli.sh -timeout 5000 -r -server ip:port

zoo.cfg参数详解

名称说明
tickTimezk中的时间单元,zk中所有时间都是以这个时间为基础,进行整数倍配置的,如session的最小超时时间是2*tickTime, 每隔tickTime发送一个心跳
clientPort客户端连接zookeeper服务器的端口号,默认2181
dataLogDir事务日志输出目录,尽量给事务日志的输出配置单独的磁盘或挂载点,这将极大的提升zk性能,用于单独设置transaction log的目录,transaction log分离可以避免和普通log还有快照的竞争。
dataDir存储快照(snapshot)文件目录,默认事务日志也存储在此路径下,建议同时配置dataLogDir,会影响zk性能,zk会通过org.apache.zookeeper.server.ZKDatabase开加载
globalOutstandingLimit系统属性:zookeeper.globalOutstandingLimit 默认1000,如果有大量的client,会造成zk server对请求的处理速度小于client的提交请求的速度,会造成server端大量请求queue滞留而导致OOM,此参数可以控制server最大持有为处理的请求个数
preAllocSize系统属性:zookeeper.preAllocSize ,为了避免大量磁盘检索,zk对txn log文件进行空间的预分配,默认为64M,当剩余空间小于4k时,会再次“预分配”。你可以尝试减小此值,比如当快照较为频繁时,可以适当减小
traceFile系统属性:requestTraceFile 请求跟踪文件,如果设置了此参数,所有请求将会被记录在traceFile.year.month.day,类似与nginx的request log,不过此参数会带来性能问题
maxClientCnxns默认:60, 一个client与server最大的socket连接数,根据IP区分,设置为0,取消此限制。可以避免DOS攻击
clientPortAddressip或者hostName,指定监听clientPort的address,此参数可选,默认是clientPort会绑定到所有ip上,在物理server具有多个网络接口时,可以设置特定的IP
minSessionTimeout默认 2*tickTime,也是server允许的最小值,如果设置的值过小,将会采用默认值
maxSessionTimeout默认20*tickTime,允许的最大值, 可以一句话概括,客户端上报的期望timeout(zookeeper_init()函数的第三个参数)一定要在服务端设置的上下界之间,如果越过边界,则以边界为准。
autopurge.snapRetainCountzk server启动时会开启一个org.apache.zookeeper.server.DatadirCleanupManager的线程,用于清理"过期"的snapshot文件和其相应的txn log file,此参数用于设定需要被retain保留的文件个数(从QuorumPeerMain跟踪代码)
autopurge.purgeInterval和上述参数配合使用,清理任务的时间间隔,单位为hours, 可以查看org.apache.zookeeper.server.DatadirCleanupManager的105行
snapCount系统属性:zookeeper.snapCount 默认为:100000,在新增Log条数达到snapCount/2 +Random.nextInt(snapCount/2)时,将会对zkDatabase内存数据库进行snapshot,将内存中的DataTree反序列化到snapshot文件数据,同时log计数重置为0,以此循环, snapshot过程中,同时也伴随txn log的新文件创建,snapshot时使用随机数的原因:让每个server snapshot的时机具有随机且可控,避免所有的server同时snapshot可见:org.apache.zookeeper.server.SyncRequestProcessor.run()方法实现
electionAlg选举算法,默认为3,可以选择(0,1,2,3), 0表示使用原生的UDP(LeaderElection), 1表示使用费授权的UDP2表示使用授权的UDP(AuthFastLeaderElection) 3基于TCP的快速选举(FastLeaderElection)具体可见:org.apache.zookeeper.server.quorum.QuorumPeer 159行createElectionAlgorithm(electionType);org.apache.zookeeper.server.quorum.LeaderElection (已被废弃)org.apache.zookeeper.server.quorum.AuthFastLeaderElection(已被废弃)org.apache.zookeeper.server.quorum.FastLeaderElection
initLimitLeader与learner建立连接中 socket通讯read所阻塞的时间(initLimit * tickTime) 如果是Leaner数量较多或者leader的数量很大, 可以增加此值 ,代码参考:org.apache.zookeeper.server.quorum.LearnerHandler第297行run()方法
SyncLimitlearner与leader建立连接中,socket通讯read阻塞的时间.其中包括数据同步/数据提交等, 参考代码:org.apache.zookeeper.server.quorum.Learner222行connectToLeader()316行syncWithLeader()
peerTypezkserver 类型 observer 观察者, participant参与者 ,默认为参与者
leaderServes系统属性 zookeeper.leaderServes leader是否接受client请求,默认为yes即leader可以接受client的连接,在zk cluster 环境中,当节点数为>3时,建议关闭
cnxTimeout系统属性:zookeeper.cnxTimeout leader选举时socket连接打开的时长,只有在electionAlg=3有效
skipACL系统属性:zookeeper.skipACL 默认为no,是否跳过ACL检查
forceSync系统属性:zookeeper.forceSync 默认yes 在update执行之前,是否强制对操作立即持久写入txn log文件.关闭此选项,会造成服务器失效后,尚未持久化的数据丢失

常用命令

bin/zkCli.sh
## 后面进入命令行
ls /  # 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
create /zkPro myData  # 创建一个新的 znode
get /zkPro  # 查
set /zkPro myData123  # 改
delete /zkPro  # 删

C API

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

  1. 在cmake文件里加上编译好的lib文件路径,
  2. 如果你需要编译多线程版本客户端程序,
    • 请添加编译选项 -dthreaded,
    • 同时链接时应链接 zookeeper_mt 库;
  3. 如果你需要编译单线程客户端程序,
    • 请不要添加编译选项 -dthreaded,
    • 同时链接时应链接 zookeeper_st 库。

zookeeper引用计数

  • zookeeper_init建立的zhandle包含成员ref_count,如果引用计数不为0,则无法关闭zookeeper(zookeeper_close)
  • ref_count可以用api_prolog加1,常见的应用场合:
    • 发送队列里有东西的时候
    • 正在初始化thread的时候
    • do_iodo_completion都会让引用计数+1

zookeeper节点类型

  1. 是否持久

    • persistent :持久节点。需要主动删除
    • ephemeral : 瞬时节点。与客户端session结束,自动删除; 不能有子节点
  2. 是否有序

    • persistent_sequential : 持久有序节点。
    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典型使用场景一览 - 阿里
    1. ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。

zk c client 连接流程

  1. 客户端连接zookeeper服务时,zookeeper服务创建session,每个session会有一个64位数字作为其ID;
  2. 除了为session分配单独ID,zookeeper还会为每个session ID创建密码,用以会话的认证。
  3. 客户端与zookeeper服务会话建立之后,session ID和密码会发送到客户端。
  4. 当由于某种原因,比如当前连接节点的zookeeper服务因OOM被kill掉了,客户端从列表中找到下一个zookeeper节点尝试建立连接;
  5. session ID和密码会发送到新的zookeeper节点,认证通过后,客户端到zookeeper新节点的会话恢复成功,否则会话因认证失败无法建立

zk 状态转换

有一点很关键,zk的状态,有一些是由zkserver转换,有一些是由zkclient转换的。

C++客户端中的connecting,这是客户端的状态(也就是curator中的SUSPENDED),如果客户端准备连接到server但是还没连接成功,或者连接上之后发给zkserver的心跳没有回应(网络异常),导致client断开当前session连接,并且换一个server地址重连。client就会把自己设为connecting状态。

  1. 初始化链接的时候,如果连接上了,server会向client发connected事件
  2. 连接已经建立,但是在2/3的session timeout时间内server没有收到client的心跳,server会发送KeeperState.Disconnected并断开当前的session连接。
    • 同理,client如果在没有收到server的心跳回包,也会断开当前链接,并且用zoo_cycle_next_server(C++客户端)找下一个可用的zkserver尝试重连。
  3. session timeout超时后,server会向client发expired事件,当然这个事件只有等重连成功之后,client才收得到

状况状态转换

连接中的异常

  1. 如果,zkserver重启之后所有的的数据都丢失了,client仍然会无限的尝试重连zkserver,导致所有之前建立的session,以及在他之上的zk client不可用。
  2. 该情况很容易复现:(在测试集群中)停掉所有节点上zookeeper服务 → 删除所有节点上snapshot以及transaction日志 → 启动所有节点上zookeeper服务;经过数据的清理过程,重启之后的zookeeper服务会丢失所有会话信息,但之前已经建立session的客户端因保存有session ID以及session密码,会不断尝试向不同的zookeeper节点恢复会话;
  3. 除非重启客户端,令客户端重新建立会话,否则客户端会进入“锲而不舍”的会话恢复悲剧之中。
应对
  1. Zookeeper的zxid会由于状态的变更主键递增1,为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。这在选举的时候启动至关重要。
  2. 但是如果把snapshot和transitionlog删除掉之后重启zookeeper, zk server的zxid编号会从0开始递增。
  3. 但是客户端仍然使用自己申请到的zxid重连,服务端发现客户端的id比服务端大,所以拒绝服务。
  4. 网上的说法都是遇到这种情况需要重启zk客户端。如果不想重启,就需要改zk库的逻辑,在重连到达一定次数之后重新建立连接

记一次线上事故

  • 我司使用zk做选主+服务发现,在某天夜里,突然zk因为压力太大,被打爆了,从而引发了多个问题
    1. zk脑裂:
      1. 我们多个服务向zk同一个节点注册,注册的升级为master节点,其他的为follower
      2. 但是事故发生时,zk服务不可用,master节点没有感知到expired事件,因此认为自己仍是master
      3. 有一个follower服务感知到了master节点被删除的事件,把自己提升为master,从此,集群有了两个master,因此导致了整个集群的瘫痪。
    2. zk hang
      1. zk客户端和服务端网络不通畅的时候,服务端发出的expire事件并不能及时通知到客户端,客户端如果不能及时对connecting状态做出反应,仍然向server拉节点,如果用同步API,就会造成hang,如果用异步API,operation_timeout的通知也会过很久(C客户端上拉取节点超时的时间是三分之二的zktimeout时间)才返回。
    3. zk雪崩
      1. zk客户端的逻辑是:重连之后会对所有节点重新watch, 如果zk不可用导致所有的zk连接expire,那么会导致所有的节点去server watch节点,惊群效应会导致zk压力过大–>服务处理延迟过大–>导致client认为客户端连接失败–>zk客户端重连–>连接失败–>然后就会一直重连。无限循环
    4. 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

参考链接

  1. zookeeper系列(基础+实战)
  2. ZooKeeper如何模拟会话失效(Session Expired)
  3. Zookeeper C Client分析 推荐
  4. zookeeper FAQ 非常好
  5. ZooKeeper 笔记
  6. ZooKeeper Programmer’s Guide 国内的简介包括保序性之类的,都是翻自这个官方文档
  7. ZooKeeper Recipes and Solutions 官方文档,介绍了客户端使用方法,包括主选举,队列,锁等等
  8. Zookeeper的关键机制的实现原理 包括会话机制,仲裁机制
  9. ZooKeeper session管理 zk负载均衡,讲zk各项和session state相关的改动。
  10. zookeeper c 客户端源码分析以及使用注意点
  11. zookeeper C API 的安装和使用指南 编译指南,make install 的时候可以通过增加--prefix选项指定安装的目录(CMAKE有对应的语句CMAKE_INSTALL_PREFIX
  12. ServiceLatencyOverview 这篇文章是zookeeper的commiter写的,文章写了做项目的latency测试的方法,掌握其中的思想很重要
  13. zookeeper 集群搭建
  14. Zookeeper运维小结–CancelledKeyException
  15. Zookeeper和跨数据中心分布式协调
  16. 理解zookeeper选举机制
  17. Zookeeper集群节点数量为什么要是奇数个?
  18. zookeeper实现主从选举
  19. expired ephemeral node reappears after ZK leader change
  20. 深入浅析zookeeper的一致性模型及其实现讲解了为什么zookeeper的一致性和其他一致性协议有区别
  21. How ZooKeeper guarantees “Single System Image”?
  22. ZooKeeper: Wait-free coordination for Internet-scale systems yahoo的论文
  23. ZooKeeper FAQ 官方文档告诉你应该如何处理CONNECTION_LOSS,SESSION_EXPIRED等等,真的对zk有所了解的人都会问的问题。。。
  24. Tech Note 10JVM pause也有可能导致脑裂
  25. KIP-537: Increase default zookeeper session timeout
  26. SpringBoot集成Zookeeper另外还简单的讲了一些zookeeper的理论基础如ZAB
  27. Spring Cloud Zookeeperspring cloud如何集成zookeeper的官方教程。Spring Cloud Zookeeper uses Apache Curator behind the scenes.
  28. 可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值