【Zookeeper入门篇一:基本概念】

出现背景

  1. ZooKeeper是一款开源的针对分布式应用分布式协调服务
  2. 使用一种类似于文件系统的目录树结构的数据模型,以 java 方式运行。
  3. ZooKeeper背后的动机是为分布式应用程序减轻从零开始实现协调服务的难度。

Zookeeper 从设计模式角度来看,是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式。

功能特性

Znode节点

ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识,结构图如下:


【Zookeeper入门篇一:基本概念】_第1张图片
图片.png

那ZooKeeper这颗"树"有什么特点呢??ZooKeeper的节点我们称之为Znode,Znode分为两种类型:

  • 短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除
  • 持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除

监听器

在上面我们已经简单知道了ZooKeeper的数据结构了,ZooKeeper还配合了监听器才能够做那么多事的。

常见的监听场景有以下两项:

  • 监听Znode节点的数据变化

  • 监听子节点的增减变化

【Zookeeper入门篇一:基本概念】_第2张图片
图片.png
【Zookeeper入门篇一:基本概念】_第3张图片
图片.png

没错,通过监听+Znode节点(持久/短暂[临时]),ZooKeeper就可以玩出这么多花样了。

适用场景

利用 ZooKeeper 可以非常方便构建一系列分布式应用中都会涉及到的核心功能。具体包括如下:

  • 配置管理(Configuration Management)
  • 命名服务(Name Service)
  • 分布式锁(Distributed Locks)
  • 集群管理(Group Membership)
  • 分布式协调/通知
  • 负载均衡(Load Balance)
  • Master 选举
  • 分布式队列

配置管理(Configuration Management)(重点)

数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到 ZooKeeper 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。

配置信息一般有几个特点:

  • 数据量小的KV
  • 数据内容在运行时会发生动态变化
  • 集群机器共享,配置一致
【Zookeeper入门篇一:基本概念】_第4张图片
图片.png

ZooKeeper 采用的是推拉结合的方式。

  • 推: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知
  • 拉: 客户端获得通知后,然后主动到服务端拉取最新的数据

实现的思路可以如下。

mysql.driverClassName=com.mysql.jdbc.Driver
dbJDBCUrl=jdbc:mysql://127.0.0.1/runzhlliu
username=runzhliu
password=runzhliu

把配置信息写到一个 Znode 上,例如 /Configuration
客户端启动初始化阶段读取服务端节点的数据,并且注册一个数据变更的 Watcher
配置变更只需要对 Znode 数据进行 set 操作,数据变更的通知会发送到客户端,客户端重新获取新数据,完成配置动态修改

集群管理(重点)

集群管理主要指集群监控和集群控制两个方面。前者侧重于集群运行时的状态的收集,后者则是对集群进行操作与控制。开发和运维中,面对集群,经常有如下需求:

希望知道集群中究竟有多少机器在工作对集群中的每台机器的运行时状态进行数据收集对集群中机器进行上下线的操作。

分布式集群管理体系中有一种传统的基于 Agent 的方式,就是在集群每台机器部署 Agent 来收集机器的 CPU、内存等指标。但是如果需要深入到业务状态进行监控,比如一个分布式消息中间件中,希望监控每个消费者对消息的消费状态,或者一个分布式任务调度系统中,需要对每个机器删的任务执行情况进行监控。对于这些业务紧密耦合的监控需求,统一的 Agent 是不太合适的。

利用 ZooKeeper 实现集群管理监控组件的思路:


【Zookeeper入门篇一:基本概念】_第5张图片
图片.png

在管理机器上线/下线的场景中,为了实现自动化的线上运维,我们必须对机器的上/下线情况有一个全局的监控。通常在新增机器的时候,需要首先将指定的 Agent 部署到这些机器上去。Agent 部署启动之后,会首先向 ZooKeeper 的指定节点进行注册,具体的做法就是在机器列表节点下面创建一个临时子节点,例如 /machine/[Hostname](下文我们以“主机节点”代表这个节点),如下图所示。

当 Agent 在 ZooKeeper 上创建完这个临时子节点后,对 /machines 节点关注的监控中心就会接收到“子节点变更”事件,即上线通知,于是就可以对这个新加入的机器开启相应的后台管理逻辑。另一方面,监控中心同样可以获取到机器下线的通知,这样便实现了对机器上/下线的检测,同时能够很容易的获取到在线的机器列表,对于大规模的扩容和容量评估都有很大的帮助。

还是以我们三个系统A、B、C为例,在ZooKeeper中创建临时节点即可:


【Zookeeper入门篇一:基本概念】_第6张图片
图片.png

只要系统A挂了,那/groupMember/A这个节点就会删除,通过监听groupMember下的子节点,系统B和C就能够感知到系统A已经挂了。(新增也是同理)

除了能够感知节点的上下线变化,ZooKeeper还可以实现动态选举Master的功能。(如果集群是主从架构模式下)

原理也很简单,如果想要实现动态选举Master的功能,Znode节点的类型是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)就好了。

Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了。

命名服务(Name Service)(重点)

命名服务就是提供名称的服务。ZooKeeper 的命名服务有两个应用方面。

提供类似 JNDI 功能

可以把系统中各种服务的名称、地址以及目录信息存放在 ZooKeeper,需要的时候去 ZooKeeper 中读取制作分布式的序列号生成器。

统一命名服务的命名结构图如下所示:

【Zookeeper入门篇一:基本概念】_第7张图片
image

1、在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。

a)类似于域名与ip之间对应关系,ip不容易记住,而域名容易记住。

b)通过名称来获取资源或服务的地址,提供者等信息。

2、按照层次结构组织服务/应用名称。

a)可将服务名称以及地址信息写到ZooKeeper上,客户端通过ZooKeeper获取可用服务列表类。

序号生成器

利用 ZooKeeper 顺序节点的特性,制作分布式的序列号生成器,或者叫 id 生成器。(分布式环境下使用作为数据库 id,另外一种是 UUID(缺点:没有规律)),ZooKeeper 可以生成有顺序的容易理解的同时支持分布式环境的编号。

在创建节点时,如果设置节点是有序的,则 ZooKeeper 会自动在你的节点名后面加上序号,上面说容易理解,是比如说这样,你要获得订单的 id,你可以在创建节点时指定节点名为 order_[日期]_xxxxxx,这样一看就大概知道是什么时候的订单。

/
└── /order
    ├── /order-date1-000000000000001
    ├── /order-date2-000000000000002
    ├── /order-date3-000000000000003
    ├── /order-date4-000000000000004
    └── /order-date5-000000000000005

分布式锁(重点)

系统A、B、C都去访问/locks节点


【Zookeeper入门篇一:基本概念】_第8张图片
图片.png

访问的时候会创建带顺序号的临时/短暂(EPHEMERAL_SEQUENTIAL)节点,比如,系统A创建了id_000000节点,系统B创建了id_000002节点,系统C创建了id_000001节点。

【Zookeeper入门篇一:基本概念】_第9张图片
图片.png

接着,拿到/locks节点下的所有子节点(id_000000,id_000001,id_000002),判断自己创建的是不是最小的那个节点

  • 如果是,则拿到锁。

    • 释放锁:执行完操作后,把创建的节点给删掉
  • 如果不是,则监听比自己要小1的节点变化

举个例子:

系统A拿到/locks节点下的所有子节点,经过比较,发现自己(id_000000),是所有子节点最小的。所以得到锁

系统B拿到/locks节点下的所有子节点,经过比较,发现自己(id_000002),不是所有子节点最小的。所以监听比自己小1的节点id_000001的状态

系统C拿到/locks节点下的所有子节点,经过比较,发现自己(id_000001),不是所有子节点最小的。所以监听比自己小1的节点id_000000的状态

……

等到系统A执行完操作以后,将自己创建的节点删除(id_000000)。通过监听,系统C发现id_000000节点已经删除了,发现自己已经是最小的节点了,于是顺利拿到锁

….系统B如上

【Zookeeper入门篇一:基本概念】_第10张图片
处理流程图

分布式协调/通知

一种典型的分布式系统机器间的通信方式是心跳。心跳检测是指分布式环境中,不同机器之间需要检测彼此是否正常运行。传统的方法是通过主机之间相互 PING 来实现,又或者是建立长连接,通过 TCP 连接固有的心跳检测机制来实现上层机器的心跳检测。

如果使用 ZooKeeper,可以基于其临时节点的特性,不同机器在 ZooKeeper 的一个指定节点下创建临时子节点,不同机器之间可以根据这个临时节点来判断客户端机器是否存活。

好处就是检测系统和被检系统不需要直接相关联,而是通过 ZooKeeper 节点来关联,大大减少系统的耦合。

负载均衡

负载均衡是一种手段,用来把对某种资源的访问分摊给不同的设备,从而减轻单点的压力。

实现的思路:

首先建立 Servers 节点,并建立监听器监视 Servers 子节点的状态(用于在服务器增添时及时同步当前集群中服务器列表)在每个服务器启动时,在 Servers 节点下建立临时子节点 Worker Server,并在对应的字节点下存入服务器的相关信息,包括服务的地址,IP,端口等等可以自定义一个负载均衡算法,在每个请求过来时从 ZooKeeper 服务器中获取当前集群服务器列表,根据算法选出其中一个服务器来处理请求。

【Zookeeper入门篇一:基本概念】_第11张图片
图片.png

分布式队列

FIFO

使用 ZooKeeper 实现 FIFO 队列,入队操作就是在 queue_fifo 下创建自增序的子节点,并把数据(队列大小)放入节点内。出队操作就是先找到 queue_fifo 下序号最下的那个节点,取出数据,然后删除此节点。

/queue_fifo
|
├── /host1-000000001
├── /host2-000000002
├── /host3-000000003
└── /host4-000000004

创建完节点后,根据以下步骤确定执行顺序:

  1. 通过 get_children() 接口获取 /queue_fifo 节点下所有子节点
  2. 确认自己的节点序号在所有子节点中的顺序
  3. 如果不是最小的子节点,那么进入等待,同时向比自己序号小的最后一个子节点注册 Watcher 监听接
  4. 收到 Watcher 通知后重复 1

Barrier

Barrier就是栅栏或者屏障,适用于这样的业务场景:当有些操作需要并行执行,但后续操作又需要串行执行,此时必须等待所有并行执行的线程全部结束,才开始串行,于是就需要一个屏障,来控制所有线程同时开始,并等待所有线程全部结束。


【Zookeeper入门篇一:基本概念】_第12张图片
图片.png

利用 ZooKeeper 的实现,开始时 queue_barrier 节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数字 n 来代表 Barrier 值,比如 n=10 代表只有当 /queue_barrier 节点下的子节点个数达到10才会打开 Barrier。之后所有客户端都会在 queue_barrier 节点下创建一个临时节点,如 queue_barrier/host1

【Zookeeper入门篇一:基本概念】_第13张图片
处理流程图

如何控制所有线程同时开始? 所有的线程启动时在 ZooKeeper 节点 /queue_barrier 下插入顺序临时节点,然后检查 /queue/barrier 下所有 children 节点的数量是否为所有的线程数,如果不是,则等待,如果是,则开始执行。具体的步骤如下:

getData() 获取 /queue_barrier节点的数据内容getChildren() 获取/queue_barrier节点下的所有子节点,同时注册对子节点列表变更的 Watche 监听。统计子节点的个数如果子节点个数不足10,那么进入等待接收 Watcher 通知后,重复2

如何等待所有线程结束? 所有线程在执行完毕后,都检查 /queue/barrier 下所有 children 节点数量是否为0,若不为0,则继续等待。

用什么类型的节点? 根节点使用持久节点,子节点使用临时节点,根节点为什么要用持久节点?首先因为临时节点不能有子节点,所以根节点要用持久节点,并且在程序中要判断根节点是否存在。 子节点为什么要用临时节点?临时节点随着连接的断开而消失,在程序中,虽然会删除临时节点,但可能会出现程序在节点被删除之前就 crash了,如果是持久节点,节点不会被删除。

ZooKeeper 在大型分布式系统的应用

  1. kafka
    Kafka 中大部分组件都应用了 ZooKeeper。
  • Broker 注册 `/broker/ids/[0...N] 记录了 Broker 服务器列表记录,这个临时节点的节点数据是 ip 端口之类的信息。
  • Topic 注册 /broker/topcs 记录了 Topic 的分区信息和 Broker 的对应关系
  • 生产者负载均衡 生产者需要将消息发送到对应的 Broker 上,生产者通过 Broker 和 Topic 注册的信息,以及 Broker 和 Topic 的对应关系和变化注册事件 Watcher 监听,从而实现一种动态的负载均衡机制。
  • 消息消费进度 Offset 记录 消费者对指定消息分区进行消息消费的过程中,需要定时将分区消息的消费进度 Offset 记录到 ZooKeeper 上,以便消费者进行重启或者其他消费者重新阶段该消息分区的消息消费后,能够从之前的进度开始继续系消费

详细介绍见参考资料4,这里不再展开介绍了。

  1. Dubbo
    Dubbo 基于 ZooKeeper 实现了服务注册中心。哪一个服务由哪一个机器来提供必需让调用者知道,简单来说就是 ip 地址和服务名称的对应关系。ZooKeeper 通过心跳机制可以检测挂掉的机器并将挂掉机器的 ip 和服务对应关系从列表中删除。

至于支持高并发,简单来说就是横向扩展,在不更改代码的情况通过添加机器来提高运算能力。通过添加新的机器向 ZooKeeper 注册服务,服务的提供者多了能服务的客户就多了。

【Zookeeper入门篇一:基本概念】_第14张图片
Dubbo

参考资料

  1. ZooKeeper 的应用场景
  2. 什么是ZooKeeper?
  3. 分布式服务框架 Zookeeper -- 管理分布式环境中的数据
    4.Zookeeper 在 Kafka 中的作用

你可能感兴趣的:(【Zookeeper入门篇一:基本概念】)