Zookeeper集群搭建以及python操作zk
一、Zookeeper原理简介
转:https://www.cnblogs.com/xiao987334176/p/10103619.html
ZooKeeper是一个开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。
Zookeeper设计目的
- 最终一致性:client不论连接到那个Server,展示给它的都是同一个视图。
- 可靠性:具有简单、健壮、良好的性能、如果消息m被到一台服务器接收,那么消息m将被所有服务器接收。
- 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
- 等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
- 原子性:更新只能成功或者失败,没有中间状态。
- 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
Zookeeper工作原理
1、在zookeeper的集群中,各个节点共有下面3种角色和4种状态:
角色:leader,follower,observer
状态:leading,following,observing,looking
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议(ZooKeeper Atomic Broadcast protocol)。Zab协议有两种模式,它们分别是恢复模式(Recovery选主)和广播模式(Broadcast同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有4种状态:
LOOKING:当前Server不知道leader是谁,正在搜寻。
LEADING:当前Server即为选举出来的leader。
FOLLOWING:leader已经选举出来,当前Server与之同步。
OBSERVING:observer的行为在大多数情况下与follower完全一致,但是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果。
Zookeeper集群节点
- Zookeeper节点部署越多,服务的可靠性越高,建议部署奇数个节点,因为zookeeper集群是以宕机个数过半才会让整个集群宕机的。
- 需要给每个zookeeper 1G左右的内存,如果可能的话,最好有独立的磁盘,因为独立磁盘可以确保zookeeper是高性能的。如果你的集群负载很重,不要把zookeeper和RegionServer运行在同一台机器上面,就像DataNodes和TaskTrackers一样。
实验环境
操作系统 | docker镜像 | docker ip | zookeeper版本 |
ubuntu-16.04.5-server-amd64 | ubuntu:16.04 | 172.168.0.2 | 3.4.13 |
ubuntu-16.04.5-server-amd64 | ubuntu:16.04 | 172.168.0.2 | 3.4.13 |
ubuntu-16.04.5-server-amd64 | ubuntu:16.04 | 172.168.0.2 | 3.4.13 |
由于机器有限,本文在一台服务器上面,开启了3个docker容器。
使用网桥连接3个docker容器。这样,就可以模拟3台服务器了!
创建网桥
bridge有以下好处:
-
好的隔离性和互操作性:连到同一自定义的bridge的各个容器默认相互之间曝露所有端口,并且不对外部曝露
-
自动提供容器之间的DNS解析服务:连到同一自定义的bridge的各个容器不用做特殊DNS配置,可直接通过hostname访问
-
运行中容器联网配置:可对运行中的容器配置自定义或取消配置自定义bridge
-
bridge之间相互独立:用户可创建N多个bridge,且连接于不同的bridge之上的容器相互独立
创建自定义bridge,名字叫br1
docker network create --driver=bridge --subnet=172.168.0.0/16 br1
语法解释:
--driver=bridge 表示使用桥接模式
--subnet 表示网络地址
为容器配置静态IP,可以使用以下命令
docker run -it --network my-net --ip 172.18.0.250 imageID bash
Zookeeper核心要点
1. Zookeeper节点必须是奇数
2. 修改zoo.cfg
末尾增加3行参数。表示有3个zk节点!
server.X=A:B:C server.X=A:B:C server.X=A:B:C
官方解释
蹩脚翻译
大概意思
server.X=A:B:C X-代表服务器编号 A-代表ip B和C-代表端口,这个端口用来系统之间通信
3. 创建ServerID标识
除了修改zoo.cfg配置文件外,zookeeper集群模式下还要配置一个myid文件,这个文件需要放在dataDir目录下。
这个文件里面有一个数据就是A的值(该A就是zoo.cfg文件中server.A=B:C:D中的A),在zoo.cfg文件中配置的dataDir路径中创建myid文件
二、Zookeeper安装
Zookeeper运行需要java环境,需要安装jdk,注:每台服务器上面都需要安装zookeeper、jdk。
基于docker安装
新建空目录
mkdir /opt/zookeeper_cluster
dockerfile
FROM ubuntu:16.04 # 修改更新源为阿里云 ADD sources.list /etc/apt/sources.list ADD zookeeper-3.4.13.tar.gz / ADD zoo.cfg / # 安装jdk RUN apt-get update && apt-get install -y openjdk-8-jdk --allow-unauthenticated && apt-get clean all && \ cd /zookeeper-3.4.13 && \ mkdir data log && \ mv /zoo.cfg conf EXPOSE 2181 # 添加启动脚本 ADD run.sh . RUN chmod 755 run.sh ENTRYPOINT [ "/run.sh"]
run.sh
#!/bin/bash if [ -z "${SERVER_ID}" ];then echo "SERVER_ID 变量缺失" exit 1 fi if [ -z "${ZOOKEEPER_CONNECT}" ];then echo "ZOOKEEPER_CONNECT环境变量缺失" echo "比如: 192.168.1.1,192.168.1.2,192.168.1.3" exit 2 fi # 配置集群 # 写入server.X对应的X echo $SERVER_ID > /zookeeper-3.4.13/data/myid # 写入文件zoo.cfg id=0 # 初始值 hosts_array=(${ZOOKEEPER_CONNECT//,/ }) # 以逗号来切割,转换为数组 for ip in ${hosts_array[@]};do ((id++)) # id自增1 echo "server.$id=$ip:2888:3888" >> /zookeeper-3.4.13/conf/zoo.cfg done # 启动zookeeper cd /zookeeper-3.4.13/ bin/zkServer.sh start tail -f /zookeeper-3.4.13/conf/zoo.cfg
注意:此脚本需要2个变量,否则会直接退出。它会在zoo.cfg写入3行内容!
除了修改zoo.cfg配置文件外,zookeeper集群模式下还要配置一个myid文件,这个文件需要放在dataDir目录下
/zookeeper-3.4.13/data/myid 这个文件的数值,待会由docker启动时,传入进去。
sources.list
deb http://mirrors.aliyun.com/ubuntu/ xenial main deb-src http://mirrors.aliyun.com/ubuntu/ xenial main deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main deb http://mirrors.aliyun.com/ubuntu/ xenial universe deb-src http://mirrors.aliyun.com/ubuntu/ xenial universe deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates universe deb http://mirrors.aliyun.com/ubuntu/ xenial-security main deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security universe
zoo.cfg
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/zookeeper-3.4.13/data dataLogDir=/zookeeper-3.4.13/log clientPort=2181
initLimit 这个配置项是用来配置zookeeper接受客户端(这里所说的客户端不是用户连接zookeeper服务器的客户端,而是zookeeper服务器集群中连接到leader的follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。
syncLimit 这个配置项标识leader与follower之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime的时间长度,总的时间长度就是5*2000=10秒。
注意:上面的2行红色部分参数,一定要加。否则会导致启动zookeeper失败,因为启动时,它会尝试连接server.X=A:B:C。有多少个,都会连接。
如果连接失败数大于集群总数一半,会认为集群不可用。
那么配置这2个参数之后,就会有时间缓解一下。因为我不是闪电侠,我不能在一瞬间同时启动3个zookeeper。而且你还得确保,zookeeper检查其他节点时,其他节点运行是正常的!
因此,只要你在10秒内,启动3个zookeeper,就可以了!
此时,目录结构如下:
./ ├── dockerfile ├── run.sh ├── sources.list ├── zoo.cfg └── zookeeper-3.4.13.tar.gz
生成镜像
docker build -t zookeeper_cluster /opt/zookeeper_cluster
启动docker
在启动之前,请确保已经创建了网桥br1
启动第一个docker
docker run -it -e SERVER_ID=1 -e ZOOKEEPER_CONNECT=172.168.0.2,172.168.0.3,172.168.0.4 -p 2181:2181 --network br1 --ip=172.168.0.2 zookeeper_cluster
启动第二个docker
docker run -it -e SERVER_ID=2 -e ZOOKEEPER_CONNECT=172.168.0.2,172.168.0.3,172.168.0.4 -p 2182:2181 --network br1 --ip=172.168.0.3 zookeeper_cluster
启动第二个docker
docker run -it -e SERVER_ID=3 -e ZOOKEEPER_CONNECT=172.168.0.2,172.168.0.3,172.168.0.4 -p 2183:2181 --network br1 --ip=172.168.0.4 zookeeper_cluster
注意红色部分,是需要修改的,其他的参数都是一样的!
三、Zookeeper集群查看
查看每个节点状态
先来查看一下docker进程
root@jqb-node128:/opt/zookeeper_cluster# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 217e012c9566 3f3a8090dcb6 "/run.sh" 15 hours ago Up 15 hours 0.0.0.0:2183->2181/tcp gallant_golick 3b4861d2fef9 3f3a8090dcb6 "/run.sh" 15 hours ago Up 15 hours 0.0.0.0:2182->2181/tcp jovial_murdock ed91c1f973d2 3f3a8090dcb6 "/run.sh" 15 hours ago Up 15 hours 0.0.0.0:2181->2181/tcp dazzling_hamilton
查看每个节点状态,使用命令 zkServer.sh status 查看
root@jqb-node128:~# docker exec -it 217e012c9566 /zookeeper-3.4.13/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /zookeeper-3.4.13/bin/../conf/zoo.cfg Mode: follower root@jqb-node128:~# docker exec -it 3b4861d2fef9 /zookeeper-3.4.13/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /zookeeper-3.4.13/bin/../conf/zoo.cfg Mode: leader root@jqb-node128:~# docker exec -it ed91c1f973d2 /zookeeper-3.4.13/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /zookeeper-3.4.13/bin/../conf/zoo.cfg Mode: follower
可以发现,第二台,也就是id为3b4861d2fef9 的容器,就是leader模式。其它容器是follow模式
四、Zookeeper集群连接
Zookeeper集群搭建完毕之后,可以通过客户端脚本连接到zookeeper集群上面,对客户端来说,zookeeper集群是一个整体,连接到zookeeper集群实际上感觉在独享整个集群的服务。
指定第一台节点
docker exec -it 217e012c9566 /zookeeper-3.4.13/bin/zkCli.sh -server 172.168.0.2:2181
使用ls / 查看根节点,默认只有一个zookeeper节点。
WatchedEvent state:SyncConnected type:None path:null [zk: 172.168.0.2:2181(CONNECTED) 0] ls / [zookeeper] [zk: 172.168.0.2:2181(CONNECTED) 1]
五、使用python操作zookeeper
kazoo 介绍
zookeeper的开发接口以前主要以java和c为主,随着python项目越来越多的使用zookeeper作为分布式集群实现,python的zookeeper接口也出现了很多,现在主流的纯python的zookeeper接口是kazoo。因此如何使用kazoo开发基于python的分布式程序是必须掌握的。
安装kazoo
pip3 install kazoo
基本操作
from kazoo.client import KazooClient zk = KazooClient(hosts='192.168.91.128:2181') #如果是本地那就写127.0.0.1 zk.start() #与zookeeper连接 zk.stop() #与zookeeper断开
创建节点
from kazoo.client import KazooClient zk = KazooClient(hosts='192.168.91.128:2181') #如果是本地那就写127.0.0.1 zk.start() #与zookeeper连接 #makepath=True是递归创建,如果不加上中间那一段,就是建立一个空的节点 zk.create('/abc/JQK/XYZ/0001',b'this is my house',makepath=True) node = zk.get_children('/') # 查看根节点有多少个子节点 print(node) zk.stop() #与zookeeper断开
执行输出:
['abc', 'zookeeper']
注意:空节点的值不能用set修改,否则执行报错!
删除节点
如果要删除这个/abc/JQK/XYZ/0001的子node,但是想要上一级XYZ这个node还是存在的,语句如下:
from kazoo.client import KazooClient zk = KazooClient(hosts='192.168.91.128:2181') #如果是本地那就写127.0.0.1 zk.start() #与zookeeper连接 #recursive=True是递归删除,就是无视下面的节点是否是空,都干掉,不加上的话,会提示子节点非空,删除失败 zk.delete('/abc/JQK/XYZ/0001',recursive=True) node = zk.get_children('/') # 查看根节点有多少个子节点 print(node) zk.stop() #与zookeeper断开
执行输出:
['abc', 'zookeeper']
更改节点
现在假如要在0001这个node里更改value,比如改成:"this is my horse!",
由于上面节点已经被删除掉了,需要先创建一次。
语句如下:
from kazoo.client import KazooClient zk = KazooClient(hosts='192.168.91.128:2181') #如果是本地那就写127.0.0.1 zk.start() #与zookeeper连接 zk.create('/abc/JQK/XYZ/0001',b'this is my house',makepath=True) zk.set('/abc/JQK/XYZ/0001',b"this is my horse!") node = zk.get('/abc/JQK/XYZ/0001') # 查看值 print(node) zk.stop() #与zookeeper断开
执行输出:
(b'this is my horse!', ZnodeStat(czxid=4294967449, mzxid=4294967452, ctime=1544598301273, mtime=1544598308267, version=1, cversion=0, aversion=0, ephemeralOwner=0, dataLength=17, numChildren=0, pzxid=4294967449))
注意!set这种增加节点内容的方式是覆盖式增加,并不是在原有基础上增添。而且添加中文的话可能在ZooInspecter里出现的是乱码
查看节点
由于所有节点,都是在/ 节点上面的,直接查看根节点,就可以知道所有节点了
from kazoo.client import KazooClient zk = KazooClient(hosts='192.168.91.128:2181') #如果是本地那就写127.0.0.1 zk.start() #与zookeeper连接 node = zk.get_children('/') print(node) zk.stop() #与zookeeper断开
执行输出:
['abc', 'zookeeper']
一键清空zookeeper
有些时候,需要将zookeeper的数据全部清空,可以使用以下代码
from kazoo.client import KazooClient zk = KazooClient(hosts='192.168.91.128:2181') #如果是本地那就写127.0.0.1 zk.start() #与zookeeper连接 jiedian = zk.get_children('/') # 查看根节点有多少个子节点 print(jiedian) for i in jiedian: if i != 'zookeeper': # 判断不等于zookeeper print(i) # 删除节点 zk.delete('/%s'%i,recursive=True) zk.stop() #与zookeeper断开