什么是 Zookeeper

ZooKeeper 的由来

Zookeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。

所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。

Zookeeper:分布式协调服务,Zookeeper 本来的目的是用来协调各个服务访问共享资源的顺序性问题。

比如我们有几个服务要访问同一个共享资源情况下,这个时候就会存在资源竞争的问题,如下图所示。

image.png

在多线程环境下,可以通过线程锁的方式来控制资源的访问顺序,但是在多个服务的环境下,没有工具来协调服务访问共享资源的顺序,所以在这种情况下,诞生了 Zookeeper。

Zookeeper 数据结构

Zookeeper 的视图结构跟标准的 Unix 文件系统很像,都有一个根节点 / 。在根节点下面就是一个个的子节点,我们称为 ZNode。ZNode 是 Zookeeper 中最小数据单位,在 ZNode 下面又可以再挂 ZNode,这样一层层下去就形成了一个层次化命名空间 ZNode 树,我们称为 ZNode Tree。对于 ZNode 节点,我们可以增删改查操作。

Zookeeper 的数据模型图:

image.png

Zookeeper 节点

Zookeeper 节点类型

Zookeeper 节点类型可以分为三大类:持久性节点(Persistent)、临时性节点(Ephemeral)、顺序性节点(Sequential)。现实开发中在创建节点的时候通过组合可以生成以下四种节点类型:持久节点、持久顺序节点、临时节点、临时顺序节点。

  • 持久节点就是节点被创建后会一直存在服务器,直到删除操作主动清除,这种节点也是最常见的类型。
  • 持久顺序节点就是有顺序的持久节点,节点特性和持久节点是一样的,只是额外特性表现在顺序上。顺序特性实质是在创建节点的时候,会在节点名后面加上一个数字后缀,来表示其顺序。
  • 临时节点就是会被自动清理掉的节点,它的生命周期和客户端会话绑在一起,客户端会话结束,节点会被删除掉。与持久性节点不同的是,临时节点不能创建子节点。
  • 临时书序节点就是有顺序的临时节点,和持久顺序节点相同,在其创建的时候会在名字后面加上数字后缀。

ZNode 的数据结构

我们看看结构图

初步认识 Zookeeper - 图3

整个 ZNode 节点内容包括两部分:节点数据内容和节点状态信息。图中 app1 是数据内容,其他的属于状态信息。那么这些状态信息都有什么含义呢?

  • cZxid 就是 Create ZXID,表示节点被创建时的事务 ID。
  • mZxid 就是 Modified ZXID,表示节点最后一次被修改时的事务 ID。
  • ctime 就是 Create Time,表示节点创建时间。
  • mtime 就是 Modified Time,表示节点最后一次被修改的时间。
  • pZxid 表示该节点的子节点列表最后一次被修改时的事务 ID。只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新。
  • cversion 表示子节点的版本号。
  • dataVersion 表示内容版本号。
  • dataLength 表示数据长度。
  • numChildren 表示子节点数。
  • ephemeralOwner 表示创建该临时节点时的会话 sessionID,如果是持久性节点那么值为 0。

Zookeeper 应用

注册中心

最早的时候,我们是集中式的单体架构,部署在 Tomcat 中,如下图所示。

image.png

随着互联网的快速发展,体量的增长,后端的单体架构已经不能支撑这么大的流量了。早期的优化方式是通过垂直的方式增加硬件设备来达到应用性能提升的效果,但是投入产出比会越来越小。所以,我们开始采用水平伸缩的方式来提升架构性能。

比如我们的电商服务,按照业务拆分成用户服务、订单服务、商品服务,其中因为订单服务比较重要,采用了集群部署的方式。

拆分后的应用服务之间存在远程调用,早期采用 webservice 的方式来实现服务之间的远程调用。

image.png

采用如上分布式架构后,会存在如下三点问题:

  1. 协议地址的维护:比如用户服务要调用订单服务,因为订单服务存在多个节点,所以用户服务本地需要维护一份订单服务的地址信息。
  2. 负载均衡机制:因为订单服务存在多个节点,所以用户服务需要确定调用哪个节点。
  3. 服务动态上下线感知:比如订单服务某个节点下线了,用户服务需要感知到订单服务的节点变化,才能避免调用到无效的节点。

所以在这样的背景下,引入 Zookeeper 中间件,来维护用户服务、订单服务、商品服务的地址信息,感知服务的上下线状态。

这样用户服务只需要维护这个中间件的地址,通过该中间件来获取其他目标服务的地址信息,根据负载均衡算法来实现服务调用,同时通过该中间件还可以感知到目标服务的状态变化。

Zookeeper 安装部署

单节点部署

官网下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/

  1. 下载 Zookeeper 安装包
  2. 创建文件目录:mkdir -p /data/program
  3. 上传 Zookeeper 安装包到 /data/program 目录中
  4. 解压 tar -zxvf zookeeper-3.4.14.tar.gz
  5. conf 目录下增加配置文件 cp zoo_sample.cfg zoo.cfg
  6. 进入 bin 目录,启动 Zookeeper 服务 sh zkServer.sh start(注意启动 Zookeeper 需要 Java 环境)

集群部署

  1. 修改 zoo.cfg 配置文件,增加如下配置
  1. server.1=192.168.202.18:2888:3888
  2. server.2=192.168.202.49:2888:3888
  3. server.3=192.168.202.50:2888:3888

server.id=ip:port:port:第一个 port 是集群内数据通信端口,第二个 port 是 leader 选举端口。

  1. 在 zoo.cfg 配置文件中 dataDir=/tmp/zookeeper 对应的目录下增加 myid,内容是上面的 id
  1. vi /tmp/zookeeper/myid

注意:配置 Zookeeper 需要关闭 Linux 的防火墙

  1. 启动 Zookeeper 后,通过命令查看各个节点的状态信息

image.png

image.png

image.png

zoo.cfg 配置文件分析

基础配置内容

  1. # The number of milliseconds of each tick
  2. # ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的。
  3. tickTime=2000
  4. # The number of ticks that the initial
  5. # synchronization phase can take
  6. # Follower同步Leader的限制时间,initLimit * tickTime,随ZK集群数据量适当增加值
  7. initLimit=10
  8. # The number of ticks that can pass between
  9. # sending a request and getting an acknowledgement
  10. # L发送心跳包到收到F响应的限制时间,syncLimit * tickTime,不适合太大,容易使L误认为F在线
  11. syncLimit=5
  12. # the directory where the snapshot is stored.
  13. # do not use /tmp for storage, /tmp here is just
  14. # example sakes.
  15. # 存储快照文件snapshot的目录。默认情况下,事务日志也会存储在这里。建议同时配置参数dataLogDir, 事务日志的写性能直接影响zk性能。
  16. dataDir=/tmp/zookeeper
  17. # the port at which the clients will connect
  18. # 客户端连接server的端口,即对外服务端口
  19. clientPort=2181
  20. # the maximum number of client connections.
  21. # increase this if you need to handle more clients
  22. #maxClientCnxns=60
  23. #
  24. # Be sure to read the maintenance section of the
  25. # administrator guide before turning on autopurge.
  26. #
  27. # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
  28. #
  29. # The number of snapshots to retain in dataDir
  30. #autopurge.snapRetainCount=3
  31. # Purge task interval in hours
  32. # Set to "0" to disable auto purge feature
  33. #autopurge.purgeInterval=1
  34. # 集群配置
  35. server.1=192.168.202.18:2888:3888
  36. server.2=192.168.202.49:2888:3888
  37. server.3=192.168.202.50:2888:3888

详细配置描述

参数名 说明
tickTime ZK 中的一个时间单元。ZK 中所有时间都是以这个时间单元为基础,进行整数倍配置的。例如,session 的最小超时时间是 2*tickTime。
dataDir 存储快照文件 snapshot 的目录。默认情况下,事务日志也会存储在这里。建议同时配置参数 dataLogDir, 事务日志的写性能直接影响zk性能。
dataLogDir 事务日志输出目录。尽量给事务日志的输出配置单独的磁盘或是挂载点,这将极大的提升ZK性能。(No Java system property)
clientPort 客户端连接 server 的端口,即对外服务端口,一般设置为2181。
globalOutstandingLimit 最大请求堆积数。默认是1000。ZK 运行的时候, 尽管 Server 已经没有空闲来处理更多的客户端请求了,但是还是允许客户端将请求提交到服务器上来,以提高吞吐性能。当然,为了防止 Server 内存溢出,这个请求堆积数还是需要限制下的。(Java system property: zookeeper.globalOutstandingLimit)
preAllocSize 预先开辟磁盘空间,用于后续写入事务日志。默认是64M,每个事务日志大小就是64M。如果 ZK 的快照频率较大的话,建议适当减小这个参数。(Java system property: zookeeper.preAllocSize )
snapCount -每进行 snapCount 次事务日志输出后,触发一次快照(snapshot), 此时,ZK 会生成一个 snapshot. 文件,同时创建一个新的事务日志文件 log.。默认是100000。(真正的代码实现中,会进行一定的随机数处理,以避免所有服务器在同一时间进行快照而影响性能)(Java system property: zookeeper.snapCount )
traceFile 用于记录所有请求的 log,一般调试过程中可以使用,但是生产环境不建议使用,会严重影响性能。(Java system property: requestTraceFile )
maxClientCnxns 单个客户端与单台服务器之间的连接数的限制,是 IP 级别的,默认是60,如果设置为0,那么表明不作任何限制。请注意这个限制的使用范围,仅仅是单台客户端机器与单台 ZK 服务器之间的连接数限制,不是针对指定客户端 IP,也不是 ZK 集群的连接数限制,也不是单台 ZK 对所有客户端的连接数限制。(No Java system property)
clientPortAddress 对于多网卡的机器,可以为每个 IP 指定不同的监听端口。默认情况是所有 IP 都监听 clientPort 指定的端口。 New in 3.3.0
minSessionTimeoutmaxSessionTimeout Session 超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。默认的 Session 超时时间是在 2 tickTime ~ 20 tickTime 这个范围 New in 3.3.0
fsync.warningthresholdms 事务日志输出时,如果调用 fsync 方法超过指定的超时时间,那么会在日志中输出警告信息。默认是1000ms。(Java system property: fsync.warningthresholdms) New in 3.3.4
autopurge.purgeInterval 在上文中已经提到,3.4.0及之后版本,ZK 提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能。(No Java system property) New in 3.4.0
autopurge.snapRetainCount 这个参数和上面的参数搭配使用,这个参数指定了需要保留的文件数目。默认是保留3个。(No Java system property) New in 3.4.0
electionAlg 在之前的版本中, 这个参数配置是允许我们选择 leader 选举算法,但是由于在以后的版本中,只会留下一种“TCP-based version of fast leader election”算法,所以这个参数目前看来没有用了,这里也不详细展开说了。(No Java system property)
initLimit Follower 在启动过程中,会从 Leader 同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader 允许F在 initLimit 时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F 在启动的时候,从 Leader 上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了。(No Java system property)
syncLimit 在运行过程中,Leader 负责与 ZK 集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果 L 发出心跳包在 syncLimit 之后,还没有从 F 那里收到响应,那么就认为这个 F 已经不在线了。注意:不要把这个参数设置得过大,否则可能会掩盖一些问题。(No Java system property)
leaderServes 默认情况下,Leader 是会接受客户端连接,并提供正常的读写服务。但是,如果你想让 Leader 专注于集群中机器的协调,那么可以将这个参数设置为 no,这样一来,会大大提高写操作的性能。(Java system property: zookeeper.leaderServes)
server.x=[hostname]:nnnnn[:nnnnn] 这里的 x 是一个数字,与 myid 文件中的 id 是一致的。右边可以配置两个端口,第一个端口用于 F 和 L 之间的数据同步和其它通信,第二个端口用于Leader 选举过程中投票通信。 (No Java system property)
group.x=nnnnn[:nnnnn]weight.x=nnnnn 对机器分组和权重设置,可以参见这里(No Java system property)
cnxTimeout Leader 选举过程中,打开一次连接的超时时间,默认是5s。(Java system property: zookeeper.cnxTimeout)
zookeeper.DigestAuthenticationProvider
ZK 权限设置相关,具体参见 《使用 super 身份对有权限的节点进行操作》《ZooKeeper 权限控制》
skipACL 对所有客户端请求都不作ACL检查。如果之前节点上设置有权限限制,一旦服务器上打开这个开头,那么也将失效(Java system property: zookeeper.skipACL)
forceSync 这个参数确定了是否需要在事务日志提交的时候调用 FileChannel .force来保证数据完全同步到磁盘。(Java system property: zookeeper.forceSync )
jute.maxbuffer 每个节点最大数据量,是默认是1M。这个限制必须在 server 和 client 端都进行设置才会生效。(Java system property: jute.maxbuffer)

文章

阿里巴巴为什么不用 ZooKeeper 做服务发现?