hello云胜

技术与生活

0%

ZooKeeper

设计目的

zk的设计是为了解决分布式服务领域的问题。

  • 最终一致性:client不论连接到哪个Server,展示给它的都是同一个视图。
  • 可靠性:具有简单、健壮、良好的性能、如果消息被到一台服务器接收,那么消息将被所有服务器接收。
  • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息
  • 原子性:更新只能成功或者失败,没有中间状态。
  • 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息a在消息b前被同一个发送者发布,a必将排在b前面。

应用场景

  • 配置管理
  • DNS服务
  • 组成员管理
  • 各种分布式锁

不适合的场景:

  • 大数据量存储

ZooKeeper架构

img

  • 每个server的数据都是一样的,Client的读请求可以请求任意一个Server。
  • ZooKeeper启动时,将从实例中选举一个leader(Paxos协议)。
  • Leader负责处理数据更新等操作(ZAB协议)。(ZooKeeper Atomic Broadcast protocol)
  • 一个更新操作成功,当且仅当大多数Server在内存中成功修改 。

在zookeeper的集群中,各个节点共有下面3种角色和4种状态:
角色:leader、follower、observer
状态:

  • LOOKING:当前Server不知道leader是谁,正在搜寻。
  • LEADING:当前Server即为选举出来的leader。
  • FOLLOWING:leader已经选举出来,当前Server与之同步。
  • OBSERVING:observer的行为在大多数情况下与follower完全一致,但是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果。

session

session是zk中非常重要的一个概念。zk客户端和zk集群的某个节点直接建立一个session。客户端可以主动关闭session。如果在一定的timeout时间内,客户端没有想zk集群发送消息,zk集群可以主动断开连接。

zk客户端发现可一个zk集群节点连接失败后,会自动同其他节点建立连接。

数据一致性

zk集群中,只有leader节点可以处理写请求。follower节点接收到写请求,会转发给leader处理。

先到达leader的写请求先被处理

zk处理写请求时序图。

节点2是leader

zk处理写请求时序图。

Observer

zk集群中,除了leader和follew之外,还有一种observer角色。

Observer不参加ZooKeeper的事务提交和选举。只是被动的接收leader的通知。所以通过observer节点来提高整个集群的性能。

9140032-9eadb32721740010

数据存储

zk

zxid:每一个对zk datatree的修改都会作为一个事务执行。每个事务都有一个id,就是zxid。zxid是递增的。

zxid是一个8Byte的整数,即java的long型。8个字节分两部分。高四个字节保存的是epoch。低4个字节保存的是递增计数。

epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。

epoch文件只有在集群模式下才会产生。accepedEpoch和currentEpoch

epoch标识了当前Leader周期,集群机器相互通信时,会带上这个epoch以确保彼此在同一个Leader周期中

事务日志

zk将所有的变更存到日志文件里。zk在DataDir下创建version-2目录,下面会存放log文件和snapshot文件。

zk提供的一个查看log的工具:zkTxnLogToolkit.sh

数据快照

数据快照是Zookeeper数据存储中非常核心的运行机制,数据快照用来记录Zookeeper服务器上某一时刻的全量内存数据内容,并将其写入指定的磁盘文件中。

zk生成快照文件的时机:

  1. 重启

  2. 使用snapCount参数来配置每次数据快照之间的事务操作次数,即ZooKeeper会在snapCount次事务日志记录后进行一个数据快照。

    但实际上,数据快照对于ZooKeeper所在机器的整体性能的影响,需要尽量避免ZooKeeper集群中的所有机器在同一时刻进行数据快照。因此ZooKeeper在具体的实现中,并不是严格的按照这个策略执行的,而是采取“过半随机”策略。

一个单独的异步线程来进行数据快照。

zk查看你快照文件,可以通过一个zk的类:org.apache.zookeeper.server.SnapshotFormatter

数据模型

zk的数据模型是树结构的层次模型。层次模型和KV模型是两种主流的数据模型。层次模型常见于文件系统。Zk选择使用文件系统模型。

  • 文件系统的树形结构,方便表达数据之间的层次关系
  • 文件系统的树形结构可以位不通应用分配独立的命名空间

zk的树形结构成为data tree。data tree的每个节点称为znode。和文件系统不一样,每个znode都是可以存储数据的。每个znode都有一个版本version,version从0开始。znode中的数据可以有多个版本,比如某一个znode下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。

img

zk是一个内存数据库。在内存里保存了整棵树的内容,Zookeeper会定时将这个数据存储到磁盘上。

zk是是使用CurrentHashMap保存这颗树,map的key就是路径

ZNode的类型

znode可以分为持久性(persistent)的和临时性(ephemeral)的。

  • 持久性的。这种znode在创建之后,即使zk集群宕机或者client宕机都不会丢失
  • 临时性的。这种znode在client宕机或者client在指定的timeout时间内没有想zk发送消息,这种node就会消失。

znode还可以设置位顺序性和非顺序性的。顺序性的znode会关联一个唯一的单调递增整数。这个整数会作为znode名字的后缀。

以上两两组合就可以出来4中znode

Zookeeper原生API

watch机制

watch是zk中非常好用的机制。客户端在读取一个数据时,可以同时给这个数据设置一个watch。当这个数据有变化时,客户端就会收到一个事件通知。这样就避免了客户端不断轮询来查询最新的数据。

zk的watch采用了一种推拉结合的模式。一旦服务端感知数据变了,那么只会发送一个事件类型和节点信息给关注的客户端,而不会包括具体的变更内容,所以事件本身是轻量级的,这就是所谓的”推”部分,然后收到变更通知的客户端需要自己去拉变更的数据,这就是”拉”部分。

注意,watch是一次性的。

条件更新

对数据的更新操作,可以设置version,就行有条件更新。即只有客户端请求的version和实际数据的version相同时,才进行更新操作。否则不能操作。

如果客户端的请求中将version设置为-1,表示无条件更新。

multi操作

zk提供了一种multi api。可以把多个对znode的操作作为一个事务进行提交,要么全部成功,要么全部失败。

实现分布式队列

使用持久有序节点

实现分布式锁

使用临时有序节点

注意解决羊群效应:watch前一个锁请求,而不是watch锁本身

这样是一个公平锁

性能比较低,代码里取出所有节点,排序,查看前面是否有锁请求,有则watch,否则获取锁。这整个过程是加锁进行的,所以效率比较慢。

实现分布式选举

使用临时顺序znode

设计上和分布式锁很像。

Apache Curator

简化zookeeper代码的开发。

有很多写好的分布式服务可以使用

服务发现

curator有一个扩展curator-x-discovery,基于zk实现了服务发现。

基本设计:一个服务注册的根目录,服务目录,服务实例(临时节点)

container 节点

container节点是zk的一种特殊节点。引入他的目的是为了下挂子节点。当子节点都被删除时,这个container节点会被zk自动删除。

服务注册的根目录和服务目录都是container类型节点。

Zookeeper的运维

最重要的三个配置项

  1. clientPort
  2. dataDir。zk保存的是数据的一个个快照文件
  3. dataLogDir。保存事务日志文件的目录。zk在提交一个事务之前,必须保证事务日志的落盘。

硬件要求,zk应分配一个独占的服务器

  1. 内存,zk需要在内存中保存data tree。一般data tree也不会特别大。8G以上。
  2. CPU。zk不是计算密集型,2核以上
  3. 存储。存储设备的写延迟严重影响事务提交的效率。所以建议给dataLogDir分配一个独占的SSD盘

ZooKeeper常用命令

Zookeeper服务端命令

启动ZK服务: sh bin/zkServer.sh start
查看ZK服务状态: sh bin/zkServer.sh status
停止ZK服务: sh bin/zkServer.sh stop
重启ZK服务: sh bin/zkServer.sh restart

Zookeeper客户端命令

客户端登录Zookeeper: sh bin/zkCli.sh -server 127.0.0.1:2181

数据清理

手动清理

可以使用zk提供的bin/zkCleanup.sh脚本进行快照文件的清理

1
bin/zkCleanup.sh  -n 5

表示保留最近的5个快照

自动清理

在conf文件中可以配置

1
2
autopurge.snapRetainCount=10
autopurge.purgeInterval=1

autopurge.purgeInterval: 这个参数指定了清理频率,单位是小时,需要填写一个1或更大的整数,默认是0,表示不开启自动清理功能。

autopurge.snapRetainCount: 这个参数和上面的参数搭配使用,这个参数指定了需要保留的快照文件数目,默认是保留3个。

zk的监控

4字监控命令。

通过telnet或者ncat向zk发出命令。

1
echo ruok | ncat *.*.*.* 2181

jmx

zk很好的支持了jmx,大量的监控和管理工作可以通过jmx来做。可以把zk的JMX数据集成到prometheus,使用prometheus来做zk的监控管理。

1
jconsole 连接jmx

默认jmx只能本地连接。要配置远程可访问。需要先开放JMX端口

1
export JMXPORT=8081

然后再启动zk

跨数据中心部署

利用Observer节点

img

比如业务需要部署北京和香港两地都使用的 ZooKeeper服务。要求北京和香港的客户端请求的延迟都低。因此,需要再北京和香港都部署zk节点。
假设leader节点在北京。如果香港的节点也是follower,那么每个来自香港的写请求要需要在北京的leader和每个香港的follower节点之间进行propose、ack、和commit跨广域网的消息确认。
解决方案就是把香港的节点都设成observer。这些propose、ack和commit消息都变成同步一个leader的inform消息。

指定节点为observer

只需要在conf文件中,节点后面加上

1
server.3=x.x.x.x:2222:2223:observer

集群节点调整

手动调整

可以采取更改配置文件的方式调整。

  1. 停掉集群
  2. 修改conf文件的server.n
  3. 启动节点

缺点:

1. 服务中断
2. 可能导致已经提交的数据被覆盖

动态配置

3.5.0的新特性

在配置文件中开启动态配置

配置digest

使用命令进行节点的动态修改

Chubby vs Zookeeper

Chubby是一个分布式锁系统,非开源,广泛应用在Google的基础架构中,比如GFS和Bigtable中都是用chubby做协同服务

zookeeper借鉴了很多chubby的设计思想。所以他们之间有很多相似之处。