zookeeper原始碼 — 五、處理寫請求過程
目錄
- 處理寫請求總體過程
- 客戶端發起寫請求
- follower和leader互動過程
- follower傳送請求給客戶端
處理寫請求總體過程
zk為了保證分散式資料一致性,使用ZAB協議,在客戶端發起一次寫請求的時候時候,假設該請求請求到的是follower,follower不會直接處理這個請求,而是轉發給leader,由leader發起投票決定該請求最終能否執行成功,所以整個過程client、被請求的follower、leader、其他follower都參與其中。以建立一個節點為例,總體流程如下
從圖中可以看出,建立流程為
- follower接受請求,解析請求
- follower通過FollowerRequestProcessor將請求轉發給leader
- leader接收請求
- leader傳送proposal給follower
- follower收到請求記錄txlog、snapshot
- follower傳送ack給leader
- leader收到ack後進行commit,並且通知所有的learner,傳送commit packet給所有的learner
這裡說的follower、leader都是server,在zk裡面server總共有這麼幾種
由於server角色不同,對於請求所做的處理不同,每種server包含的processor也不同,下面細說下具體有哪些processor。
follower的processor鏈
這裡的follower是FollowerZooKeeperServer,通過setupRequestProcessors來設定自己的processor鏈
FollowerRequestProcessor -> CommitProcessor ->FinalRequestProcessor
每個processor對應的功能為:
FollowerRequestProcessor:
作用:將請求先轉發給下一個processor,然後根據不同的OpCode做不同的操作
如果是:sync,先加入org.apache.zookeeper.server.quorum.FollowerZooKeeperServer#pendingSyncs,然後傳送給leader
如果是:create等,直接轉發
如果是:createSession或者closeSession,如果不是localsession則轉發給leader
CommitProcessor :
有一個WorkerService,將請求封裝為CommitWorkRequest執行
作用:
轉發請求,讀請求直接轉發給下一個processor
寫請求先放在pendingRequests對應的sessionId下的list中,收到leader返回的commitRequest再處理
- 處理讀請求(不會改變伺服器狀態的請求)
- 處理committed的寫請求(經過leader 處理完成的請求)
維護一個執行緒池服務WorkerService,每個請求都在單獨的執行緒中處理
- 每個session的請求必須按順序執行
- 寫請求必須按照zxid順序執行
- 確認一個session中寫請求之間沒有競爭
FinalRequestProcessor:
總是processor chain上最後一個processor
作用:
- 實際處理事務請求的processor
- 處理query請求
- 返回response給客戶端
SyncRequestProcessor:
作用:
- 接收leader的proposal進行處理
- 從org.apache.zookeeper.server.SyncRequestProcessor#queuedRequests中取出請求記錄txlog和snapshot
- 然後加入toFlush,從toFlush中取出請求交給org.apache.zookeeper.server.quorum.SendAckRequestProcessor#flush處理
leader的processor鏈
這裡的leader就是LeaderZooKeeperServer
通過setupRequestProcessors來設定自己的processor鏈
PrepRequestProcessor -> ProposalRequestProcessor ->CommitProcessor -> Leader.ToBeAppliedRequestProcessor ->FinalRequestProcessor
客戶端發起寫請求
在客戶端啟動的時候會建立Zookeeper例項,client會連線到server,後面client在建立節點的時候就可以直接和server通訊,client發起建立建立節點請求的過程是:
org.apache.zookeeper.ZooKeeper#create(java.lang.String, byte[], java.util.List<org.apache.zookeeper.data.ACL>, org.apache.zookeeper.CreateMode)
org.apache.zookeeper.ClientCnxn#submitRequest
org.apache.zookeeper.ClientCnxn#queuePacket
- 在
ZooKeeper#create
方法中構造請求的request - 在
ClientCnxn#queuePacket
方法中將request封裝到packet中,將packet放入傳送佇列outgoingQueue中等待發送- SendThread不斷從傳送佇列outgoingQueue中取出packet傳送
- 通過 packet.wait等待server返回
follower和leader互動過程
client發出請求後,follower會接收並處理該請求。選舉結束後follower確定了自己的角色為follower,一個埠和client通訊,一個埠和leader通訊。監聽到來自client的連線口建立新的session,監聽對應的socket上的讀寫事件,如果client有請求發到follower,follower會用下面的方法處理
org.apache.zookeeper.server.NIOServerCnxn#readPayload
org.apache.zookeeper.server.NIOServerCnxn#readRequest
readPayload這個方法裡面會判斷是連線請求還是非連線請求,連線請求在之前session建立的文章中介紹過,這裡從處理非連線請求開始。
follower接收client請求
follower收到請求之後,先請求請求的opCode型別(這裡是create)構造對應的request,然後交給第一個processor執行,follower的第一個processor是FollowerRequestProcessor.
follower轉發請求給leader
由於在zk中follower是不能處理寫請求的,需要轉交給leader處理,在FollowerRequestProcessor中將請求轉發給leader,轉發請求的呼叫堆疊是
serialize(OutputArchive, String):82, QuorumPacket (org.apache.zookeeper.server.quorum), QuorumPacket.java
writeRecord(Record, String):123, BinaryOutputArchive (org.apache.jute), BinaryOutputArchive.java
writePacket(QuorumPacket, boolean):139, Learner (org.apache.zookeeper.server.quorum), Learner.java
request(Request):191, Learner (org.apache.zookeeper.server.quorum), Learner.java
run():96, FollowerRequestProcessor (org.apache.zookeeper.server.quorum), FollowerRequestProcessor.java
FollowerRequestProcessor是一個執行緒在zk啟動的時候就開始執行,主要邏輯在run方法裡面,run方法的主要邏輯是
先把請求提交給CommitProcessor(後面leader傳送給follower的commit請求對應到這裡),然後將請求轉發給leader,轉發給leader的過程就是構造一個QuorumPacket,通過之前選舉通訊的埠傳送給leader。
leader接收follower請求
leader獲取leader地位以後,啟動learnhandler,然後一直在LearnerHandler#run迴圈,接收來自learner的packet,處理流程是:
processRequest(Request):1003, org.apache.zookeeper.server.PrepRequestProcessor.java
submitLearnerRequest(Request):150, org.apache.zookeeper.server.quorum.LeaderZooKeeperServer.java
run():625, org.apache.zookeeper.server.quorum.LearnerHandler.java
handler判斷是REQUEST請求的話交給leader的processor鏈處理,將請求放入org.apache.zookeeper.server.PrepRequestProcessor#submittedRequests,即leader的第一個processor。這個processor也是一個執行緒,從submittedRequests中不斷拿出請求處理
processor主要做了:
- 交給CommitProcessor等待提交
- 交給leader的下一個processor處理:ProposalRequestProcessor
leader 傳送proposal給follower
ProposalRequestProcessor主要作用就是講請求交給下一個processor並且發起投票,將proposal傳送給所有的follower。
// org.apache.zookeeper.server.quorum.Leader#sendPacket
void sendPacket(QuorumPacket qp) {
synchronized (forwardingFollowers) {
// 所有的follower,observer沒有投票權
for (LearnerHandler f : forwardingFollowers) {
f.queuePacket(qp);
}
}
}
follower 收到proposal
follower處理proposal請求的呼叫堆疊
processRequest(Request):214, org.apache.zookeeper.server.SyncRequestProcessor.java
logRequest(TxnHeader, Record):89, org.apache.zookeeper.server.quorumFollowerZooKeeperServer.java
processPacket(QuorumPacket):147, org.apache.zookeeper.server.quorum.Follower.java
followLeader():102, org.apache.zookeeper.server.quorum.Follower.java
run():1199, org.apache.zookeeper.server.quorum.QuorumPeer.java
將請求放入org.apache.zookeeper.server.SyncRequestProcessor#queuedRequests
follower 傳送ack
執行緒SyncRequestProcessor#run從org.apache.zookeeper.server.SyncRequestProcessor#toFlush中取出請求flush,處理過程
- follower開始commit,記錄txLog和snapShot
- 傳送commit成功請求給leader,也就是follower給leader的ACK
leader收到ack
leader 收到ack後判斷是否收到大多數的follower的ack,如果是說明可以commit,commit後同步給follower
follower收到commit
還是Follower#followLeader裡面的while迴圈收到leader的commit請求後,呼叫下面的方法處理
org.apache.zookeeper.server.quorum.FollowerZooKeeperServer#commit
最終加入CommitProcessor.committedRequests佇列,CommitProcessor主執行緒發現佇列不空表明需要把這個request轉發到下一個processor
follower傳送請求給客戶端
follower的最後一個processor是FinalRequestProcessor,最後會建立對應的節點並且構造response返回給client
總結
本篇文章主要介紹了client發起一次寫請求,client、follower和leader各自的處理過程。當然了,為了簡單,其中設定了一些具體的場景,比如請求是傳送到follower的而不是leader