Zookeeper什么,它能够作什么?看了这篇就懂了

前言

什么是ZooKeeper,你真的了解它吗。咱们一块儿来看看吧~html

什么是 ZooKeeper

ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效、高可用的分布式协调服务,提供了诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知和分布式锁等分布式基础服务。因为 ZooKeeper 便捷的使用方式、卓越的性能和良好的稳定性,被普遍地应用于诸如 Hadoop、HBase、Kafka 和 Dubbo 等大型分布式系统中。node

Zookeeper 有三种运行模式:单机模式、伪集群模式和集群模式。面试

单机模式:
这种模式通常适用于开发测试环境,一方面咱们没有那么多机器资源,另外就是平时的开发调试并不须要极好的稳定性。算法

集群模式:
一个 ZooKeeper 集群一般由一组机器组成,通常 3 台以上就能够组成一个可用的 ZooKeeper 集群了。组成 ZooKeeper 集群的每台机器都会在内存中维护当前的服务器状态,而且每台机器之间都会互相保持通讯。docker

伪集群模式:
这是一种特殊的集群模式,即集群的全部服务器都部署在一台机器上。当你手头上有一台比较好的机器,若是做为单机模式进行部署,就会浪费资源,这种状况下,ZooKeeper 容许你在一台机器上经过启动不一样的端口来启动多个 ZooKeeper 服务实例,以此来以集群的特性来对外服务。apache

ZooKeeper 的相关知识

Zookeeper 中的角色:服务器

领导者(leader):
负责进行投票的发起和决议,更新系统状态。session

跟随者(follower):
用于接收客户端请求并给客户端返回结果,在选主过程当中进行投票。负载均衡

观察者(observer):
能够接受客户端链接,将写请求转发给 leader,可是observer 不参加投票的过程,只是为了扩展系统,提升读取的速度。框架

Zookeeper 的数据模型

1.层次化的目录结构,命名符合常规文件系统规范,相似于 Linux。

2.每一个节点在 Zookeeper 中叫作 Znode,而且其有一个惟一的路径标识。

3.节点 Znode 能够包含数据和子节点,可是 EPHEMERAL 类型的节点不能有子节点。

4.Znode 中的数据能够有多个版本,好比某一个路径下存有多个数据版本,那么查询这个路径下的数据就须要带上版本。

5.客户端应用能够在节点上设置监视器。

6.节点不支持部分读写,而是一次性完整读写。

ZooKeeper 的节点特性

ZooKeeper 节点是生命周期的,这取决于节点的类型。在 ZooKeeper 中,节点根据持续时间能够分为持久节点(PERSISTENT)、临时节点(EPHEMERAL),根据是否有序能够分为顺序节点(SEQUENTIAL)、和无序节点(默认是无序的)。

持久节点一旦被建立,除非主动移除,否则一直会保存在 Zookeeper 中(不会由于建立该节点的客户端的会话失效而消失)。

Zookeeper 的应用场景

ZooKeeper 是一个高可用的分布式数据管理与系统协调框架。基于对 Paxos 算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得 ZooKeeper 解决不少分布式问题。

值得注意的是,ZooKeeper 并不是天生就是为这些应用场景设计的,都是后来众多开发者根据其框架的特性,利用其提供的一系列 API 接口(或者称为原语集),摸索出来的典型使用方法。

数据发布与订阅(配置中心)

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

应用中用到的一些配置信息放到 ZooKeeper 上进行集中管理。这类场景一般是这样:应用在启动的时候会主动来获取一次配置,同时在节点上注册一个 Watcher。这样一来,之后每次配置有更新的时候,都会实时通知到订阅的客户端,历来达到获取最新配置信息的目的。

分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在 ZooKeeper 的一些指定节点,供各个客户端订阅使用。

分布式日志收集系统

这个系统的核心工做是收集分布在不一样机器的日志。收集器一般是按照应用来分配收集任务单元,所以须要在 ZooKeeper 上建立一个以应用名做为 path 的节点 P,并将这个应用的全部机器 IP,以子节点的形式注册到节点 P 上。这样一来就可以实现机器变更的时候,可以实时通知到收集器调整任务分配。

系统中有些信息须要动态获取,而且还会存在人工手动去修改这个信息的发问。一般是暴露出接口,例如 JMX 接口,来获取一些运行时的信息。引入 ZooKeeper 以后,就不用本身实现一套方案了,只要将这些信息存放到指定的 ZooKeeper 节点上便可。

注意:
在上面提到的应用场景中,有个默认前提——数据量很小,可是数据更新可能会比较快的场景。

负载均衡

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

命名服务(Naming Service)

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

阿里巴巴集团开源的分布式服务框架 Dubbo 中使用 ZooKeeper 来做为其命名服务,维护全局的服务地址列表。在 Dubbo 的实现中:

1.服务提供者在启动的时候,向 ZooKeeper 上的指定节点 /dubbo/${serviceName}/providers 目录下写入本身的 URL 地址,这个操做就完成了服务的发布。

2.服务消费者启动的时候,订阅 /dubbo/${serviceName}/providers 目录下的提供者 URL 地址, 并向 /dubbo/${serviceName} /consumers 目录下写入本身的 URL 地址。

注意:
全部向 ZooKeeper 上注册的地址都是临时节点,这样就可以保证服务提供者和消费者可以自动感应资源的变化。

另外,Dubbo 还有针对服务粒度的监控。方法是订阅 /dubbo/${serviceName} 目录下全部提供者和消费者的信息。

分布式通知/协调

ZooKeeper 中特有 Watcher 注册与异步通知机制,可以很好的实现分布式环境下不一样系统之间的通知与协调,实现对数据变动的实时处理。使用方法一般是不一样系统都对 ZooKeeper 上同一个 Znode 进行注册,监听 Znode 的变化(包括 Znode 自己内容及子节点的),其中一个系统 Update 了 Znode,那么另外一个系统可以收到通知,并做出相应处理。

另外一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是经过 ZooKeeper 上某个节点关联,大大减小系统耦合。

另外一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工做。管理人员在控制台做的一些操做,其实是修改了 ZooKeeper 上某些节点的状态,而 ZooKeeper 就把这些变化通知给它们注册 Watcher 的客户端,即推送系统。因而,做出相应的推送任务。

另外一种工做汇报模式:一些相似于任务分发系统。子任务启动后,到 ZooKeeper 来注册一个临时节点,而且定时将本身的进度进行汇报(将进度写回这个临时节点)。这样任务管理者就可以实时知道任务进度。

分布式锁

分布式锁主要得益于 ZooKeeper 为咱们保证了数据的强一致性。锁服务能够分为两类:一类是保持独占,另外一类是控制时序。

所谓保持独占,就是全部试图来获取这个锁的客户端,最终只有一个能够成功得到这把锁。一般的作法是把 ZooKeeper 上的一个 Znode 看做是一把锁,经过 create znode的方式来实现。全部客户端都去建立 /distribute_lock 节点,最终成功建立的那个客户端也即拥有了这把锁。

控制时序,就是全部视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。作法和上面基本相似,只是这里 /distribute_lock 已经预先存在,客户端在它下面建立临时有序节点(这个能够经过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL 来指定)。ZooKeeper 的父节点(/distribute_lock)维持一份 sequence,保证子节点建立的时序性,从而也造成了每一个客户端的全局时序。

1.因为同一节点下子节点名称不能相同,因此只要在某个节点下建立 Znode,建立成功即代表加锁成功。注册监听器监听此 Znode,只要删除此 Znode 就通知其余客户端来加锁。

2.建立临时顺序节点:在某个节点下建立节点,来一个请求则建立一个节点,因为是顺序的,因此序号最小的得到锁,当释放锁时,通知下一序号得到锁。

分布式队列

队列方面,简单来讲有两种:一种是常规的先进先出队列,另外一种是等队列的队员聚齐之后才按照顺序执行。对于第一种的队列和上面讲的分布式锁服务中控制时序的场景基本原理一致,这里就不赘述了。

第二种队列实际上是在 FIFO 队列的基础上做了一个加强。一般能够在 /queue 这个 Znode 下预先创建一个 /queue/num 节点,而且赋值为 n(或者直接给 /queue 赋值 n)表示队列大小。以后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否能够开始执行了。

这种用法的典型场景是:分布式环境中,一个大任务 Task A,须要在不少子任务完成(或条件就绪)状况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下创建本身的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL)。当 /taskList 发现本身下面的子节点知足指定个数,就能够进行下一步按序进行处理了。

使用 dokcer-compose 搭建集群

上面咱们介绍了关于 ZooKeeper 有这么多的应用场景,那么接下来就先学习如何搭建 ZooKeeper 集群而后再进行实战上面的应用场景。

文件的目录结构以下:

├── docker-compose.yml

编写 docker-compose.yml 文件

docker-compose.yml 文件内容以下:

version: '3.4'

services:
  zoo1:
    image: zookeeper
    restart: always
    hostname: zoo1
    ports:
      - 2181:2181
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181

  zoo2:
    image: zookeeper
    restart: always
    hostname: zoo2
    ports:
      - 2182:2181
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zoo3:2888:3888;2181

  zoo3:
    image: zookeeper
    restart: always
    hostname: zoo3
    ports:
      - 2183:2181
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181

在这个配置文件中,Docker 运行了 3 个 Zookeeper 镜像,经过 ports 字段分别将本地的 2181, 2182, 2183 端口绑定到对应容器的 2181 端口上。

ZOO_MY_ID 和 ZOO_SERVERS 是搭建 Zookeeper 集群须要的两个环境变量。ZOO_MY_ID 标识服务的 id,为 1-255 之间的整数,必须在集群中惟一。ZOO_SERVERS 是集群中的主机列表。

在 docker-compose.yml 所在目录下执行 docker-compose up,能够看到启动的日志。

链接 ZooKeeper

将集群启动起来之后咱们能够链接 ZooKeeper 对其进行节点的相关操做。

1.首先须要下载 ZooKeeper。

2.将其解压。

3.进入其 conf/ 目录,将 zoo_sample .cfg 改为 zoo.cfg。

配置文件说明

# The number of milliseconds of each tick
# tickTime:CS通讯心跳数
# Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每一个 tickTime 时间就会发送一个心跳。tickTime以毫秒为单位。
tickTime=2000

# The number of ticks that the initial
# synchronization phase can take
# initLimit:LF初始通讯时限
# 集群中的follower服务器(F)与leader服务器(L)之间初始链接时能容忍的最多心跳数(tickTime的数量)。
initLimit=5

# The number of ticks that can pass between
# sending a request and getting an acknowledgement
# syncLimit:LF同步通讯时限
# 集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数(tickTime的数量)。
syncLimit=2

# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# dataDir:数据文件目录
# Zookeeper保存数据的目录,默认状况下,Zookeeper将写数据的日志文件也保存在这个目录里。
dataDir=/data/soft/zookeeper-3.4.12/data

# dataLogDir:日志文件目录
# Zookeeper保存日志文件的目录。
dataLogDir=/data/soft/zookeeper-3.4.12/logs

# the port at which the clients will connect
# clientPort:客户端链接端口
# 客户端链接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1


# 服务器名称与地址:集群信息(服务器编号,服务器地址,LF通讯端口,选举端口)
# 这个配置项的书写格式比较特殊,规则以下:
# server.N=YYY:A:B
# 其中N表示服务器编号,YYY表示服务器的IP地址,A为LF通讯端口,表示该服务器与集群中的leader交换的信息的端口。B为选举端口,表示选举新leader时服务器间相互通讯的端口(当leader挂掉时,其他服务器会相互通讯,选择出新的leader)。通常来讲,集群中每一个服务器的A端口都是同样,每一个服务器的B端口也是同样。可是当所采用的为伪集群时,IP地址都同样,只能时A端口和B端口不同。

能够不修改 zoo.cfg,使用默认配置。接下来在解压后的 bin/ 目录中执行命令 ./zkCli.sh -server 127.0.0.1:2181 就能进行链接了。

Welcome to ZooKeeper!
2020-06-01 15:03:52,512 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1025] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2020-06-01 15:03:52,576 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@879] - Socket connection established to localhost/127.0.0.1:2181, initiating session
2020-06-01 15:03:52,599 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x100001140080000, negotiated timeout = 30000
WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: 127.0.0.1:2181(CONNECTED) 0]

接下来可使用命令查看节点:

使用 ls 命令查看当前 ZooKeeper 中所包含的内容。命令:ls /

[zk: 127.0.0.1:2181(CONNECTED) 10] ls /
[zookeeper]

建立了一个新的 znode 节点 zk 以及与它关联的字符串。命令:create /zk myData

[zk: 127.0.0.1:2181(CONNECTED) 11] create /zk myData
Created /zk
[zk: 127.0.0.1:2181(CONNECTED) 12] ls /
[zk, zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 13]

获取 znode 节点 zk。命令:get /zk

[zk: 127.0.0.1:2181(CONNECTED) 13] get /zk
myData
cZxid = 0x400000008
ctime = Mon Jun 01 15:07:50 CST 2020
mZxid = 0x400000008
mtime = Mon Jun 01 15:07:50 CST 2020
pZxid = 0x400000008
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0

删除 znode 节点 zk。命令:delete /zk

[zk: 127.0.0.1:2181(CONNECTED) 14] delete /zk
[zk: 127.0.0.1:2181(CONNECTED) 15] ls /
[zookeeper]

因为篇幅有限,在接下来的文章中会根据上面提到的 ZooKeeper 应用场景逐一进行用代码进行实现。

你们能够直接从 GitHub 拉取项目,启动只须要两步:

1.从 GitHub 上面拉取项目。

2.在 ZooKeeper 文件夹中执行 docker-compose up 命令。

最后

我这边整理了一份:Zookeeper相关资料,Java核心知识点(包括Spring全家桶系列、面试专题和20年最新的互联网真题、电子书等)有须要的朋友能够关注公众号【程序媛小琬】便可获取。

相关文章
相关标签/搜索