Zookeeper可以帮我们实现服务的注册与发现。然而,现在有一个问题是,如果只采用一个Zookeeper服务器,那么当这个服务器宕机时,意味着整个分布式服务无法正常工作。为了解决这一问题,就需要Zookeeper集群。
然而,在使用Zookeeper集群时,也存在着一个问题,即集群中数据一致性的维护。
如上图所示,Zookeeper集群是一主多从结构。
- 在更新数据时,首先更新到主服务节点,再更新到从服务节点;
- 在读数据时,直接读取任意从服务节点;
- 为了保证从节点的数据一致性,Zookeeper采用ZAB协议,类似于一致性算法Paxos和Raft。
ZAB解决了集群崩溃恢复和主从数据同步两个问题。
1. 集群崩溃恢复
服务节点共有三种状态:
- Looking:选举状态;
- Following:从服务节点状态;
- Leading:主服务节点状态。
选举过程
当主服务节点宕机时,从从服务节点中选举主服务节点。
-
集群中的服务节点各自向其他节点发起投票,投票当中包含自己的服务器ID和最大ZXID(自增事务ID);
-
节点会用自身的ZXID和从其他服务节点接收到的ZXID做比较。如果发现其他节点的ZXID比自己的大,也就是数据比自己新,那么久重新发起投票,投票给目前已知的最大ZXID所属的服务节点;
-
每次投票之后,服务器都会统计投票数量,判断是否某个服务节点得到半数或以上的投票,如果存在这样的节点,该节点将成为准Leader,状态变为Leading,其他节点的状态变为Following。
发现阶段
通过上述选举阶段后,为了防止网络等原因而发生的意外的情况,也就是说,可能出现多主节点的情况。这种情况会发生写写冲突,为了避免这种情况,Leader集思广益,接收所有的Follower发来的各自最新的epoch值,Leader从中选出最大的epoch,并基于此值加1,生成的新的epoch分发给各个Follower。
各个Follower接收到全新的epoch后,返回ACK给Leader,并带上各自最大的ZXID和历史事务日志。Leader选出最大的ZXID,并更新自身的历史日志,以保持数据最新。
同步阶段
把Leader收到的最新历史事务日志同步给集群中所有的Follower,只有当半数Follower同步成功,这个准Leader才能成为正式的Leader。
2. 主从数据同步
ZAB的数据写入涉及到Broadcase阶段,即Leader广播到所有的Follower,其过程如下:
- 客户端发出写入数据请求给任意的Follower(服务注册);
- Follower把写入数据请求转发给Leader(读写分离,读节点不负责写,只有主节点能够写入);
- Leader采用两阶段提交的方式,先发送Propose广播给Follower(先保留提交日志,后执行更新,类似于事务);
- Follower接收到Propose消息,写入日志成功后,返回ACK给Leader(开启事务);
- Leader接收到半数以上的ACK消息,返回成功给客户端,并且广播Commit请求给Follower(提交事务,可以进行数据持久化)。
数据一致性有三种:强一致性、弱一致性和顺序一致性。而Zookeeper属于顺序一致性。