ZooKeeper

zk是什么?zookeeper当然是动物管理员啦 doge 先看官方的介绍: A Distributed Coordination Service for Distributed Applications 分布式应用的分布式协作服务?翻译起来怪怪的。 先来讲讲它的主要特点: high available 与 high performance。

high available是通过raft的共识算法,从而达到多副本高可用的特点。

high performance这个是这里要介绍的主要的点。细分为几个点:

  1. 为什么raft没法实现high performance?
  2. zk是如何完成high performance?

raft 为什么没有high performance ?

众所周知,raft都是client的把请求发送给 leader, leader再与majority的follower交流之后完成操作。但是现实中往往读请求比写请求的数量来的多,如果所有请求都是过leader,那么是不是就会性能较差呢? 那么我们是否可以考虑把读请求下发到follower,降低了leader的负载,从而改善性能呢?当然是不可以的。因为follower不一定能及时的跟上leader的更新。比如当你访问了一个非majority的follower,那么你读到的信息就是滞后的内容。不过呢? 如果你可以接受滞后性,那就当我啥也没说。

ZooKeeper 如何实现high performance呢?

ZooKeeper 是就是加了一些黑科技,使得客户端的读请求可以下发到follower从而提高了性能。这里就会介绍一下它的黑科技。 zookeeper 提供了两个重要的Guarentees:

  1. Linearizable writes: all requests that update the state of ZooKeeper are serializable and respect precedence. 所有的写请求都是Linearizable的
  2. FIFO client order: all requests from a given client are executed in the order that they were sent by the client. 所有的来自同一个客户端的请求都是按照发送出来的顺序执行的。

这两个保证,与读请求下发到follower又有什么关系呢? 首先要介绍一个概念,watch: ZooKeeper supports the concept of watches. Clients can set a watch on a znode. A watch will be triggered and removed when the znode changes. When a watch is triggered, the client receives a packet saying that the znode has changed. If the connection between the client and one of the ZooKeeper servers is broken, the client will receive a local notification.

当客户端可以对某个znode设置一个watch,当这个znode发生改变的时候,watch可以观察到,使得客户端会收到一个通知。这里znode可以理解为类似于linux里的一个目录或者文件。

C1: read(“/path/to/znode”, watch) C2: write(“/path/to/znode”, watch)

C1通过 follower读一个数据,这时候如果C2正好在写这个数据,那么C1就会收到通知,不读取那个旧的数据,直接读取C2操作完的数据。

举一个论文里的例子讲解这两个保证是如何被用上的: 当集群里选出一个新的leader的时候,新的leader会修改配置文件,比如修改一下谁是leader谁是follower这种。当然我们希望这个修改是原子的,论文是给出了requirement:

  • leader开始修改配置的时候,其它server不会继续使用旧的配置。
  • 当leader配置修改到一半,不幸挂了,其它server也不会使用改了一半的配置。

感觉这完全就是数据库里的事务原子性的要求

新的leader会指定一个”ready”的znode,当它开始操作的时候,删”ready”, 修改配置,创建新的 “ready”,是不是有点像lock,unlock的味道了。其实互斥锁也可以解决第一个需求。但是没法解决第二个需求。

由于Lineariable Write的存在,当进程看见”ready”的时候,就表示它也能看到”ready”之前的修改配置了。如果leader不幸挂了,那么进程就看不到 “ready”,那么自然就没法使用修改一半的数据。

这时候就会有另一个问题:如果进程看见的是旧的”ready”,那么他会不会读到旧的config数据呢?

这里就利用到了watch机制, client在读的时候会有一个watch,如果znode被修改,client会收到通知。并使用修改后的最新的数据。就保证了不会读取到旧的数据。

还有最后一个问题:如果client A与B share某个configuration,如果A修改了这个configuration,但是B的replica同步比A慢,那么要怎么保证B能够读到最新的configuration呢? 这就要利用到 FIFO这个保证了,B下发一个写操作,再下发一个读操作。那么就有 A写 -〉 B写 ——〉 B读的顺序,于是B就可以读到最新的数据了。这里ZooKeeper包装了一个sync的操作,就可以读到最新的数据了。

通过以上两个保证,zookeeper就可以充分的利用上replica从而实现了High performance的特性,且继承了Raft的High Available,