作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。
前面我们介绍介绍了几个常用的代理服务器,本章节我们讲来讲解Zookeeper这个中间件。
上个小节我们介绍了Zookeeper的选举流程,本小节来讲解一个读写流程,作为集群对外提供服务,而我们的客户端连接的时候也会把所有的地址都写到配置文件里面,那么读写数据的时候他具体是怎么实现的呢?
Zookeeper读流程
我这里使用一个测试python程序来连接到集群进行测试,代码里面写了3个Zookeeper地址。
zk_hosts = '192.168.31.140:2181,192.168.31.141:2181,192.168.31.142:2181'
执行写入程序,从下面的网络连接可以看到,我们虽然写了3个zk地址,但是实际上他只会随机连接其中一个,如果连接失败,他则重新选择一个进行连接(业务程序不受影响,从PID来看,他没有变化)。
我们前面介绍了zk的2个角色,leader是可读可写的,follower只读,如果我的客户端连接刚好连接到follower以后,他是如何来出来我的的写请求呢?下面都是修改了日志级别才看到的日志情况。
sed -i 's/INFO/DEBUG/g' logback.xml
连接leader 写日志
#leader日志
2025-04-19 15:39:38,194 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.SessionTrackerImpl@309] - Checking session 0x200003287770000
2025-04-19 15:39:38,194 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.ZooKeeperServer@2012] - Permission requested: 2
2025-04-19 15:39:38,194 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.ZooKeeperServer@2013] - ACLs for node: [31,s{'world,'anyone}]
2025-04-19 15:39:38,194 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.ZooKeeperServer@2014] - Client credentials: ['ip,'192.168.31.120]
2025-04-19 15:39:38,195 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.PrepRequestProcessor@1136] - Digest got from data tree is: 2535257766
2025-04-19 15:39:38,195 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.q.CommitProcessor@606] - Processing request:: sessionid:0x200003287770000 type:setData cxid:0x3 zxid:0x800000021 txntype:5 reqpath:n/a
#Leader发起投票请求
2025-04-19 15:39:38,195 [myid:] - DEBUG [ProcessThread(sid:2 cport:-1)::o.a.z.s.q.Leader@1273] - Proposing:: sessionid:0x200003287770000 type:setData cxid:0x3 zxid:0x800000021 txntype:5 reqpath:n/a
2025-04-19 15:39:38,214 [myid:] - DEBUG [LearnerHandler-/192.168.31.140:54388:o.a.z.s.q.CommitProcessor@594] - Committing request:: sessionid:0x200003287770000 type:setData cxid:0x3 zxid:0x800000021 txntype:5 reqpath:n/a
2025-04-19 15:39:38,215 [myid:] - DEBUG [LearnerHandler-/192.168.31.142:59648:o.a.z.s.q.Leader@1022] - outstanding is 0
2025-04-19 15:39:38,215 [myid:] - DEBUG [CommitProcessor:2:o.a.z.s.FinalRequestProcessor@148] - Processing request:: sessionid:0x200003287770000 type:setData cxid:0x3 zxid:0x800000021 txntype:5 reqpath:n/a
2025-04-19 15:39:38,215 [myid:] - DEBUG [CommitProcessor:2:o.a.z.c.PathTrie@312] - mydata
2025-04-19 15:39:38,215 [myid:] - DEBUG [CommitProcessor:2:o.a.z.s.DataTree@1815] - Digests are matching for Zxid: 800000021, Digest in log and actual tree: 3757378326
2025-04-19 15:39:38,215 [myid:] - DEBUG [CommitProcessor:2:o.a.z.s.FinalRequestProcessor@202] - sessionid:0x200003287770000 type:setData cxid:0x3 zxid:0x800000021 txntype:5 reqpath:n/a
大体意思是先对请求进行鉴权以及显示客户端信息,领导者(Leader)正在提议这个setData请求,准备提交给集群中的其他服务器。并且其他服务器同意,最终写入这个数据。
连接foller请求日志
可以发现他并没发起投票请求,确实也没看到转发的日志。
#follower日志
2025-04-19 15:44:07,421 [myid:] - DEBUG [FollowerRequestProcessor:1:o.a.z.s.q.CommitProcessor@606] - Processing request:: sessionid:0x10000328b4e0000 type:setData cxid:0x3 zxid:0xfffffffffffffffe txntype:unknown reqpath:/mydata
2025-04-19 15:44:07,497 [myid:] - DEBUG [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):o.a.z.s.q.CommitProcessor@594] - Committing request:: sessionid:0x10000328b4e0000 type:setData cxid:0x3 zxid:0x80000002f txntype:5 reqpath:n/a
2025-04-19 15:44:07,497 [myid:] - DEBUG [CommitProcessor:1:o.a.z.s.FinalRequestProcessor@148] - Processing request:: sessionid:0x10000328b4e0000 type:setData cxid:0x3 zxid:0x80000002f txntype:5 reqpath:/mydata
2025-04-19 15:44:07,498 [myid:] - DEBUG [CommitProcessor:1:o.a.z.c.PathTrie@312] - mydata
2025-04-19 15:44:07,498 [myid:] - DEBUG [CommitProcessor:1:o.a.z.s.DataTree@1815] - Digests are matching for Zxid: 80000002f, Digest in log and actual tree: 1443723835
2025-04-19 15:44:07,498 [myid:] - DEBUG [CommitProcessor:1:o.a.z.s.FinalRequestProcessor@202] - sessionid:0x10000328b4e0000 type:setData cxid:0x3 zxid:0x80000002f txntype:5 reqpath:/mydata
ZooKeeper写请求流程图
graph TD
subgraph 客户端直连Leader
A1[Client] -->|1. 写请求 /mydata| B1[Leader]
B1 -->|2. 生成ZXID, 广播Proposal| C1[Followers]
C1 -->|3. 回复ACK| B1
B1 -->|4. 收到多数ACK后提交Commit| C1
C1 -->|5. 应用事务到DataTree| D1[数据持久化]
B1 -->|6. 返回成功| A1
end
subgraph 客户端连接Follower
A2[Client] -->|1. 写请求 /mydata| B2[Follower]
B2 -->|2. 转发请求到Leader| B1
B1 -->|3. 生成ZXID, 广播Proposal| C2[Followers]
C2 -->|4. 回复ACK| B1
B1 -->|5. 提交Commit| C2
C2 -->|6. 应用事务到DataTree| D2[数据持久化]
B1 -->|7. 通知Follower提交完成| B2
B2 -->|8. 返回成功| A2
end
详细流程说明
场景1:客户端直连Leader
-
客户端发送写请求:客户端直接向Leader节点(如
myid=2
)发送setData
请求(路径/mydata
)。 -
Leader发起提案(Proposal):Leader生成全局唯一事务ID(ZXID,如
0x800000012
),通过ZAB协议将提案(Proposal)广播给所有Followers。 -
Followers回复ACK:Followers接收提案后,检查数据合法性,并向Leader发送ACK确认。
-
Leader提交事务(Commit):Leader收到多数派(Quorum)的ACK后,向所有Followers发送Commit指令。
-
数据持久化:Leader和Followers将事务应用到本地
DataTree
,并记录到事务日志(Txn Log)。 -
返回客户端成功:Leader直接向客户端返回写操作成功。
场景2:客户端连接Follower
-
客户端发送写请求:客户端向Follower节点(如
myid=3
)发送setData
请求。 -
Follower转发请求:Follower识别到写请求无法本地处理,将请求转发给Leader。
-
Leader处理提案(同场景1的步骤2-5):Leader生成ZXID、广播提案、收集ACK、提交事务,并通知所有Followers提交完成。
-
Follower响应客户端:Follower收到Leader的Commit通知后,更新本地数据,并向客户端返回成功。
关键差异点
步骤 |
客户端直连Leader |
客户端连接Follower |
---|---|---|
请求入口 |
Leader直接接收请求 |
Follower接收请求并转发 |
网络路径 |
客户端→Leader→Followers |
客户端→Follower→Leader→Followers |
客户端响应延迟 |
较低(减少一次转发) |
较高(增加转发和回传时间) |
适用场景 |
客户端已知Leader地址 |
客户端通过负载均衡或随机连接访问集群 |
运维小路
一个不会开发的运维!一个要学开发的运维!一个学不会开发的运维!欢迎大家骚扰的运维!
关注微信公众号《运维小路》获取更多内容。