zookeeper动物管理员,是一个很形象的名字,是一个分布式协调服务。它可以用来做分布式配置管理,服务注册及发现,分布式锁。在CAP中,属于CP型。
下图是zookeeper的架构图:
图中,绿色的是client,红色的是一个leader,蓝色的是其follower,红色和蓝色合称为Ensemble(合奏团)。
- Ensemble:zookeeper集群所有server构成一个Ensemble,要求必须至少3台server。
- Leader:负责改变zookeeper的状态ZooKeeper state: create, setData, and delete.
- Follower:负责执行leader的命令。
- Client:client和server之间有心跳保持连接,如果server挂了zookeeper会自动failover到别的server上去。Client连接后zookeeper都会有相应的session,session就是一次活动的时间段,session中可以有这次活动的变量,client的session从client建立连接开始一直到client断开连接为止。
- Quorum:集群中过半数的server的数量。比如集群中共有5台机器,那么quorum是5/2+1==3台。
一个zookeeper集群有一个leader,负责读写并把数据复制给follower,follower只负责读。是主从结构。当leader挂掉后,zookeeper可以在200ms内重新选出新leader,可用性比较强。Read操作会从当前连接的server读(但是因为write只需要quorum数量满足即可生效,所以有可能client读的是一台没同步的机器,所以这台机器需要先从leader sync一下再返回),write写操作需要集群里面大多数server都确认后才算ok,大多数指的是过半数。所以写性能随集群数量的增大而下降,读性能随集群数量增大而增加。所以,zookeeper有了observer server,他们不参与选举,不算大多数的一部分,但是可以同步leader的数据扩展读能力且不降低写能力,这样集群增大时不至于降低太多写能力。可见,read和write都需要leader,所以leader挂了整个cluster不能对外提供任何服务。如果集群中不够quorum量,整个cluster也不能对外提供任何服务。
zookeeper由leader把数据复制到follower上,同传统数据库类似,zookeeper的每项操作也是有事务的,有事务id,叫做Zxid(zookeeper transaction id,是个递增的数值)。先来看看zookeeper的数据模型,是kv型,其中key是类似于linux的目录树:
zookeeper数据模型遵循分层命名空间,其中每个节点称为Znode,这是群集运行的系统的一部分。和目录不同的是即使中间的znode也能存数据,数据大小不超过1M,同redis一样,存放的数据也是二进制安全的。如下图:
zookeeper的znode节点有3种类型:
- 持久性Znode:
集合中的所有节点都将自己视为持久性Znode。即使在客户端断开连接后,这些节点仍会保持活动状态。
- 临时Znode:
这些类型的节点将保持活动状态,直到客户端连接到它们为止。当客户端断开连接时,他们会死亡。这些类型的节点不允许有子节点。
- 顺序Znode:
它可以是持久性Znode或临时Znode。将节点创建为顺序Znode时,可以通过在原始名称上附加10位序列号来分配Znode的路径。
zookeeper如何选择leader呢?每个server启动后都是looking状态,它要么选举一个leader要么找到一个leader。有如下两种情况:
- 如果leader已经存在了,其他server就会通知它哪台server是leader,然后这台机必须和leader进行沟通以使得自己的state和leader的state保持一致。
- 如果leader不存在,也就是所有的server都是looking状态,那server必须相互沟通以选举出一个leader。一开始每台server都投票给自己,然后根据规则(voteZxid > myZxid) or (voteZxid = myZxid and voteSid > mySid)决定是否改变自己的投票(比如别人比自己更up to date那就投别人),当收到过半数投票后就会选一个胜出的,简单来说就是most up to date的那台server胜出。
zookeeper使用两步法来进行数据的写入。叫做zab协议,zookeeper atomic broadcast,即zookeeper原子广播协议。
具体步骤是这样:
- 客户端发起写请求给某台server,server如果不是leader再转给leader。
- Leader将客户端请求信息转化为事务Proposal,同时为每个Proposal分配一个事务ID(Zxid)。
- Leader为每个Follower单独分配一个FIFO的队列,将需要广播的Proposal依次放入到队列中。
- Follower接收到Proposal后,首先将其以事务日志log的方式写入到本地磁盘中,写入成功后给Leader反馈一个ACK响应。
- Leader接收到半数以上Follower的ACK响应后,即认为消息发送成功,可以发送Commit消息。
- Leader向所有Follower广播Commit消息,同时自身也会完成事务提交。Follower接收到Commit消息后也会完成事务的提交,写入内存,commit后follower自身才有这时的zxid可用于leader挂掉后的leader选举,zookeeper同redis也是内存型的。
之所以分为两个阶段,是为了原子性。如果第一个步骤有过半的成功响应,则commit,否则不commit,不会存在中间状态。
另外,之所以要求过半的成功响应,是为了当集群挂掉后数据不会丢失。因为集群再次可用需要过半的server ok状态,因为之前有过半的成功响应,所以此时过半的server ok中必然存在log中保存有commit的数据,数据不会丢失。