What's ZK
ZooKeeper是一个分布式服务协调框架,属于CAP原则中的CP系统(最终一致性+分区容错性)
How can u do with ZK
它主要是用来解决分布式一些数据管理问题:
- service discovery
- state syncronization
- decentralized config
- distributed lock
- ...
Priciple
Consistency
zk的是通过基于Paxos的ZAB协议(Zookeeper Atomic Broadcast)来实现一致性的, 但默认只是最终一致性
原因是: 不同于Raft算法的读写都只能在Leader操作, ZAB协议下ZK的写只能在Leader操作但是ZK的读可以在Follower上进行, 所以ZK满足如下case:
- Leader保证一个client的顺序写, 即写操作的顺序在所有 Follower 上是保持一致的, 比如一个client要求先完成写操作 A、接着写操作 B、之后是写操作 C,那么在最终整体的写请求中将看到是先写 A 再写 B 再写 C 的,但 A B C 可能不是相邻的(因为可能插入其他client的写请求)
- client A 更新了键 X 的值,而 client B 在另一台服务器上读取键 X 的值可能会读到更新之前的值 (这里能看出来不是线性一致的), 因为Leader还没把数据同步到Slave
- 同一个client顺序向两个服务器A和B分别发起写和读请求, 因为请求会带上
zxid标记, 在服务器B读的时候如果发现服务器B最新的zxid小于自身的zxid则请求会阻塞; 所以实际上zk对于单个客户端是线性一致的; 所以zk提供了sync命令,sync命令本质上是一个写+读的组合命令, 得益于zxid所以能可以通过这种方式保证读到的是最新的
ZK的节点类型
- PERSISTENT :
create /first_znode - EPHEMERAL :
create -e /tmp_znode_1 - PERSISTENT_SEQUENTIAL :
create -s /first_znode - EPHEMERAL_SEQUENTIAL :
create -e -s /tmp_znode_1
ephemeralnode can't have child node
ZK的监控机制及应用
cli命令 : get -w /normal_znode0000000007
通过监听机制能实现很多功能:
- 监听集群中各节点的状态(配合临时节点)
- 节点存储的是配置, 各节点可以监听配置的更新
- 服务发现, 节点以PSM命名, 节点的值是IP列表
- 实现分布式锁(配合临时节点或顺序节点) : 比如排他锁取锁可以在
/lock节点下create -e, 谁create成功了谁就取到锁, 取锁失败的监听这个临时节点, 等解锁收到通知后再create -e; 再比如可以使用顺序节点create -e -s创建完成后再调用ls命令查看自己创建的顺序节点是不是排在第一个,如果不是则监听前一个顺序临时节点
ZK写数据流程
- 如果client连接的是follower, follower会先将命令发到leader (类似于
etcd的forward) - leader会将写命令广播到所有follower (以提案的形式
proposal) - 在超过半数follower给leader正确的返回后, leader会给所有slave发送
commit消息 - 所有follower会将上一个提案提交 (类似于二阶段提交)
write to leader
write to follower
Data Structure
ZNode
zk是树状存储, 每个存储节点叫znode, 最大可存储1MB
ZXid
zxid is noly generated by leader, the structure of zxid is that:
leader 每发起一个提案就会同时生成一个zxid, zxid是顺序递增生成
分布式
zk server has three roles:
leader: 同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader完成再由 Leader 将写操作广播给其它服务器follower: 可同时存在多个 Follower,它会响应 Leader 的心跳。Follower 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理,参与事务请求 Proposal 的投票及 Leader 选举投票observer: 可同时存在多个 Observer, 功能与 Follower 类似,但是,不参与投票 (为了性能考虑, 当follower节点过多时, 投票很耗时间)
ZK的使用
Distributed Lock
ZK作为分布式锁性能优于etcd
| redis | etcd | zookeeper | |
|---|---|---|---|
| 优点 | 并发高 | RAFT协议 | 使用临时节点可以达到公平锁; 已经被锁时可注册回调事件(no need poll) |
| 缺点 | 取锁需要重试(耗CPU);一主多从可能存在多个client取到锁情况 | 延时比较高, 已经被锁时可需要业务方重试 | 拿到锁的节点断网情况下,后一个节点也会拿到锁 |
Bad Cases:
现在clientA已经拿到锁了,它的临时节点排在第一位, 有一个clientB临时节点排在第二位, 同时watch了第一个节点, 如果此时clientA的连接断了, clientB就会认为锁被释放了, 所以它也会拿到锁
ZK的使用
搭建启动单机伪集群
依次启动zoo3.cfg zoo2.cfg zoo1.cfg
- # 依次启动zoo3.cfg zoo2.cfg zoo1.cfg
- zookeeper-bin ❯ bin/zkserver.sh start conf/zoo3.cfg
- /usr/bin/java
- ZooKeeper JMX enabled by default
- Using config: conf/zoo3.cfg
- Starting zookeeper ... STARTED
查看节点状态
- # 查看节点状态
- zookeeper-bin ❯ bin/zkserver.sh status conf/zoo3.cfg
- /usr/bin/java
- ZooKeeper JMX enabled by default
- Using config: conf/zoo3.cfg
- Client port found: 2183. Client address: localhost. Client SSL: false.
- Mode: leader
操作节点
用cli命令连接node
- zookeeper-bin ❯ bin/zkcli.sh -server 127.0.0.1:2183
创建节点
# 创建节点
[zk: 127.0.0.1:2181(CONNECTED) 1] create /first_znode
Created /first_znode
# 查看节点下的所有节点
[zk: 127.0.0.1:2183(CONNECTED) 2] ls /
[first_znode, t1, zookeeper]
设置节点的值
# 设置节点的值
[zk: 127.0.0.1:2183(CONNECTED) 3] set /first_znode "set from 3"
# 读节点的值
[zk: 127.0.0.1:2181(CONNECTED) 4] get /first_znode
set from 3
查看节点状态
# 查看节点状态
[zk: 127.0.0.1:2181(CONNECTED) 5] stat /first_znode
cZxid = 0x400000003
ctime = Wed Aug 17 15:43:53 CST 2022
mZxid = 0x400000004
mtime = Wed Aug 17 15:51:13 CST 2022
pZxid = 0x400000003
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 10
numChildren = 0
# 删除节点
[zk: 127.0.0.1:2181(CONNECTED) 6] delete /first_znode
创建临时节点
# 创建临时节点, 退出会话后该会话创建的临时节点会销毁
[zk: 127.0.0.1:2181(CONNECTED) 7] create -e /tmp_znode_1
Created /tmp_znode_1
创建顺序节点
# 创建顺序节点
[zk: 127.0.0.1:2183(CONNECTED) 3] create -s /normal_znode
Created /normal_znode0000000006
[zk: 127.0.0.1:2183(CONNECTED) 4] create -s /normal_znode
Created /normal_znode0000000007
退出会话
# 退出会话, 该会话创建的临时节点会销毁
[zk: 127.0.0.1:2183(CONNECTED) 7] quit
监听节点
# 监听节点
[zk: 127.0.0.1:2181(CONNECTED) 3] get -w /normal_znode0000000007
007
# 如果另一个cli更改了这个节点的值, 该cli会收到通知
# 但watcher是一次性的, 收到通知完后需要重新设置watcher
[zk: 127.0.0.1:2181(CONNECTED) 4]
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/normal_znode0000000007
Ref
- zhuanlan.zhihu.com/p/363396366
- tech.byte dance.net/articles/6895924688841605134
- cloud.tencent.com/developer/a…