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有以下好处:

  1. 好的隔离性和互操作性:连到同一自定义的bridge的各个容器默认相互之间曝露所有端口,并且不对外部曝露

  2. 自动提供容器之间的DNS解析服务:连到同一自定义的bridge的各个容器不用做特殊DNS配置,可直接通过hostname访问

  3. 运行中容器联网配置:可对运行中的容器配置自定义或取消配置自定义bridge

  4. 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

 

官方解释

 View Code

 

蹩脚翻译

 View Code

 

大概意思

复制代码
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断开


posted on 2019-03-19 18:33  WapN  阅读(326)  评论(0编辑  收藏  举报

导航