ZooKeeper集群架构以及读写原理
in Zookeeper with 0 comment

ZooKeeper集群架构以及读写原理

in Zookeeper with 0 comment

1 什么是Zookeeper

Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance) 等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。核心实现了分布式CAP原理的CP特性
关于分布式CAP原理参考如下文章:分布式CAP原理

image.png

2 Zookeeper集群

2.1 Zookeeper集群原理

ZookeeperCluster.jpg

2.2 Zookeeper 集群中的角色

Zookeeper中,能改变ZooKeeper服务器状态的操作称为事务操作。一般包括数据节点创建与删除数据内容更新客户端会话创建与失效等操作

Zookeeper 通过复制来实现高可用,以Leader节点为准,Zookeeper的ZNode树上面的每一个修改都会被同步(复制)到其他的Server 节点上面

2.3 Zookeeper读写机制

读写流程

下图就是集群模式下一个Zookeeper Server节点提供读写服务的一个流程。

ZookeeperServer.jpg

如上图所示,每个Zookeeper Server节点除了包含一个请求处理器来处理请求以外,都会有一个内存数据库(ReplicatedDatabase) 用于持久化数据。ReplicatedDatabase 包含了整个Data Tree

ZookeeperClusterWrite.jpg

集群模式下,Zookeeper使用简单的同步策略,通过以下三条基本保证来实现数据的一致性

串行化可以把变量包括对象,转化成连续bytes数据. 你可以将串行化后的变量存在一个文件里或在网络上传输. 然后再反串行化还原为原来的数据。

简单来说,对数据的写请求,都会被转发到Leader节点来处理,Leader节点会对这次的更新发起投票,并且发送提议消息给集群中的其他节点,当半数以上的Follower节点将本次修改持久化之后,Leader 节点会认为这次写请求处理成功了,提交本次的事务

2.4 乐观锁

Zookeeper 的核心思想就是,提供一个非锁机制的Wait Free的用于分布式系统同步的核心服务。其核心对于文件、数据的读写服务,并不提供加锁互斥的服务

但是由于Zookeeper的每次更新操作都会更新ZNode的版本(详见第一章),也就是客户端可以自己基于版本的对比,来实现更新数据时的加锁逻辑,就像我们更新数据库时,会新增一个version字段,通过更新前后的版本对比来实现乐观锁

3 ZAB协议

ZAB协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的一致性协议,这个机制保证了各个server之间的同步。全称 Zookeeper Atomic Broadcast Protocol - Zookeeper 原子广播协议,Zab协议有两种模式,它们分别是恢复模式广播模式

3.1 广播模式

广播模式类似于分布式事务中的 Two-phase commit (两阶段式提交),因为Zookeeper中一次写操作就是被当做一个事务,所以这实际上本质是相同的

broadcastingMode.jpg

步骤:

广播协议在所有的通讯过程使用TCP的FIFO信道中****,通过使用该信道,使保持有序性变得非常的容易。通过FIFO信道,消息被有序的deliver。只要收到的消息一被处理,其顺序就会被保存下来

但是这种模式下,如果Leader自身发生了故障,Zookeeper的集群不就提供不了写服务了吗? 这就引入了下面的恢复模式

3.2 恢复模式

简单点来说,当集群中的Leader故障或者服务启动的时候,ZAB就会进入恢复模式,其中包括Leader选举和完成其他Server和Leader之间的状态同步

4 Zookeeper应用场景

4.1 配置中心

发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。

4.2 负载均衡

这里说的负载均衡是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡

4.3 服务注册(Naming Service)

命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。

阿里巴巴集团开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表, 点击这里查看Dubbo开源项目。在Dubbo实现中:
服务提供者在启动的时候,向ZK上的指定节点/dubbo/$
/providers目录下写入自己的URL地址,这个操作就完成了服务的发布。
服务消费者启动的时候,订阅/dubbo/$
/providers目录下的提供者URL地址, 并向/dubbo/$ /consumers目录下写入自己的URL地址。
注意,所有向ZK上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。 另外,Dubbo还有针对服务粒度的监控,方法是订阅/dubbo/$
目录下所有提供者和消费者的信息。

4.4 分布式通知/协调

ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理

  1. 另一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。
  2. 另一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了ZK上某些节点的状态,而ZK就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。
  3. 另一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。
    总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合

4.5 集群管理与Master选举

  1. 集群中机器有变动的时候,牵连修改的东西比较多。
  2. 有一定的延时。
    利用ZooKeeper有两个特性,就可以实现另一种集群机器存活性监控系统:
  3. 客户端在节点 x 上注册一个Watcher,那么如果 x?的子节点变化了,会通知该客户端。
  4. 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。
    例如,监控系统在 /clusterServers 节点上注册一个Watcher,以后每动态加机器,那么就往 /clusterServers 下创建一个 EPHEMERAL类型的节点:/clusterServers/
    . 这样,监控系统就能够实时知道机器的增减情况,至于后续处理就是监控系统的业务了。
  1. 在搜索系统中,如果集群中每个机器都生成一份全量索引,不仅耗时,而且不能保证彼此之间索引数据一致。因此让集群中的Master来进行全量索引的生成,然后同步到集群中其它机器。另外,Master选举的容灾措施是,可以随时进行手动指定master,就是说应用在zk在无法获取master信息时,可以通过比如http方式,向一个地方获取master。
  2. 在Hbase中,也是使用ZooKeeper来实现动态HMaster的选举。在Hbase实现中,会在ZK上存储一些ROOT表的地址和HMaster的地址,HRegionServer也会把自己以临时节点(Ephemeral)的方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的存活状态,同时,一旦HMaster出现问题,会重新选举出一个HMaster来运行,从而避免了HMaster的单点问题

4.6 分布式锁

分布式锁,这个主要得益于ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

  1. 所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把 zk 上的一个 znode 看作是一把锁,通过 create znode 的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。
  2. 控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distributelock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERALSEQUENTIAL 来指定)。Zk 的父节点(/distribute_lock)维持一份 sequence, 保证子节点创建的时序性,从而也形成了每个客户端的全局时序