Zookeeper集群数据一致性

一个完整的Zookeeper集群由一个主节点(Leader),和若干个从节点(Follower、Observer)组成。

  • Leader:主要负责写数据和向从节点同步数据(如果有读请求打到主节点,会转发到任意从节点进行数据读取。当有写请求打到从节点,则会转发到主节点进行写操作,再由主节点同步至从节点)。
  • Follower:主要负责读数据和选举新的Leader(选举新的Leader时,只要有超过一半的Follower节点通过则生效),是集群环境中默认的从节点类型,会参与选举Leader。
  • Observer:主要用于扩容时使用的从节点类型,跟Follower类型一样负责读数据和接收Leader节点的数据同步,但是不参与选举新的Leader节点(主要是考虑不影响重新选举新的Leader节点时所需要的时间,因为参与选举的Follower节点越多,则选举花的时间就越长,而Zookeeper集群在选举新的Leader节点时是不可用的)。
ZAB协议
  • 消息广播模式:Zookeeper通过ZAB原子广播协议实现集群中主节点向从节点的数据同步,原理(2PC二阶段提交):当主节点接收到一个写请求时,会生成一个Zxid(全局事务ID),然后带上这个Zxid向从节点发出第一阶段的通知来询问从节点是否可以同步数据,当有超过一半的从节点在接收到通知并作出可以同步数据的应答时,Leader节点就会执行第二段的数据Commit操作,而实现集群中各节点的数据同步。
  • 崩溃恢复模式:用于当Leader节点崩溃时重新选举新的Leader节点,原理:当Leader节点挂掉后,剩余的Follower节点会优先选择每个节点中Zxid最大的那个做为新的Leader节点,如果Zxid的值都是相同的,则myid谁最大就是新的Leader节点,如果已经有过半的节点选举出了新的Leader节点,则后续启动的节点不会再加入到选举当中了。比如:
    1.(1,100)、(2,100)、(3,100)的三台Follower节点会选举出(2,100)这台作为Leader节点
    2.(1,103)、(2,102)、(3,101)的三台Follower节点会选举出(1,103)这台作为Leader节点
    Follower节点的Zxid:(最新事务ID)系统默认初始化时为0,主节点每次同步数据成功后Follower节点会更新Zxid的值,也就是每个从节点最后一次从主节点同步数据成功的那个Zxid的值。如果Leader节点挂了,需要重新选举Leader节点时,从节点的Zxid值越大则说明存储的数据越新,当这个数据最新的从节点被设为Leader节点后再将自己的数据同步到其他的从节点,也就重新实现了集群中各个节点的数据一致性。
    myid:Zookeeper服务器唯一ID
对比Eureka
  • 相同点:都可以实现微服务的注册中心
  • 不同点:Eureka去除了中心化的概念,即没有主节点与从节点的说法,集群中各个节点都是平等的,主要侧重与CAP理论当中的可用性。当集群中某个或多个Eureka节点不可用时,也不会影响到整个Eureka的使用,只要有一个节点存活就能保证整个服务注册中心的使用。

ZK基础理论

  • 数据模型:zookeeper的数据存储结构与标准的unix文件系统类似,在唯一的根节点(/)下挂很多子节点(树形结构),使用znode作为数据节点,是zookeeper中的最小数据单元,每个 znode 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。



    zookeeper的节点类型有持久节点、持久顺序节点、临时节点、临时顺序节点

    1. 持久节点:一旦创建了就会一直存在,直到主动删除该节点。
    2. 持久顺序节点:一个父节点通过在其子节点的名称后自动添加一个由10位数字组成的数字串(从0开始计数,如第一个顺序节点为name-0000000000)来创建顺序节点。
    3. 临时节点:临时节点的生命周期是与客户端会话绑定的,会话消失则节点消失(即客户端与服务端的TCP连接断开了会话就消失了)。临时节点只能做叶子节点,不能创建子节点。
    4. 临时顺序节点:跟持久顺序节点一样(只不过这个创建的是临时节点)。
  • Watcher机制
    zookeeper提供了Watcher事件监听器,客户端可以向服务端注册不同的Watcher监听器,当服务端被监听的节点符合某些事件或要求时会向客户端发送事件通知,客户端收到通知后找到自己定义的Watcher然后执行相应的回调方法 。



    Watcher事件具有one-time trigger(一次性触发)特性,即Watcher设置后,一旦触发一次就会失效,如果需要重复监听,则需要重新设置Watcher,watcher事件类型包含:

    1. EventType.NodeCreated:当node-x这个节点被创建时,该事件被触发。
    2. EventType.NodeChildrenChanged:当node-x这个节点的直接子节点被创建、被删除、子节点数据发生变更时,该事件被触发。
    3. EventType.NodeDataChanged:当node-x这个节点的数据发生变更时,该事件被触发。
    4. EventType.NodeDeleted:当node-x这个节点被删除时,该事件被触发。
    5. EventType.None:当zookeeper客户端的连接状态发生变更时,即KeeperState.Expired、KeeperState.Disconnected、KeeperState.SyncConnected、KeeperState.AuthFailed状态切换时,描述的事件类型为EventType.None。

ZK实现分布式锁

由于zookeeper的强一致性,能够很好地保证在高并发场景下保证节点创建的全局唯一性 (即无法重复创建同样的节点),可以通过创建临时节点+注册这个临时节点的消失监听事件来实现分布式锁。具体实现:由客户端发起临时节点的创建,如果创建成功,则说明获取锁成功。如果创建失败,则添加此临时节点消失的Watcher事件。如果之前获取到锁的客户端执行完任务主动删除临时节点或者出现异常断开与服务端的TCP长连接时就会触发监听事件,然后其他客户端重新通过创建临时节点来竞争锁(通过zookeeper实现分布式锁时,不需要像redis一样设置锁过期时间了(通过redis实现分布式锁时如果不设置过期时间,一旦持锁的遇到异常挂了,则这个锁就会一直无法释放了。所以会加个过期时间,如果没有正常的释放锁,则到了过期时间自动释放,以防止死锁),因为一旦客户端异常挂了,节点就消失了,锁也就释放了)。
但是上面这种模式会产生一个"羊群效应"问题,也就是当锁被释放时,会通知所有监听此临时节点的客户端来重新获取锁,但是每次却只有一个客户端能够获得锁。为了解决这个“羊群效应”问题,通过在某一个持久性根节点下创建临时顺序节点+监听自己的上一个临时顺序节点来实现(Apache Curator框架实现分zookeeper分布式锁时就是使用的这种模式)。
Apache Curator:是一个比较完善的ZooKeeper客户端框架,通过封装的一套高级API简化了ZooKeeper的操作。通过查看官方文档,可以发现Curator主要解决了三类问题:

  • 封装ZooKeeper client与ZooKeeper server之间的连接处理
  • 提供了一套Fluent风格的操作API
  • 提供ZooKeeper各种应用场景(比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装

关于Apache Curator实现zookeeper分布式锁的原理介绍,可以参考如下文章:ZooKeeper分布式锁的实现原理(https://my.oschina.net/u/3492343/blog/2992492)

你可能感兴趣的:(Zookeeper集群数据一致性)