s先解释一下zookeeper的作用和原理,明白它是用来干嘛的以及它是通过怎样的方式去实现它的功能的之后再去考虑是否选择这一款服务以及如何搭建这个服务。
zookeeper是用来干嘛的
首先,zookeeper是一个分布式协调服务。那什么是分布式协调呢?所谓分布式协凋主要是用来解决分布式系统中多个进程之间的数据管理问题的,例如统一命名服务、统一配置管理、统一集群管理、分布式锁等。
举个例子。假设小何想找A团队实现一个需求,小何直接找A团队的leader小王,因为小王可以管理A团队的成员,也知道A团队里面大大小小的事,然后小王协助安排了合适的人帮小何完成了需求。那么这里的小王,实际上承担了团队里面协调的任务。那我们假设小王不在了,会出现什么问题?假设一个团队三个员工s1、s2、s3(一个集群下三个服务)
这个时候会出现几个问题:
1. s1员工换了办公地点(换了ip),防止其他员工找不到自己,怎么同步给其他员工呢?(每个服务都有一个配置文件,配置文件中信息动态改变,如何保证各节点数据一致性)
2.来了一个新的需求,怎么分配给某个员工做?(保证一个任务只在一个服务执行)
3.员工s2辞职了,怎么通知其他员工,并把工作交接给其他员工?(某个服务下线了,怎么通知其他服务并接替它的任务)
4.三个员工做任务的时候,都涉及一个公共文件,怎么保证几个员工在看这个文件的时候,没有被另一个人修改?(保证节点访问共享资源的互斥性和安全性)
这里就体现出来leader小王承担的分布式协调的工作了。
了解zookeeper的作用之后,接下来介绍一下zookeeper为了完成分布式协调这个工作,它是如何实现的。
zookeeper是怎么实现分布式协调的
将zookeeper看作一个整体,一个zookeeper的集群和外部服务的关系大概是这样子的。这里有几个特点:
- 最终一致性:client 不论连接到哪个 server,展示给它都是同一个视图,这是 zookeeper 最重要的性能。
- 可靠性:具有简单、健壮、良好的性能,如果消息 m 被一台服务器接受,那么它将被所有的服务器接受。
- 实时性:ZooKeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,ZooKeeper 不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync()接口。
- 等待无关(wait-free):慢的或者失效的 client 不得干预快速的 client 的请求,使得每个 client 都能有效的等待。
- 原子性:更新只能成功或者失败,没有中间状态。
- 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 server 上消息 a 都将在消息 b 前被发布;偏序是指如果一个消息 b 在 消息 a 后被同一个发送者发布,a 必将排在 b 前面。
接下来看看zookeeper的数据模型,他这个是一个有层级关系的数据结构,类似于一个文件系统。
实际上我们通过api操作zookeeper实际上就是对znode的操作。
这里介绍一下这个模型:
- 每个子目录都被称为znode,这个znode的唯一标识是它的路径,server1这个目录就是/module1/server1。
- znode除了可以有子节点外还可以存储数据。但是需要区分类型,ephemeral(临时的)类型的目录节点就不能有子节点目录。
- znode和znode中存储的数据都是有版本的(version),一个访问路径中可以存储多份数据,version号自动增加。
- znode有4种类型:
- persistent;客户端断开连接,节点不会被删除;
- ephemeral;客户端断开连接,节点立刻被删除;
- non-sequence;节点名称没有序号区分,多个客户端创建同名节点,只有一个成功,其余均失败。
- sequence;创建出的节点名称在名称后面带有10位10进制的序号。多个客户端创建同一名称的节点时,都能创建成功,但是序号不同。
- znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是zookeeper的核心特性,zookeeper很多功能都是基于这个特性实现的。可以支持响应式编程模式,它可以对某个路径的终节点及其子节点的变更进行监视,当其发生变更以后,会调用注册的callback方法,然后进行具体的业务逻辑。例如监测路径为/module1/server1,那么它会加测server1节点,以及附属于server1的所有子节点,这个子不单单只一层子节点,是指所有层的子节点。
- zxid,每次对zookeeper的状态的改变,即使操作的是不同的znode,都会产生一个zxid(zookeeper transaction id),zxid是全局有序的,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
知道了zookeeper是怎么实现之后,接下来介绍一下zookeeper集群的原理。
zookeeper集群的原理
1. zookeeper集群角色
zookeeper集群模式是主备模式,里面有三种角色,分别是leader、follower、observer。
- leader:事务请求的唯一调度者和执行者,保证请求的顺序执行,集群内部各个服务的调用者。
- follower:用来处理非事务请求,并将事务请求发送给leader。参与事务的投票和选举的投票。
- observer:用来处理非事务请求,并将事务请求发送给leader。观察节点的最新数据并进行同步。与follower的区别就是不参与事务的投票和选举的投票。
2. zookeeper事务操作
- zookeeper事务请求指修改数据的操作,集群中所有事务操作全部都是由leader完成的,不管是follower还是observer在接收到事务请求后都会转发到leader上,再由leader统一进行操作。
- leader会为集群中每一个follower建立一个队列,leader在接收到follower给它的事务请求之后,会将该事务请求转化为一个proposal,并将该proposal通过队列发送到集群上的所有follower上。
- 每一个follower在接收到proposal之后都会回复leader,当leader接收到一半以上的follower的回复,就会再一次发送一个commit指令给所有follower,告诉他们将上一个proposal进行提交并写入本地。
- 当leader接收到超过半数follower同步完成的消息之后,会进行数据同步,将数据同步到follower和observer上。至此,整个事务请求操作完成。
注:在这个阶段上面会发生两个问题导致集群数据不一致,而面对两种不一致会有两个原则
1. leader收到过半数follower回复之后,会下发commit指令,但是在发送commit指令之后,在所有follower收到之前,leader挂了。那这个时候将会有一些follower收到,有一些follower没收到。这个时候若follower1将事务成功的消息发送给客户端了,那么这条被确认成功的消息就必须同步到集群中的所有机器上,因为客户端已经收到了成功的消息了,所以即便其他follower没有收到commit指令,也必须设法告诉其他follower这条消息保存成功。因此已经被处理的消息不能丢失。
2. 当leader收到了事务请求之后,生成proposal之后挂掉了,这个时候集群里面的follower还没收到commit指令。当leader挂掉之后会进行重新选举,那么新的leader是不知道挂掉的那个proposal的存在的。那么此时为了保证集群中数据的一致性,该proposal必须被丢弃。也就是说该proposal的事务操作是必须要失败的。所以被丢弃的消息不能再次出现。
为了保证以上两个原则,所以使用了zab协议来保障数据的一致性,zab协议我们放后面说。
3. zookeeper消息有序性
- leader在接收到事务请求后,都会创建一个新的proposal,同时会为该proposal生成一个全局唯一递增的事务id,也就是zookeeper transaction id(zxid)。由于zab协议保证执行的顺序性,所以所有的事务会根据zxid的大小来进行顺序执行。保证了集群事务的顺序性问题。
- zxid是一个64位的数据,其高32位是epoch编号(每个leader任期的代号,每投票一轮,这个数值就会增加),也就是通过epoch编号来判断当前谁是leader,每次重新选举后都会重新生成一个epoch(++1这样)。该编号低32位是一个消息计数器,每收到一条消息都会+1。
4. zab协议(zookeeper atomic broadcast protocol)
zab协议包括两种基本模式,分别是消息广播和崩溃恢复。
- 消息广播;zab协议使用的消息广播模式类似于二阶段提交。相当于原子广播协议。针对事务请求生成一个proposal,在将proposal广播到所有follower上。一旦超过半数接收到并进行回复就相当于该集群全部接收到提议。在使用广播形式告诉所有跟随者进行commit。一旦接收到消息的follower不足一半时,leader就会将该事务修改成失败。用来保证集群数据的一致性。
- 崩溃恢复;当leader出现崩溃退出或服务宕机,又或者有一半以上的follower连不上leader了。此时集群就会进入崩溃恢复模式。此时需要重新选举一个新的leader。而leader选举是根据zxid来进行选择的。最大的zxid会成为leader。当leader选举出来以后,会通知所有的follower,告诉他们谁是leader,并进行数据同步。当超过半数的follower回复收到leader确认的消息后,会自动退出奔溃回复模式,进入到消息广播模式进行消息同步。
原理了解完了,接下来到了搭建环节。
zookeeper集群的搭建
上一篇集群系列已经介绍过怎么搭建dns了,这里zookeeper集群的搭建配置里面的host全部用域名代替,有关dns解析请查看上一篇文章。
这次我们用server02、server03、server04的机器做zookeeper集群的搭建。首先我们先新建文件夹zookeeper-cluster,我们zookeeper的docker-compose文件会放在文件夹里面,这次我们依然是同时操作3个服务器,这样省时省力。这里提一点,其实docker compose搭建服务,完全可以自己上dockerhub(hub.docker.com)去看的,上面有完整的使用示例以及每一个环境属性的作用。
在写配置文件之前,我习惯是先创建好每一个文件夹,而不是等他自动创建,这里我先创建好我需要用到的文件夹
然后在zookeeper-cluster文件夹内创建docker-compose.yaml文件,文件内容如下
server02:
version: '3'
services:
zookeeper:
image: zookeeper:3.8.0
restart: always
container_name: zookeeper
user: 1001:1001
ports:
- 2181:2181
- 2888:2888
- 3888:3888
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;0.0.0.0:2181 server.2=xdeas-server03:2888:3888;xdeas-server03:2181 server.3=xdeas-server04:2888:3888;xdeas-server04:2181
ZOO_CFG_EXTRA: quorumListenOnAllIPs=true
volumes:
- "/etc/localtime:/etc/localtime"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/data:/data"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/datalog:/datalog"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/logs:/logs"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/conf:/conf"
server03:
version: '3'
services:
zookeeper:
image: zookeeper:3.8.0
restart: always
container_name: zookeeper
user: 1001:1001
ports:
- 2181:2181
- 2888:2888
- 3888:3888
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=xdeas-server02:2888:3888;xdeas-server02:2181 server.2=0.0.0.0:2888:3888;0.0.0.0:2181 server.3=xdeas-server04:2888:3888;xdeas-server04:2181
ZOO_CFG_EXTRA: quorumListenOnAllIPs=true
volumes:
- "/etc/localtime:/etc/localtime"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/data:/data"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/datalog:/datalog"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/logs:/logs"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/conf:/conf"
server04:
version: '3'
services:
zookeeper:
image: zookeeper:3.8.0
restart: always
container_name: zookeeper
user: 1001:1001
ports:
- 2181:2181
- 2888:2888
- 3888:3888
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=xdeas-server02:2888:3888;xdeas-server02:2181 server.2=xdeas-server03:2888:3888;xdeas-server03:2181 server.3=0.0.0.0:2888:3888;0.0.0.0:2181
ZOO_CFG_EXTRA: quorumListenOnAllIPs=true
volumes:
- "/etc/localtime:/etc/localtime"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/data:/data"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/datalog:/datalog"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/logs:/logs"
- "/data/deploy/xdeas-docker/zookeeper-cluster/volumes/conf:/conf"
文件夹路径根据个人不同情况调整,如果是1台机器内,可以在zookeeper节点下,加上hostname节点然后用zoo_my_id区分开,然后zoo_servers节点后面的内容就写你的server.x=[你的hostname]:2888:3888:2181就可以了。我这里就因为是3台服务器,所以就直接写我的服务器的hostname了。然后比较重要的一点就是,ZOO_SERVERS这个属性表示的是集群的配置,server.x,那个x代表的是你的ZOO_MY_ID,是需要对应的,然后本机也是需要包含在里面的,但是本机需要写ip 0.0.0.0,否则leader选举的监听会报错。
最后在zookeeper-cluster文件夹下运行docker-compose up -d就可以了。没有镜像的它会自己下载的。如果因为配置问题运行失败,记得conf文件夹下是已经将zoo.cfg挂载出来了,即使down掉这个也是不会删掉了,下次再up如果有这个配置文件会直接读取的,所以如果因为配置运行失败记得把conf和data里的文件先删掉再重新运行。运行后可以docker logs -f [容器名] 看一下日志。
最后 docker ps 看一下是否都有在运行
然后 docker exec -ti [你的容器名] /bin/bash zkServer.sh status 检查一下,
我这里容器名是zookeeper,那运行 docker exec -ti zookeeper /bin/bash zkServer.sh status 检查一下就可以了。
可以看见我这边是server02、server03是follower,server04是leader。
至此,原理以及搭建分享完毕,切勿直接复制粘贴,里面的配置请根据个人情况稍作修改。