1. zookeeper安装部署
1.1 系统环境及安装版本
系统环境:centos 7.8
安装版本:ZooKeeper CLI version: 3.6.2–803c7f1a12f85978cb049af5e4ef23bd8b688715, built on 09/04/2020 12:44 GMT
1.2 集群模式
单机模式:stand-clone
集群模式:多机多server
伪集群模式:单机多个server
1.3 安装部署单个节点
- 下载软件包,配置jdk:
#wget https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz
gunzi apache-zookeeper-3.6.2-bin.tar.gz
tar xf apache-zookeeper-3.6.2-bin.tar
cd apache-zookeeper-3.6.2-bin
cp zoo_sample.cfg zoo.cfg # 复制一组配置文件,zookeeper在启动之前默认是加载的zoo.cfg配置文件
# vim zoo.cfg
# The number of milliseconds of each tick
tickTime=2000 // 服务器心跳检测时间
# The number of ticks that the initial
# synchronization phase can take
initLimit=10 //投票选举leader的初始化时间
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5 // leader监听不到follower的心跳,从集群中将follower移除的时间
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/opt/zookeeper //数据目录
# the port at which the clients will connect
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
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
# 配置jdk
下载jdk:https://www.oracle.com/cn/java/technologies/javase-downloads.html
解压等 略
配置JAVA_HOME
# cat /etc/profile
export JAVA_HOME=/usr/local/jdk-14.0.2
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
export CLASSPATH=.:$JAVA_HOME/lib
# JAVA_HOME: jdk软件包的路径
# PATH:是指命令搜索路径
# CLASSPATH:是指类搜索路径
[root@oracle ~]# java -version
openjdk version "14.0.2" 2020-07-14
OpenJDK Runtime Environment (build 14.0.2+12-46)
OpenJDK 64-Bit Server VM (build 14.0.2+12-46, mixed mode, sharing)
- 启动zookeeper
[root@localhost apache-zookeeper-3.6.2-bin]# cd bin/
[root@localhost bin]# ls
README.txt zkCli.cmd zkEnv.cmd zkServer.cmd zkServer.sh zkSnapShotToolkit.sh zkTxnLogToolkit.sh
zkCleanup.sh zkCli.sh zkEnv.sh zkServer-initialize.sh zkSnapShotToolkit.cmd zkTxnLogToolkit.cmd
[root@localhost bin]# ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /opt/apache-zookeeper-3.6.2-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
# 进入命令行
[root@localhost bin]# ./zkCli.sh
Connecting to localhost:2181
2020-09-21 18:42:22,190 [myid:] - INFO [main:Environment@98] - Client environment:zookeeper.version=3.6.2--803c7f1a12f85978cb049af5e4ef23bd8b688715, built on 09/04/2020 12:44 GMT
2020-09-21 18:42:22,191 [myid:] - INFO [main:Environment@98] - Client environment:host.name=localhost
2020-09-21 18:42:22,192 [myid:] - INFO [main:Environment@98] - Client environment:java.version=14.0.2
2020-09-21 18:42:22,192 [myid:] - INFO [main:Environment@98] - Client environment:java.vendor=Oracle Corporation
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:java.home=/usr/local/jdk-14.0.2
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:java.class.path=/opt/apache-zookeeper-3.6.2-bin/bin/../zookeeper-server/target/classes:/opt/apache-zookeeper-3.6.2-bin/bin/../build/classes:/opt/apache-zookeeper-3.6.2-bin/bin/../zookeeper-server/target/lib/*.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../build/lib/*.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/zookeeper-prometheus-metrics-3.6.2.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/zookeeper-jute-3.6.2.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/zookeeper-3.6.2.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/snappy-java-1.1.7.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/slf4j-log4j12-1.7.25.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/slf4j-api-1.7.25.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/simpleclient_servlet-0.6.0.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/simpleclient_hotspot-0.6.0.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/simpleclient_common-0.6.0.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/simpleclient-0.6.0.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-transport-native-unix-common-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-transport-native-epoll-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-transport-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-resolver-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-handler-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-common-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-codec-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/netty-buffer-4.1.50.Final.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/metrics-core-3.2.5.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/log4j-1.2.17.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/json-simple-1.1.1.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jline-2.14.6.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jetty-util-9.4.24.v20191120.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jetty-servlet-9.4.24.v20191120.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jetty-server-9.4.24.v20191120.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jetty-security-9.4.24.v20191120.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jetty-io-9.4.24.v20191120.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jetty-http-9.4.24.v20191120.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/javax.servlet-api-3.1.0.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jackson-databind-2.10.3.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jackson-core-2.10.3.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/jackson-annotations-2.10.3.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/commons-lang-2.6.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/commons-cli-1.2.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../lib/audience-annotations-0.5.0.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../zookeeper-*.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../zookeeper-server/src/main/resources/lib/*.jar:/opt/apache-zookeeper-3.6.2-bin/bin/../conf:.:/usr/local/jdk-14.0.2/lib
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:java.library.path=/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:java.io.tmpdir=/tmp
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:java.compiler=<NA>
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:os.name=Linux
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:os.arch=amd64
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:os.version=3.10.0-1062.el7.x86_64
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:user.name=root
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:user.home=/root
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:user.dir=/opt/apache-zookeeper-3.6.2-bin/bin
2020-09-21 18:42:22,193 [myid:] - INFO [main:Environment@98] - Client environment:os.memory.free=11MB
2020-09-21 18:42:22,194 [myid:] - INFO [main:Environment@98] - Client environment:os.memory.max=247MB
2020-09-21 18:42:22,195 [myid:] - INFO [main:Environment@98] - Client environment:os.memory.total=15MB
2020-09-21 18:42:22,204 [myid:] - INFO [main:ZooKeeper@1006] - Initiating client connection, connectString=localhost:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@215be6bb
2020-09-21 18:42:22,206 [myid:] - INFO [main:X509Util@77] - Setting -D jdk.tls.rejectClientInitiatedRenegotiation=true to disable client-initiated TLS renegotiation
2020-09-21 18:42:22,211 [myid:] - INFO [main:ClientCnxnSocket@239] - jute.maxbuffer value is 1048575 Bytes
2020-09-21 18:42:22,221 [myid:] - INFO [main:ClientCnxn@1716] - zookeeper.request.timeout value is 0. feature enabled=false
Welcome to ZooKeeper!
JLine support is enabled
2020-09-21 18:42:22,305 [myid:localhost:2181] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1167] - Opening socket connection to server localhost/127.0.0.1:2181.
2020-09-21 18:42:22,308 [myid:localhost:2181] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1169] - SASL config status: Will not attempt to authenticate using SASL (unknown error)
2020-09-21 18:42:22,312 [myid:localhost:2181] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@999] - Socket connection established, initiating session, client: /127.0.0.1:49492, server: localhost/127.0.0.1:2181
2020-09-21 18:42:22,377 [myid:localhost:2181] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1433] - Session establishment complete on server localhost/127.0.0.1:2181, session id = 0x10000214bdb0000, negotiated timeout = 30000
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0]
- zoo.cfg参数配置解释
参数 | 描述 |
---|---|
dataDir | 存放内存数据库快照文件,同时用于集群的myid文件也存在这个文件里 |
dataLogDir | 用于单独设置transaction log目录,transaction log分离可以避免和普通log还有快照竞争。 |
tickTime | 心跳时间,为了确保client-server连接存在,以毫秒为单位。 |
clientPort | 客户端监听端口 |
globalOoutstandingLimit | client请求队列的最大长度,防止内存溢出,默认值为1000. |
preAllocSize | 预分配的transaction log空间block 为proAllocSizekb 默认block为64M. |
snapCount | 在snapCount个snapshot后写一次transaction log,默认值为100000。 |
traceFile | 用于记录请求的log,打开会影响性能,用于debug,最好不要定义。 |
maxClientCnxns | 最大并发客户端数,用于防止DOS攻击的,默认值为10. |
clientPortBindAddress | 指定client ip 端口 |
minSessionTimeout | 最小的客户端session超时时间,毫秒 |
electionAlg | 用于选举实现的参数,0以原始基于UDP的方式协作,1为不进行用户验证的基于UDP的快速选举2为进行用户验证基于UDP的快速选举;3为基于TCP的快速选举,默认为3 |
initLimit | 多少个ticktime内,允许其他server连接并初始化数据,如果zookeeper管理的数据较大,则应该增大这个值。 |
syncLimit | leader监听不到follower的心跳,从集群中将follower移除的时间 |
leaderServers | leader是否接收客户端,默认值为yes ,leader负责协调更新,当更新吞量时,可以设置为不接受客户端连接,以便leader可以专注于同步协调工作。 |
server.x=[hostname]:n[:nn] | 配置集群的主机信息。 |
group.x=nn[:nn] | 分组信息,表示哪个组有哪些节点。 |
weight.x=n | 权重信息。 |
1.4 部署集群
- 环境描述
hostname | ip address | leader选举端口 | 服务器通信端口 | 客户端访问端口 |
---|---|---|---|---|
master | 172.16.70.207 | 2888 | 3888 | 2181 |
slave1 | 172.16.70.213 | 2888 | 3888 | 2181 |
slave2 | 172.16.70.205 | 2888 | 3888 | 2181 |
- 下载软件,放到/usr/local目录下
scp -rp zookeeper/ 172.16.70.213:/usr/local/
scp -rp zookeeper/ 172.16.70.205:/usr/local/
scp -rp conf/zoo.cfg 172.16.70.213:/usr/local/zookeeper/conf/
scp -rp conf/zoo.cfg 172.16.70.205:/usr/local/zookeeper/conf/
scp -rp jdk-14.0.2 172.16.70.213:/usr/local/
scp -rp jdk-14.0.2 172.16.70.205:/usr/local/
[root@master local]# cat zookeeper/conf/zoo.cfg
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/opt/zookeeper
dataLogDir=/opt/zookeeper_log
# the port at which the clients will connect
clientPort=2181
...
server.0=172.16.70.207:2888:3888
server.1=172.16.70.213:2888:3888
server.2=172.16.70.205:2888:3888
server.A=B:C:D
A: 是一个数字,表示服务器的编号;
B:是服务器的ip地址
C:是leader选举的端口
D:zookeeper服务器之前的通信端口
- 配置myid文件
# 在172.16.70.207上数据目录下配置
# 根据以下配置
server.0=172.16.70.207:2888:3888
server.1=172.16.70.213:2888:3888
server.2=172.16.70.205:2888:3888
[root@master local]# cat /opt/zookeeper/myid
0
- 配置环境变量
# 三个节点相同
cat >>/etc/profile<EOF
export JAVA_HOME=/usr/local/jdk-14.0.2
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
export CLASSPATH=.:$JAVA_HOME/lib
export PATH=/usr/local/zookeeper/bin:$PATH
EOF
source /etc/profile
- 启动
# master
[root@master local]# zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@master local]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: leader
# slave1
[root@slave1 conf]# zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@slave1 conf]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
# slave2
[root@slave2 zookeeper]# zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@slave2 zookeeper]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
- 报错总结
slave节点出现[root@slave1 zookeeper]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/…/conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Error contacting service. It is probably not running.
现象:服务已经启动,端口已经开启,在显示状态信息的时候显示报错
解决:是因为服务启动节点在寻找master,但是节点的防火墙是开启状态
iptables -F
iptables -X
iptables -Z
iptables -L
1.5 zookeeper命令详解
# zkCli.sh
连接命令:
connect 127.0.0.1:2181
关闭: close
节点命令:
创建节点:create /t1 v1
-s 为顺序节点,-e为临时节点
查看节点:ls /t1 #用绝对路径
查看节点状态:stat /t1
输出信息如下:
cZxid = 0x20000000f
ctime = Tue Sep 22 19:56:22 CST 2020
mZxid = 0x20000000f
mtime = Tue Sep 22 19:56:22 CST 2020
pZxid = 0x20000000f
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 1
numChildren = 0
输出信息解释:
cZxid:节点创建时的zxid
ctime:节点创建时间
mZxid:节点最近一次更新时的zxid
mtime:节点最近一次更新的时间
cversion:子节点数据更新次数
dataVersion:本节点数据更新次数
aclVersion:节点ACL(授权信息)的更新次数
ephemeralOwner:如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是临时节点,ephemeralOwner值为0
dataLength:节点数据长度,本例中为hello world的长度
numChildren:子节点个数
查看节点的值:get /t1
删除节点: delete /a
配额命令:
显示配额:listquota /storm #前提是/storm已经存在
设置配额:setquota -n 2 /t1 设置/t1子节点的数量最大为2
setquota -b 100 /t1 设置/t1节点长度最大为100
删除配额:delquota
-n :子节点个数 -b 子节点数据长度
历史命令:history
认证命令:
用于认证: addauth digest username:password
设置节点的ACL:setAcl schema:id:permissions
获取节点的acl:getAcl
scheme和id
world: 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的
auth: 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication)
digest: 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication
ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段
super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)
permissions
CREATE(c): 创建权限,可以在在当前node下创建child node
DELETE(d): 删除权限,可以删除当前的node
READ(r): 读权限,可以获取当前node的数据,可以list当前node所有的child nodes
WRITE(w): 写权限,可以向当前node写数据
ADMIN(a): 管理权限,可以设置当前node的permission
强制同步命令:sync
由于请求在半数以上的zk server上生效就表示此请求生效,那么就会有一些zk server上的数据是旧的。sync命令就是强制同步所有的更新操作
设置和显示监视状态:printwatchers
退出:quit
2. zookeeper基本概念
2.1 zookeeper基础介绍
ZooKeeper 是Hadoop下的一个子项目,它是一个针对大型分布式系统的可靠协调系统;它提供的功能包括:配置维护、名字服务、分布式同步、组服务等; 它的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
zookeeper的应用场景:用于担任服务生产者和服务消费者的注册中心。
2.2 zookeeper设计目标
zookeeper允许分布式进行通过共享的层次结构命名空间进行相互协调。zookeeper 的数据保存在内存中,意味着可以实现高吞吐量和低延迟。
名称空间由zookeeper中的数据寄存器组成,称为znode,这些类似于文件和目录。zookeeper层次结构是树型结构。
zookeeper层次结构命名空间示意图:
2.3 zookeeper的主要特点
1)、最终一致性:为客户端展示同一视图,这是 ZooKeeper 最重要的性能。
2)、可靠性:如果消息被一台服务器接受,那么它将被所有的服务器接受。
3)、实时性:ZooKeeper 不能保证两个客户端同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
4)、等待无关(wait-free):慢的或者失效的 client 不干预快速的client的请求。
5)、原子性:更新只能成功或者失败,没有中间其它状态。
6)、顺序性:对于所有Server,同一消息发布顺序一致。
3. zookeeper原理说明
3.1 zookeeper 系统架构
zookeeper原理:
a. 如果客户端在服务器端执行读操作,首先,客户端向服务器端建立连接,发送tcp连接请求,server端接响应,获取观察的事件以及发送心跳,如果这个连接中断,client会向其他server发送连接请求,建立连接;
b. 如果是读请求,则客户端只需要连接到follower节点即可就能返回数据,如果是读请求client将会连接到leader端获取数据。上图每一个server都代表一个follower节点,每个follower节点组成一组zookeeper服务的集群。
c. 当zookeeper启动时,将从实例中选举一个leader,leader负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数server在内存中成功修改数据。每个server在内存中存储了一份数据。
d. zookeeper集群是可以复制的,为了保证数据间的一致性,zookeeper是通过zab协议来保证的。
e.** zab协议内容:崩溃恢复,消息广播**
1. 当集群中选举出一个leader之后,剩下的其他节点被称为follower,客气端发送的所有写请求都将由leader处理,leader接收之后,以广播的方式发送给follower.
2. 当leader崩溃或失去大多数时,集群中重新开始选举leader,选举出来以后,让所有的服务器都恢复到一个正确的状态。
3. 当选举出一个新的leader之后,会和其他follower同步数据。
4. zab协议
4.1 zab协议概念及作用
概念:zab协议的全称是zookeeper Atomic Broadcast(zookeeper原子广播),zookeeper是通过zab协议来保证分布式事务的一致和性。
zab协议是一种支持崩溃恢复和原子广播协议,借鉴paxos算法,专门为zookeeper设计的。
zab协议的作用:客户端连接到zookeeper集群中的一个节点,如果是读请求,则直接返回结果,如果是写请求,节点就会向leader提交事务之后leader向所有节点发送广播,如果超过半数执行成功,那么leader就会向client发送commit ok的消息。
zab协议的特性:
1)zab协议需要确保那些已经在leader服务器上提交的事务最终最所有的服务器提交;
2)zab协议需要确保丢弃那些只在leader上被提出而没有被提交的事务。
4.2 zab协议的原理
zab协议的三个阶段: 发现,同步,广播
发现:选leader,zookeeper集群必须选举一个leader进程,同时leader会维护一个follower的可用客户端列表,客户端可以根据这个列表和follower进行通信。
同步:leader将自己本身的数据和follower进行同步,做成多副本,也提现了CAP中的高可用和分区容错性。
广播:leader接收客户端的写请求,被当做一个提案proposal,且赋予一个提案编号,leader根据提案编号将proposal发送给其他follower节点。
4.3 zab协议内容
zab协议包括:崩溃恢复和消息传播
崩溃恢复又包括:leader选举和数据恢复。
- 状态转换:当集群中启动中,突然出现网络中断或者其他原因造成leader no online,zab协议就会进入崩溃恢复模式;之后选举出新的leader之后,向其他follower节点同步数据,数据一致后进入消息传播模式。
在进入消息传播模式中,client向leader发送写请求,会将每一个写请求做为一个事务请求转换为一个提案proposal,并且在广播事务proposal之前leader分配一个全局事务递增的唯一id,leader会根据事务的唯一id进行处理。
- 消息传播具体步骤
a. 客户端发起一个写请求;
b. leader端将client发起的事务请求分配一个事务id,zxid;
c. leader服务器给每一个follower服务器分配一个队列,将新的proposal放到队列中,然后根据FIFO策略进行消息发送;
d. 当follower接收到leader的提案之后,首先将其以事务的方式写入到二进制中,写入成功之后返回给leader ack的消息;
e. 当leader接收到半数以上的ack消息之后,则认为消息发送成功,返回给client commit ok的消息;
f. 返回给client之后,leader也会将commit ok的消息返回给所有follower节点。
使用FIFO的优点:
leader服务器与每一个follower服务器之间都维护了一个FIFO消息队列来发送消息,可以做到异步解耦。如果使用同步的方式,就造成性能下降。
4.4 zab协议保证数据一致性的因素
如果一个事务在leader上提交了,并且过半的节点都响应了ack,但是在leader发送commit消息之前挂机。
如果发生以上情况,那么zab协议为了保证数据一致性的要求:
- 确保已经被leader提交的proposal必须最终被所有的follower服务器提交;
2)确保丢弃已经被leader提出的但是没有被提交的proposal.
根据以上要求,zab协议则必须满足以下条件:
a. 新选举出来的leader不能包含未提交的proposal,即内存中的数据是干净的;
b. 新选举的leader节点中含有最大的zxid.
4.5 在zab数据同步过程中,怎么丢弃一个proposal
zxid: 是事务编号id,是一个64位的数字。
每当客户端连接一次写请求,leader都会分配一个zxid。其中低32位可以看成简单的递增的计数器,每请求一个次计数器加1,而高32位代表leader周期的epoch编号。
每当选举一次leader ,就会从这个leader服务器取出本地最大的事务号+1赋予epoch。以这样的方式来记录每个leader的生命周期。并将低32位的值,重置为0,重新生成txid;
当一台follower机器包含上一个leader周期中未提交的proposal的服务器启动时,这台机器加入集群和现在的leader关联之后,为了保证数据一致性,会和follower机器做比对,比对的结果是follower机器的proposal值大于leader的proposal,这时会将follower节点做回退,回退到一个确实已经被集群中过半的机器commit的最新的proposal.
4.6 zab和paxos
zookeeper的主要功能是维护一个高可用且一致的数据库,数据库内容复制在多个节点上,只要满足集群大多数系统就可用。实现这一核心的是ZAB算法,一种atomic Broadcast协议,形象的说就是能够保证发给各复本的消息顺序相同。
- 那么zookeeper为什么不基于paxos协议呢?
首先paxos协议的一致性不能达到zookeeper的要求。例如
假设使用paxos协议,一开始集群中只有leader p1,他发起了两个事务,分别是t1 v1,t2 v2(序号tx的值为vx),假设这两个事务在写入的过程中挂了,又经过重新选举主为leader p2,他又发起了一个事务t1,v1,此时在p2里事务的顺序由新到旧是这样的(t1-v1,t2-v2,t1-v1)后者的t1-v1是leader1的,所以在执行事务时会将p1的t1-v1替换。即p2的t1-v1在前,p1的t2-v2在后。
那么我们都知道zookeeper集群架构是一个树形结构,假如我们在p1的t1-v1中创建/a目录 ,在t2-v2中创建/a/app_1文件,在p2的t1-v1中创建/b目录,由上可知p1的t1-v1已经被p2的t1-v1覆盖。由此变成/b,/a/app_1,那么a目录没有创建,执行/a/app_1也是不成功的。
- 解决方案
为了保证这一点,zab要保证同一个leader的发起事务要按顺序被apply,同时还要保证只有先前的leader的所有事务都被apply之后,新选的leader才能发起事务。
zab的核心思想,形象的说就是保证任意时刻只有一个leader,所有更新事务由leader发起去更新所有复本,只要多数commit成功,就发给客户端commit ok的信息。因为zab处理的事务永远不会回滚,zab的2PC做了优化,多个事务只要通知zxid最大的那么commit,之前的各follower会统统commit.
4.7 zab的四个阶段
zab节点的三个状态:
following:当前节点是跟随者,服从leader节点的命令;
leading: 当前节点是leader,负责协调事务;
election/looking:节点处理选举状态,正在寻找leader ;
observer: 不参数选举,是只读节点;
节点的持久状态:
history:当前节点接收到事务 Proposal 的LogacceptedEpoch:Follower 已经接受的 Leader 更改 epoch 的 newEpoch 提议。currentEpoch:当前所处的 Leader 年代lastZxid:history 中最近接收到的Proposal 的 zxid(最大zxid)
- 选举阶段
节点一开始处于选举节点,只要有一个节点选举得的票数超过半数,就可以当leader。
zookeeper规定所有的有效的票数必须在一个轮次里,每个服务器在开始新一轮投票时,都会对自己维护的logicalClock进行自增操作。
投票之前都会先将自己的logicalClock reset .投票格式为(1,2) (2,1)(3,1) 前者为投票者,后者为被选举者。
快速选举:
在选举阶段使用的是fast Leader Election(FLE)。
FLE 会选举拥有最新Proposal history (lastZxid最大)的节点作为 Leader,这样就省去了发现最新提议的步骤。这是基于拥有最新提议的节点也拥有最新的提交记录
成为 Leader 的条件:
1)选 epoch 最大的
2)若 epoch 相等,选 zxid 最大的
3)若 epoch 和 zxid 相等,选择 server_id 最大的(zoo.cfg中的myid)
节点在选举开始时,都默认投票给自己,当接收其他节点的选票时,会根据上面的 Leader条件 判断并且更改自己的选票,然后重新发送选票给其他节点。当有一个节点的得票超过半数,该节点会设置自己的状态为 Leading ,其他节点会设置自己的状态为 Following。
- 发现阶段(descovery)
在这个阶段,followers和上一轮选举出的准leader进行通信,同步followers最近接收的事务proposal.
一个follower只会连接一个leader,如果一个follower节点认为另一个follower节点,则会在尝试连接时被拒绝,被拒绝之后,该节点就会进入leader election阶段。
这个阶段的主要目的是发现当前大多数节点接收的最新 Proposal,并且准 Leader 生成新的 epoch ,让 Followers 接收,更新它们的 acceptedEpoch。
选举流程:
a. 候选节点A初始化自身的zxid和epoch;
b. 向其他所有节点发送选主通知;
c. 等待其他节点的回复;
d. 如果来自B节点的回复不为空,且B是一个有效的节点,判断B此时的运行状态是LOOKING(也在选 主)还是LEADING/FOLLOWING
- 同步阶段
同步阶段主要是利用 Leader 前一阶段获得的最新 Proposal 历史,同步集群中所有的副本。
只有当 quorum(超过半数的节点) 都同步完成,准 Leader 才会成为真正的 Leader。Follower 只会接收 zxid 比自己 lastZxid 大的 Proposal。
- 广播阶段(broadcast)
zookeeper集群对外提交事务服务,并且leader可以进行消息广播。同时如果有新的节点加入,需要对新节点同步。