分布式专题(4)- Zookeeper

本篇一句话总结:ZooKeeper是一种分布式协调服务,可以实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能。

正文开始:

  • 什么是Zookeeper?
  • 为什么选择Zookeeper?
  • 怎么用Zookeeper?

       上面这几个问题,是每个刚接触Zookeeper的人都想知道的。下面小兵综合自己的理解和使用情况,在分布式专题里总结一篇关于Zookeeper的内容。全文看完,我们对Zookeeper也有一定的了解了。

 

什么是Zookeeper?

总结一句话,就是:

ZooKeeper是一种分布式协调服务,可以实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能。

从这句话我们可以知道 Zookeeper 的基本定位是作为一个分布式服务协调框架,能够提供配置管理、统一命名服务、转态同步服务、管理集群、分布式锁和分布式队列等功能。

 

为什么选择Zookeeper?

在讲《分布式专题(2)- 分布式 Java通信》时我们说过,所谓分布式,无非就是“将一个系统拆分成多个子系统并散布到不同设备”的过程而已。在微服务的大潮之中, 我们把系统拆分成了多个服务,根据需要部署在多个机器上,这些服务非常灵活,单个或几个系统的故障不会使整个系统出现故障。并且可以随着访问量弹性扩展,能够持续不间断地提供服务。

分布式专题(4)- Zookeeper_第1张图片 一个基本的分布式应用

虽然分布式应用给我们带来了诸多好处,但拆分系统的同时也带来了巨大的复杂性,我们要写一个分布式应用还是非常困难的,如需要处理分布式session、分布式跨域、分布式任务调度、分布式事务、分布式锁、分布式局部故障等问题。如局部故障问题,一个消息通过网络在两个节点之间传递时,网络如果发生故障,发送方并不知道接收方是否接收到了这个消息。他可能在网络故障前就收到了此消息,也可能没有收到,又或者可能接收方的进程死了。发送方了解情况的唯一方法就是再次连接接收方,并向他进行询问。这就是局部故障:根本不知道操作是否失败。因此,大部分分布式应用需要一个主控、协调控制器来管理物理分布的子进程。目前,大部分应用需要开发私有的协调程序,缺乏一个通用的机制。协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器。协调服务非常容易出错,并很难从故障中恢复。例如:协调服务很容易处于竞态甚至死锁。Zookeeper的设计目的,就是为了减轻分布式应用程序所承担的协调任务。

Zookeeper并不能阻止局部故障的发生,因为它们的本质是分布式系统。他当然也不会隐藏局部故障。ZooKeeper的目的就是提供一些工具集,用来建立安全处理局部故障的分布式应用。

ZooKeeper是一个分布式小文件系统,并且被设计为高可用性(数据保持在内存中)。通过选举算法和集群复制可以避免单点故障,由于是文件系统,所以即使所有的ZooKeeper节点全部挂掉,数据也不会丢失,重启服务器之后,数据即可恢复。另外ZooKeeper的节点更新是原子的,也就是说更新不是成功就是失败。通过版本号,ZooKeeper实现了更新的乐观锁,当版本号不相符时,则表示待更新的节点已经被其他客户端提前更新了,而当前的整个更新操作将全部失败。当然所有的一切ZooKeeper已经为开发者提供了保障,我们需要做的只是调用API。与此同时,随着分布式应用的的不断深入,需要对集群管理逐步透明化监控集群和作业状态,可以充分利ZK的独有特性。

总结上面的几段话,得出选择使用Zookeeper的原因就是:

  1. 大部分分布式应用需要一个主控、协调控制器来管理物理分布的子进程;
  2. 大部分分布式应用需要开发私有的协调程序,缺乏一个通用的机制;
  3. 协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器;
  4. ZooKeeper提供了通用且高可用的分布式服务,用以协调分布式应用

所以我们说ZooKeeper是一种分布式协调服务,重在协调二字。下面,我们先把Zookeeper下载回来安装,结合实例,一起来认识和理解“ZooKeeper是一种分布式协调服务,可以实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能”的含义。

 

Zookeeper安装

一、下载Zookeeper包(本篇以zookeeper-3.4.10为例)

可以自己前往官网下载,也可以在百度云(https://pan.baidu.com/s/1rzp2U2-yE3BXsD-akJcxzw  提取码:basa )下载。

二、提取tar文件

# tar -zxvf zookeeper-3.4.10.tar.gz
# cd zookeeper-3.4.10

三、重命名配置文件zoo_sample.cfg

cp conf/zoo_sample.cfg conf/zoo.cfg

四、启动zookeeper

bin/zkServer.sh start

五:检测是否成功启动,用zookeeper客户端连接下服务端

bin/zkCli.sh

出现 Welcome to ZooKeeper! 字样即启动成功。

(zkCli需要依赖JAVA_HOME的环境变量,如果没有,需要在 /etc/profile 中添加) 

另外:

  • zookeeper的初始配置文件zoo_sample.cfg中各配置项意义如下:
# tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间(ms)就会发送一个心跳
tickTime=2000  
# dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
dataDir=/tmp/zookeeper
# clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
clientPort=2181
# 集群中的follower服务器(F)与leader服务器(L)之间 初始连接 时能容忍的最多心跳数(tickTime的数量),当超过设置倍数的 tickTime 时间,则连接失败。
initLimit=10
# 集群中的follower服务器(F)与leader服务器(L)之间 请求和应答 之间能容忍的最多心跳数(tickTime的数量)。
syncLimit=5
  • zookeeper的服务端、客户端命令相关参数如下
zkServer.sh {start|start-foreground|stop|restart|status|upgrade|print-cmd}
zkCli.sh -server  ip:port

 

Zookeeper数据模型

(此部分参考博客:Zookeeper入门看这篇就够了)

ZooKeeper是一种分布式协调服务,可以实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能。具体是通过它独特的数据结构来实现的,我们可以认为 Zookeeper = 文件系统+监听通知机制。下面我们看一下它的数据结构:

Zookeeper数据结构

 

文件系统

在Zookeeper的数据结构中,每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。

有持久、持久有序、临时、临时有序四种类型的znode:

  • PERSISTENT(持久节点):客户端与zookeeper断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL(持久有序节点):客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL(临时节点):客户端与zookeeper断开连接后,该节点被删除
  • EPHEMERAL_SEQUENTIAL(临时有序节点):客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

通知机制

zookeeper客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

我们说 Zookeeper = 文件系统+监听通知机制,好像看起来挺简单的啊(E=mc²看起来也挺简单的),它怎么就能通过这么简单两点东西,实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理等功能呢?我们看一个它实现配置管理功能的例子,就大概知道它是怎么实现其它功能了。

配置管理

假设我们的程序是分布式部署在多台机器上,如果我们要改变程序的配置文件,需要逐台机器去修改,非常麻烦,现在把这些配置全部放到zookeeper上去,保存在 zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 zookeeper 的通知,然后从 zookeeper 获取新的配置信息应用到系统中。

分布式专题(4)- Zookeeper_第2张图片

是不是通过配置管理这个例子,对Zookeeper的了解就更清晰了?还有另外一个经典用法,如下

如上图所示,系统A发送一个请求到MQ,然后系统B消费消息之后处理了。那系统A如何知道系统B的处理结果?

用ZK就可实现分布式系统之间的协调工作!

系统A发送请求之后可以在ZK上对某个节点的值注册监听器,一旦系统B处理完了就修改ZK那个节点的值,A立马就可以收到通知。

通过上面这两个例子,我们对 “Zookeeper = 文件系统+监听通知机制,ZooKeeper是一种分布式协调服务,可以实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能”可以有更形象地理解。

下面我们就看看Zookeeper一些基本命令,如节点的增删改查等,然后通过Zookeeper提供的API,一起用Java代码实现配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能。

 

Zookeeper基本命令

Zookeeper是用来解决分布式应用中经常遇到的数据管理问题,对于数据,那基础的无非都是增删改查。下面我们启动zookeeper服务端,然后启动zookeeper客户端,一起敲一下它增删改查的命令。

zookeeper基本的五个命令为 ls 、 create 、 delete 、 set 、get 。输入-h可以查看所有命令,相关命令示例可参考这里。

下面开始使用

1、使用 ls 命令来查看当前 ZooKeeper 中所包含的内容(默认只有zookeeper一个节点)

2、创建一个新的 znode ,使用 create /zkPro myData

3、再次使用 ls 命令来查看现在 zookeeper 中所包含的内容:

4、下面我们运行 get 命令来确认第二步中所创建的 znode 是否包含我们所创建的字符串:

5、下面我们通过 set 命令来对 zk 所关联的字符串进行设置:

分布式专题(4)- Zookeeper_第3张图片

6、下面我们将刚才创建的 znode 删除

可以看到,客户端中对节点进行增删改查的命令还是非常简单的。下面看一下在Java代码中如何实现。

Zookeeper提供Java API

1.通过maven引入zookeeper API的jar包


    org.apache.zookeeper
    zookeeper
    3.4.10

2.启动zookeeper服务端,客户端,并查看节点信息(此处我把2181改成了12181端口)

zkServer.sh start
zkCli.sh -server localhost:12181
ls /

3.通过Java API获取Zookeeper对象并查看节点信息。

(初始化zk客户端时,可以看到需要输入一个sessionTimeout时间,这个会话超时时间也是有点讲究的。)

执行后可得结果:

可以看到,Zookeeper API中对节点进行增删改查的命令还是非常简单的。Zookeeper客户端提供了基本的操作,比如,创建会话、创建节点、读取节点、更新数据、删除节点和检查节点是否存在等。但对于我们开发人员来说,Zookeeper提供的基本操纵还是有一些不足之处,因此,另外两款开源框架ZKClient和Curator又被人们开发了出来,此处更推荐使用Curator。

 

Zookeeper集群

以上的讲解中都是基于单机模式,这种模式下,如果当前主机宕机,那么所有依赖于当前zookeeper服务工作的其他服务器都不能在进行正常工作,这种事件称为单节点故障。所以这种模式一般用在测试环境。为了防止这种情况出现,生产环境一般都是会在多台机器上配置zookeeper来组成集群,以实现容错和高可用。集群就涉及到数据一致性的问题,即集群中的的数据都应该要保持同步。为此,zookeeper集群引入了Leader、Follower的概念。即对来自于Client的写服务(Write Requst),都会被转发到Leader节点来处理,Leader节点会对这次的更新发起投票,并且发送提议消息给集群中的其他节点,当半数以上的Follower节点将本次修改持久化成功之后,Leader 节点会认为这次写请求处理成功了,提交本次的事务。而来自于Client的读服务(Read Requst),是直接由对应Server的本地副本来进行服务的。具体可以参考Zookeeper一致性原理(ZAB协议),另外此处补充几个著名的分布式理论。

zookeeper搭建集群也很简单,只需要简单的几步即可完成。

(在一台机器上搭建多个zookeeper就是俗称的伪集群了,资源有限,此处模拟搭建的是伪集群)

1.修改zoo.cfg配置文件

在conf下将zoo_sample.cfg复制为zoo-1.cfg并修改内容如下(其它配置不用动,只修改目录和端口,并添加集群信息):

dataDir=/tmp/zookeeper-1
clientPort=2181
server.1=127.0.0.1:12181:13181
server.2=127.0.0.1:12182:13182
server.3=127.0.0.1:12183:13183

对比单机模式,配置zk集群时配置文件只需要添加一句:server.id=ip:port1:port2 即可,配置3条即指集群中有3个zk。

可以看到zookeeper的配置中有三个端口,其作用如下:

  • clientPort:表示与服务端与 clinet 端交换信息的端口号;
  • port1:表示follower节点与leader节点交换信息的端口号;
  • port2:表示leader节点挂掉了, 来重新选举leader需要的端口号。

2.复制zoo-1.cfg配置文件

再从zoo-1.cfg复制两个配置文件zoo-2.cfg和zoo-3.cfg,只需修改dataDir和clientPort不同即可,其它配置保存不变。

# zoo2.cfg中的目录和clientPort:
dataDir=/tmp/zookeeper-2
clientPort=2182

# zoo3.cfg中的目录和clientPort:
dataDir=/tmp/zookeeper-3
clientPort=2183

3.创建dataDir目录和标识server.id

每个人都有自己的身份证,每台电脑都有自己的Mac地址,每个zookeeper也有自己唯一标识,就是 server.id 中的id,这个值需要记录在 dataDir 的 myid 文件中。所以我们要为集群中的zookeeper创建这个myid文件,内容就是我们zoo.cfg中配置的id。

# 创建zoo-1.cfg对应的目录及server.id
mkdir /tmp/zookeeper-1
vim /tmp/zookeeper-1/myid
1

# 创建zoo-2.cfg对应的目录及server.id
mkdir /tmp/zookeeper-2
vim /tmp/zookeeper-2/myid
2

# 创建zoo-3.cfg对应的目录及server.id
mkdir /tmp/zookeeper-3
vim /tmp/zookeeper-3/myid
3

4.启动集群实例

bin/zkServer.sh start conf/zoo-1.cfg
bin/zkServer.sh start conf/zoo-2.cfg
bin/zkServer.sh start conf/zoo-3.cfg

5.检测集群状态

bin/zkServer.sh status conf/zoo-1.cfg
bin/zkServer.sh status conf/zoo-2.cfg
bin/zkServer.sh status conf/zoo-3.cfg

可以看到,有一台leader、两台作为follower,集群启动成功。选举过程可以参看这里。

  • 可以通过多个客户端连接集群中不同的机器,并修改其中一台机器上的数据,观察数据是否会同步更新到其它机器上
  • 可以通过安装zookeeper图形化界面来更方便地观察zk的节点信息和对zk节点进行增删改查

终于把zookeeper的基本使用和集群讲完了。接下来,我们就一起再次理解“ZooKeeper是一种分布式协调服务,可以实现分布式应用配置管理、统一命名服务、状态同步服务、集群管理、分布式锁和分布式队列等功能”这句话

 

zk实现分布式应用配置管理

大型应用通常会按业务拆分成一个个业务子系统,这些大大小小的子应用,往往会使用一些公用的资源,比如:需要文件上传、下载时,各子应用都会访问公用的Ftp服务器。如果把Ftp Server的连接IP、端口号、用户名、密码等信息,配置在各子应用中,然后这些子应用再部署到服务器集群中的N台Server上,突然有一天,Ftp服务器要换IP或端口号,那么问题来了?如何快速的把这一堆已经在线上运行的子应用,通通换掉相应的配置,而且还不能停机?

传送门:ZooKeeper 统一配置管理
 

zk实现统一命名服务

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

 

zk实现状态同步服务

选完Leader以后,zk就进入状态同步过程。
1. Leader等待其它server连接;
2 .Follower连接leader,将最大的zxid发送给leader; 
3 .Leader根据follower的zxid确定同步点; 
4 .完成同步后通知follower 已经成为uptodate状态; 
5 .Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

分布式专题(4)- Zookeeper_第4张图片

 

zk实现集群管理

Zookeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。

所以集群管理无在乎两点:是否有机器退出和加入、选举master。 对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它删除了。新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了。对于第二点“选举master”,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。(即当这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。)

分布式专题(4)- Zookeeper_第5张图片 集群管理结构图

 

zk实现分布式锁

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

 

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

zk实现分布式队列

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

第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在 /queue 这个znode下预先建立一个/queue/num 节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。

 

zk实现负载均衡

把ZooKeeper作为一个服务的注册中心,在其中登记每个服务,每台服务器知道自己是属于哪个服务,在服务器启动时,自己向所属服务进行登记,这样,一个树形的服务结构就呈现出来了

分布式专题(4)- Zookeeper_第6张图片

服务的调用者到注册中心里面查找:能提供所需服务的服务器列表,然后自己根据负载均衡算法,从中选取一台服务器进行连接

调用者取到服务器列表后,就可以缓存到自己内部,省得下次再取,当服务器列表发生变化,例如某台服务器宕机下线,或者新加了服务器,ZooKeeper会自动通知调用者重新获取服务器列表

由于ZooKeeper并没有内置负载均衡策略,需要调用者自己实现,这个方案只是利用了ZooKeeper的树形数据结构、watcher机制等特性,把ZooKeeper作为服务的注册和变更通知中心。常用的负载均衡算法有:轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接数法

你可能感兴趣的:(分布式专题)