基礎(chǔ)
配置文件
- zk的配置文件中可以配置三個(gè)端口
- clientPort=2181 這個(gè)是配置服務(wù)端用來接收客戶端連接的端口纽什。
- server.1=127.0.0.1:2888:3888
- 配置服務(wù)器1的ip地址
- 2888端口為集群peer之間的通信端口,除了選舉以外都用這個(gè)端口
- 3888端口為選舉端口蜡吧,只有選舉的時(shí)候使用。
1. 數(shù)據(jù)加載階段
- 集群版的入口在QuorumPeerMain.main():
- 讀取并解析配置文件雅镊。
- 構(gòu)建并啟動(dòng)對(duì)事務(wù)日志文件和快照文件的定期刪除清理任務(wù)。清理策略一般是保留最近的3個(gè)文件。
- 集群版會(huì)調(diào)用runFromConfig
- runFromConfig():
- 創(chuàng)建QuorumPeer并賦值锯茄,QuorumPeer extends ZooKeeperThread,是一個(gè)線程任務(wù)
- QuorumPeer復(fù)寫了Thread.start()方法莉御,方法內(nèi)部邏輯為:
- 加載磁盤快照和提交日志撇吞,生成或者說是恢復(fù)內(nèi)存DataTree
- 初始化并啟動(dòng)接收client端連接的線程,監(jiān)聽clientPort礁叔。處理連接默認(rèn)使用NIOServerCnxnFactory(內(nèi)部使用jdk原生nio)牍颈,也可以選擇使用NettyServerCnxnFactory(netty版)。
- 這里提前說下琅关,對(duì)于client連接煮岁,zk使用的是nio,對(duì)于集群peer之間內(nèi)部連接涣易,zk使用的jdk bio画机。這個(gè)用的很好,很適合各自的情況新症,可以想下步氏。
- 啟動(dòng)jetty web服務(wù)器
- 開始leader選舉
- 在main主線程調(diào)用quorumPeer.join(),等待quorumPeer執(zhí)行完畢徒爹。
2. Leader選舉開始
- 初始服務(wù)器狀態(tài)為LOOKING荚醒,此時(shí)每個(gè)peer都會(huì)創(chuàng)建投票,寫入自己的sid隆嗅,最大zxid界阁,currentEpoch,表示選自己做leader胖喳,投給自己泡躯。
- 根據(jù)選舉類型創(chuàng)建選舉算法,以前的都過時(shí)了丽焊,現(xiàn)在就剩下FastLeaderElection這個(gè)算法了较剃。
- 創(chuàng)建新的QuorumCnxManager,替換舊的qcmRef粹懒,將新的qcm傳入FastLeaderElection線程重付,啟動(dòng)線程,開始選舉凫乖。
- 先說下QuorumCnxManager:
- 根據(jù)配置的集群地址和選舉端口确垫,每個(gè)peer都會(huì)主動(dòng)去連接及群眾的其他服務(wù)器,然后根據(jù)sid判斷帽芽,只保留sid大的連接小的這個(gè)方向的連接删掀,銷毀掉sid小的連接大的這個(gè)方向的連接。這樣每個(gè)peer都有且只有一個(gè)與其他服務(wù)器的連接导街,該鏈接為長連接披泪,使用jdk bio實(shí)現(xiàn)。
- QuorumCnxManager就是選舉連接管理器搬瑰,將收到的投票數(shù)據(jù)放到QuorumCnxManager.recvQueue中款票。
- 而選舉過后正常服務(wù)時(shí)控硼,使用的是服務(wù)端口,是所有follower主動(dòng)連接leader艾少,只保留這個(gè)方向的連接卡乾,不管sid大小嫩舟。
- 先說下QuorumCnxManager:
- FastLeaderElection線程的算法邏輯
- 每個(gè)peer都會(huì)把自己的投票轉(zhuǎn)發(fā)給集群中的其他服務(wù)器筷弦,放到sendqueue中,有獨(dú)立的發(fā)送線程發(fā)送傍菇。
- 獨(dú)立的接收線程會(huì)不斷的從QuorumCnxManager.recvQueue中拿數(shù)據(jù)谍椅,解析误堡,轉(zhuǎn)為投票的結(jié)構(gòu)體,放到FastLeaderElection.recvqueue中雏吭。
- 只要當(dāng)前peer的狀態(tài)時(shí)LOOKING锁施,就會(huì)不斷的從FastLeaderElection.recvqueue中取投票數(shù)據(jù),先判斷是否有效思恐,然后查看遠(yuǎn)程服務(wù)器的狀態(tài)沾谜,如果也是LOOKING,則:
- 將遠(yuǎn)程服務(wù)器的投票數(shù)據(jù)和當(dāng)前服務(wù)器的投票數(shù)據(jù)進(jìn)行對(duì)比胀莹,返回是否應(yīng)該采用遠(yuǎn)程的投票(即當(dāng)前服務(wù)器投票的服務(wù)器數(shù)據(jù)較老)
- 遠(yuǎn)程服務(wù)器的epoch更大基跑,則使用遠(yuǎn)程的投票。
- epoch一樣大描焰,遠(yuǎn)程的zxid更大媳否,則使用遠(yuǎn)程的投票。
- epoch荆秦、zxid一樣大篱竭,遠(yuǎn)程的serverid更大,則使用遠(yuǎn)程的投票步绸〔舯疲總之一定會(huì)比較出來到底用哪個(gè)投票的服務(wù)器,不會(huì)有模棱兩可的結(jié)果瓤介。
- 如果使用遠(yuǎn)程服務(wù)器的投票吕喘,則清空投票箱,重新投刑桑。
- 將遠(yuǎn)程服務(wù)器的投票數(shù)據(jù)和當(dāng)前服務(wù)器的投票數(shù)據(jù)進(jìn)行對(duì)比胀莹,返回是否應(yīng)該采用遠(yuǎn)程的投票(即當(dāng)前服務(wù)器投票的服務(wù)器數(shù)據(jù)較老)
- 如果遠(yuǎn)程服務(wù)器的狀態(tài)是FOLLOWING或者LEADING:
- 可以確定遠(yuǎn)程服務(wù)器已經(jīng)知道了leader是誰氯质,它投過來的票記錄的就是leader,我們使用這個(gè)投票的leader就可以了祠斧。
- 投票結(jié)束闻察,會(huì)調(diào)用QuorumPeer.setPeerState更新當(dāng)前peer的狀態(tài)。
3. Leader選舉結(jié)束后
Leader選舉結(jié)束后的邏輯主要集中在Leader,LearnerHandler,Follower,Learner這幾個(gè)類中。
- 選舉結(jié)束后辕漂,F(xiàn)astLeaderElection算法邏輯中呢灶,會(huì)調(diào)用QuorumPeer.setPeerState(...)方法,將當(dāng)前peer的最新角色通過synchronized同步鎖钉嘹,通知給QuorumPeer.run()方法里的while循環(huán)填抬,這樣當(dāng)前peer就知道了自己的角色,走新的角色邏輯隧期。
- 假如當(dāng)前peer是Leader
- leader首先會(huì)啟動(dòng)一個(gè)獨(dú)立的LearnerCnxAcceptor線程,這個(gè)線程主要邏輯就是赘娄,使用bio的ServerSocket仆潮,監(jiān)聽通訊端口,accept每個(gè)Follower主動(dòng)發(fā)來的連接遣臼,為每一個(gè)連接創(chuàng)建一個(gè)LearnerHandler線程性置,將socket交給該線程處理。
- 轉(zhuǎn)3.1
- leader會(huì)阻塞等待所有Follower發(fā)來消息揍堰,知道確認(rèn)最新的epoch是多少
- 然后將最新的epoch發(fā)給所有follower鹏浅,阻塞等待Follower對(duì)最新epoch的ack。
- 轉(zhuǎn)3.4
- leader在收到Follower對(duì)epoch的ack包后屏歹,判斷超過半數(shù)都接受隐砸,那么該epoch就可用。
- 根據(jù)這次Follower發(fā)來的消息蝙眶,leader與自己的數(shù)據(jù)做比較季希,
- 如果發(fā)現(xiàn)某個(gè)Follower的最大zxid與自己相同,就發(fā)DIFF包幽纷,表示不需要同步式塌。
- 其他情況發(fā)不同的包,如TRUNC友浸,SNAP等峰尝,指明Follower與leader如何同步數(shù)據(jù)。
- 轉(zhuǎn)3.7
- 不會(huì)有對(duì)同步數(shù)據(jù)的ack收恢,直接再發(fā)一個(gè)NEWLEADER包給follower武学,包括了sid,zxid派诬,仲裁器等信息劳淆。然后阻塞等待follower對(duì)NEWLEADER包的ack響應(yīng)達(dá)到大多數(shù)。轉(zhuǎn)3.8
- 大多數(shù)follower響應(yīng)后默赂,leader會(huì)發(fā)送UPTODATE包沛鸵,表示數(shù)據(jù)已經(jīng)都同步好了,集群服務(wù)的準(zhǔn)備工作都完成了,可以對(duì)client提供服務(wù)了曲掰。轉(zhuǎn)3.9.
- 假如當(dāng)前peer是Learner中的Follower:
- Follower會(huì)根據(jù)選舉時(shí)的投票和配置信息疾捍,重新獲取leader的地址和通信端口,主動(dòng)連接leader栏妖,建立與leader之間的長連接乱豆。
- 發(fā)送第一個(gè)包,是將自己的sid吊趾,最大zxid等信息發(fā)給leader宛裕。
- 轉(zhuǎn)上面步驟2.3,2.4
- Follower在收到leader發(fā)來的最新的epoch后论泛,與自己當(dāng)前的epoch比較揩尸,
- 如果leader的epoch最大,就用這個(gè)epoch作為ack響應(yīng)的epoch屁奏。
- 如果自己用的比leader的大岩榆,表示有問題,用-1作為ack響應(yīng)的epoch坟瓢。
- Follower會(huì)將sid勇边,使用的epoch等信息作為epoch的ack包發(fā)給leader。
- 轉(zhuǎn)2.6
- follower收到同步方式和需要同步的數(shù)據(jù)后折联,會(huì)完成數(shù)據(jù)同步粒褒,完成同步后,不會(huì)有對(duì)同步結(jié)果的ack包發(fā)給leader诚镰,就是接著等著讀取leader的數(shù)據(jù)怀浆,轉(zhuǎn)2.9
- 收到leader發(fā)來的NEWLEADER包,使用包里的仲裁器和epoch等數(shù)據(jù)作為自己的數(shù)據(jù)怕享。發(fā)送ack包給leader执赡。轉(zhuǎn)2.10
- 因?yàn)樵谕綌?shù)據(jù)階段,會(huì)用while循環(huán)讀取leader發(fā)來的數(shù)據(jù)函筋,使用的bio沙合,無消息的時(shí)候,流本身就有阻塞的作用跌帐。正是使用此處流的阻塞首懈,來保證在learner啟動(dòng)初始化階段,只要沒有收到leader發(fā)來的UPTODATE包谨敛,就一直循環(huán)讀取或者阻塞等待究履,不會(huì)對(duì)外提供服務(wù),處理client的請(qǐng)求脸狸。收到UPTODATE包后跳出while循環(huán)最仑,解除阻塞藐俺。進(jìn)入讀取和處理client數(shù)據(jù)的while循環(huán)中。
4. 正式對(duì)client提供服務(wù)
- leader:
- leader線程會(huì)不斷的循環(huán)泥彤,檢測(cè)每個(gè)LearnerHandler線程是否存活欲芹,定時(shí)的ping每一個(gè)learner,判斷與Learner的ping是否超時(shí)等吟吝,以此來判斷某個(gè)learner是否存活菱父,再根據(jù)這個(gè)判斷是否還能夠達(dá)成多數(shù)follower的有效仲裁,如果不能有效仲裁了剑逃,就退出循環(huán)浙宜,重新選舉。
- follower:
- follower
- 處理器都是繼承自Thred蛹磺,本身是一個(gè)線程梆奈,說說通用的處理器流程:
-
CommitProcessor:
- 對(duì)服務(wù)器來說,只會(huì)啟動(dòng)一個(gè)CommitProcessor線程称开。
- 在線程第一次運(yùn)行時(shí),因?yàn)閝ueuedRequests和committedRequests都為空乓梨,所以線程阻塞wait鳖轰。
- 然后上一個(gè)處理器調(diào)用CommitProcessor.processRequest()提交請(qǐng)求到queuedRequests隊(duì)列中。
- 因?yàn)榇藭r(shí)是第一個(gè)請(qǐng)求扶镀,無待commit請(qǐng)求蕴侣,所以會(huì)調(diào)用notifyAll喚醒阻塞的所有線程。
- 線程喚醒后臭觉,因?yàn)閝ueuedRequests不為空昆雀,所以跳出while循環(huán),向下走蝠筑。此時(shí)下一個(gè)while循環(huán)為true狞膘,因?yàn)闆]有待commit或者正在commit的請(qǐng)求,而且從queuedRequests拉數(shù)據(jù)不為空什乙。
- 判斷這個(gè)請(qǐng)求是不是需要被commit的請(qǐng)求挽封,即寫請(qǐng)求。
- 如果是讀請(qǐng)求臣镣,更新正在處理請(qǐng)求的計(jì)數(shù)numRequestsProcessing+1辅愿,根據(jù)請(qǐng)求的sessionId對(duì)線程數(shù)組取余,拿到執(zhí)行線程忆某,封裝成任務(wù)点待,提交給該線程,線程內(nèi)操作就是將該讀請(qǐng)求提交給下一個(gè)執(zhí)行器弃舒。即調(diào)用nextProcessor.processRequest(request);一般就是走到FinalRequestProcessor癞埠,會(huì)根據(jù)路徑取對(duì)應(yīng)內(nèi)存DataTree的node節(jié)點(diǎn),將數(shù)據(jù)封裝成響應(yīng),寫回給客戶端燕差≡馑瘢回到CommitProcessor后會(huì)執(zhí)行finally塊,里面會(huì)將currentlyCommitting置為null徒探,將正在處理請(qǐng)求的計(jì)數(shù)numRequestsProcessing-1瓦呼。相當(dāng)于該numRequestsProcessing最大值為1。
- 如果是寫請(qǐng)求测暗,則set為待commit請(qǐng)求央串,賦值給nextPending變量。因?yàn)橛辛舜齝ommit請(qǐng)求碗啄,所以再走while循環(huán)條件不成立质和,跳出while,向下走稚字。
- 因?yàn)閏ommittedRequests為空饲宿,所以if條件不成立,走回while循環(huán)胆描,繼續(xù)阻塞wait瘫想。
- 假如后來再有一個(gè)寫請(qǐng)求入隊(duì)queuedRequests。根據(jù)上面說的邏輯昌讲,會(huì)賦值給nextPending變量国夜。
- 假如leader對(duì)寫的請(qǐng)求投票結(jié)束,確定要commit該數(shù)據(jù)短绸,會(huì)給Follower發(fā)送COMMIT,給Observer發(fā)送INFORM命令车吹。處理器鏈會(huì)調(diào)用CommitProcessor.commit,將待commit請(qǐng)求入隊(duì)committedRequests醋闭。調(diào)用notifyAll喚醒阻塞線程窄驹。
- 此時(shí)因?yàn)閏ommittedRequests不為空,并且也沒有正在commit的請(qǐng)求证逻,所以跳出while馒吴,向下走。
- 從committedRequests出隊(duì)該已確定要被提交的請(qǐng)求瑟曲,set為正在commit的請(qǐng)求饮戳,即賦值給currentlyCommitting。然后將待commit變量置為null洞拨,即nextPending為null扯罐。
- 更新正在處理請(qǐng)求的計(jì)數(shù)numRequestsProcessing+1,根據(jù)請(qǐng)求的sessionId對(duì)線程數(shù)組取余烦衣,拿到執(zhí)行線程歹河,封裝成任務(wù)掩浙,提交給該線程,線程內(nèi)操作就是將該讀請(qǐng)求提交給下一個(gè)執(zhí)行器秸歧。即調(diào)用nextProcessor.processRequest(request);一般就是走到FinalRequestProcessor厨姚。FinalRequestProcessor會(huì)將寫請(qǐng)求的數(shù)據(jù),寫到內(nèi)存DataTree上键菱,寫成功響應(yīng)給client谬墙。
- 回到CommitProcessor后會(huì)執(zhí)行finally塊,里面會(huì)將currentlyCommitting置為null经备,將正在處理請(qǐng)求的計(jì)數(shù)numRequestsProcessing-1拭抬。相當(dāng)于該numRequestsProcessing最大值為1。
- 假如在有一個(gè)寫請(qǐng)求處于待commit狀態(tài)侵蒙,且CommitProcessor線程處于wait狀態(tài)造虎,此時(shí)有一個(gè)讀請(qǐng)求或者寫請(qǐng)求被上一個(gè)處理器塞到了queuedRequests中,因?yàn)镃ommitProcessor線程阻塞等待leader的提交命令來喚醒纷闺,所以在沒有l(wèi)eader的commit命令之前算凿,都是阻塞的,不會(huì)從queuedRequests隊(duì)列中去拿后面的讀或者寫請(qǐng)求犁功。相當(dāng)于沒有寫的情況下氓轰,可以同時(shí)有多個(gè)讀,只要有寫的情況下波桩,只能有一個(gè)寫。很類似于讀寫鎖请敦。
-
CommitProcessor:
1. **FinalRequestProcessor:**
1. 對(duì)服務(wù)器來說镐躲,只會(huì)啟動(dòng)一個(gè)FinalRequestProcessor線程。
2. 根據(jù)request.hdr字段來判斷侍筛,如果有值證明是寫請(qǐng)求萤皂,如果為null,證明是讀請(qǐng)求匣椰。如果是寫請(qǐng)求裆熙,將結(jié)果應(yīng)用在內(nèi)存DataTree上,觸發(fā)事件通知禽笑,返回執(zhí)行結(jié)果入录。
3. 如果是寫請(qǐng)求,還會(huì)刪除對(duì)應(yīng)的outstandingChanges數(shù)據(jù)佳镜,將請(qǐng)求放到committedLog已提交列表中僚稿,方便與follower快速同步數(shù)據(jù)。
4. 獲取請(qǐng)求中的客戶端信息蟀伸,如果為null蚀同,證明請(qǐng)求是leader發(fā)來的commit命令等缅刽,不是直連的客戶端發(fā)給自己的寫請(qǐng)求,不需要繼續(xù)執(zhí)行蠢络。
5. 如果客戶端信息不為null衰猛,證明是客戶端直連該follower發(fā)的寫請(qǐng)求,根據(jù)request.type判斷是什么命令刹孔,構(gòu)造響應(yīng)啡省,寫回給客戶端。
1. 比如是getData請(qǐng)求芦疏,那么會(huì)取路徑對(duì)應(yīng)的Node數(shù)據(jù)冕杠,檢查用戶的讀權(quán)限,將數(shù)據(jù)和狀態(tài)封裝成響應(yīng)寫回給請(qǐng)求方酸茴。
2. 如果是create請(qǐng)求分预,因?yàn)樯厦嬉呀?jīng)更新過內(nèi)存DataTree的數(shù)據(jù)了,此時(shí)只需要返回路徑和狀態(tài)給請(qǐng)求方就可以了薪捍。
2. **SyncRequestProcessor:**
1. 對(duì)服務(wù)器來說笼痹,只會(huì)啟動(dòng)一個(gè)SyncRequestProcessor線程。
2. SyncRequestProcessor在3種不同情況下使用:
1. Leader - 將請(qǐng)求同步到磁盤酪穿,并將其轉(zhuǎn)發(fā)到AckRequestProcessor凳干,后者將ack發(fā)回給自己。
2. Follower - 將請(qǐng)求同步到磁盤被济,并將請(qǐng)求轉(zhuǎn)發(fā)到SendAckRequestProcessor救赐,后者將數(shù)據(jù)包發(fā)送到leader。 SendAckRequestProcessor是可刷新的只磷,這使我們能夠?qū)⑼扑蛿?shù)據(jù)包強(qiáng)制發(fā)送到leader经磅。
3. Observer - 將提交的請(qǐng)求同步到磁盤(作為INFORM數(shù)據(jù)包接收)。 它永遠(yuǎn)不會(huì)將確認(rèn)發(fā)送回給leader钮追,因此nextProcessor將為null预厌。 因?yàn)樗话峤坏膖xns,所以這改變了觀察者上txnlog的語義元媚。
3. 該處理器的執(zhí)行時(shí)機(jī)和主要作用:
1. 對(duì)leader來說:
1. 對(duì)于一個(gè)寫操作轧叽,先經(jīng)過預(yù)處理器,封裝好要寫的數(shù)據(jù)刊棕,然后提交給下一個(gè)處理器提案處理器
2. 提案處理器將要寫的數(shù)據(jù)封裝到提案請(qǐng)求中炭晒,發(fā)給所有follower
3. 然后執(zhí)行當(dāng)前同步處理器,將這個(gè)寫請(qǐng)求寫到事務(wù)日志磁盤緩沖區(qū)甥角,緩沖區(qū)對(duì)象LinkedList toFlush腰埂,大小一般為1000,超過該值會(huì)執(zhí)行flush磁盤操作蜈膨。
2. 對(duì)follower來說:
1. 接到leader發(fā)來的提案請(qǐng)求后屿笼,follower基本就是先執(zhí)行該處理器牺荠,即將這個(gè)寫請(qǐng)求寫到事務(wù)日志磁盤緩沖區(qū)。
3. 對(duì)leader和follower來說驴一,在寫完一定數(shù)量的寫請(qǐng)求后休雌,會(huì)隨機(jī)觸發(fā)拍快照操作。
4. 只要能寫完事務(wù)緩存肝断,沒有異常中斷杈曲,就表示贊同提案,調(diào)下一個(gè)處理器發(fā)送應(yīng)答給leader胸懈。 相當(dāng)于只要發(fā)了應(yīng)答担扑,就表示贊同提案,出現(xiàn)異橙で或者錯(cuò)誤涌献、網(wǎng)絡(luò)問題等導(dǎo)致沒有發(fā)應(yīng)答給leader,就表示不贊同首有。 沒有中間態(tài)度燕垃,而且只要能將事務(wù)寫到磁盤,就一定要投贊同票井联。
- 處理器都是繼承自Thred卜壕,不同角色服務(wù)器的處理器鏈:
- Observer:
- ObserverRequestProcessor
- client.getData請(qǐng)求到ObserverRequestProcessor,入隊(duì)再從隊(duì)列取出烙常,調(diào)用nextProcessor.processRequest轴捎,交給下一個(gè)處理器,即CommitProcessor處理蚕脏。
- 如果是寫請(qǐng)求侦副,將請(qǐng)求放到下一個(gè)處理器的隊(duì)列后,會(huì)轉(zhuǎn)發(fā)該寫請(qǐng)求給leader蝗锥。
- CommitProcessor
- FinalRequestProcessor
- ObserverRequestProcessor
- Follower:
- FollowerRequestProcessor
- 和ObserverRequestProcessor的邏輯差不多跃洛。
- CommitProcessor
- FinalRequestProcessor
- 還有獨(dú)立的SyncRequestProcessor -> SendAckRequestProcessor
- SendAckRequestProcessor:
- 向leader發(fā)送ACK率触。
- FollowerRequestProcessor
- Leader:
- LeaderRequestProcessor
- 負(fù)責(zé)執(zhí)行本地會(huì)話升級(jí)终议。 只有直接提交給領(lǐng)導(dǎo)者的請(qǐng)求才能通過此處理器。即客戶端直連的請(qǐng)求才會(huì)走這個(gè)處理器葱蝗。
- PrepRequestProcessor
- 首先是單線程穴张,不斷從LinkedBlockingQueue中拿請(qǐng)求數(shù)據(jù),根據(jù)request.type判斷屬于客戶端的哪一種請(qǐng)求两曼。
- 如果是新增節(jié)點(diǎn)皂甘、修改節(jié)點(diǎn)等寫操作,會(huì)調(diào)用zks.getNextZxid()悼凑,拿到遞增的zxid(事務(wù)id)偿枕。
- 然后根據(jù)內(nèi)存樹DataTree的現(xiàn)有數(shù)據(jù)璧瞬,來計(jì)算新的數(shù)據(jù)path名稱,版本號(hào)渐夸,acl都應(yīng)該是什么嗤锉,封裝數(shù)據(jù),沿著處理器鏈向下傳遞墓塌。
- 注意這里的操作不會(huì)對(duì)內(nèi)存樹有影響瘟忱,也就是操作結(jié)果不會(huì)在內(nèi)存樹生效。只是得到數(shù)據(jù)應(yīng)該是什么樣苫幢,比如創(chuàng)建的節(jié)點(diǎn)名稱是什么等等访诱。
- ProposalRequestProcessor(包含獨(dú)立運(yùn)行的線程SyncRequestProcessor->AckRequestProcessor)
- 這個(gè)處理器將請(qǐng)求分成兩種進(jìn)行處理,
- Follower轉(zhuǎn)發(fā)過來的"sync"同步請(qǐng)求韩肝。
- 剩下的其他請(qǐng)求,主要是其他learner server端轉(zhuǎn)過來的寫請(qǐng)求触菜,以及client的讀請(qǐng)求。
- 先說如何處理Follower轉(zhuǎn)發(fā)過來的"sync"同步請(qǐng)求:
- zk不能保證每個(gè)服務(wù)實(shí)例在每個(gè)時(shí)間都具有相同的ZooKeeper數(shù)據(jù)視圖伞梯。由于網(wǎng)絡(luò)延遲之類的因素玫氢, 一個(gè)客戶端可能會(huì)在另一客戶端收到更改通知之前執(zhí)行更新∶战耄考慮兩個(gè)客戶端A和B的情況漾峡, 如果客戶端A將znode/a的值從0設(shè)置為1,然后告訴客戶端B讀取/a喻旷,則客戶端B可能讀取舊值0生逸,具體取決于連的哪個(gè)服務(wù)器。 如果客戶端A和客戶端B讀取相同的值很重要且预,則客戶端B應(yīng)該在執(zhí)行讀取之前從ZooKeeper API方法中調(diào)用sync()方法槽袄。 sync是使得client當(dāng)前連接著的ZooKeeper服務(wù)器,和ZooKeeper的Leader節(jié)點(diǎn)同步(sync)一下數(shù)據(jù)锋谐。 用法一般是同一線程串行執(zhí)行遍尺,先調(diào) zookeeper.sync("關(guān)注的路徑path","可阻塞的回調(diào)對(duì)象","回調(diào)上下文對(duì)象"), 調(diào)用完后涮拗,會(huì)再調(diào)用"可阻塞的回調(diào)對(duì)象.await等阻塞方法"乾戏,等待Leader和當(dāng)前Follower數(shù)據(jù)同步完成,返回響應(yīng)三热, 然后就可以調(diào)用zookeeper.getData("關(guān)注的路徑path")了鼓择,可以保證在該路徑的數(shù)據(jù)上,獲取到和Leader一致的視圖就漾。 具體可以參考o(jì)rg.apache.zookeeper.cli.SyncCommand#exec()和org.apache.zookeeper.server.quorum .EphemeralNodeDeletionTest#testEphemeralNodeDeletion()呐能。
- 當(dāng)follower收到到客戶端發(fā)來的sync請(qǐng)求時(shí),會(huì)將這個(gè)請(qǐng)求添加到一個(gè)pendingSyncs隊(duì)列里抑堡,然后將這個(gè)請(qǐng)求發(fā)送給leader摆出, 直到收到leader的Leader.SYNC響應(yīng)消息時(shí)朗徊,才將這個(gè)請(qǐng)求從pendingSyncs隊(duì)列里移除,并commit這個(gè)請(qǐng)求偎漫。
- 當(dāng)Leader收到一個(gè)sync請(qǐng)求時(shí)荣倾,如果leader當(dāng)前沒有待commit的決議,那么leader會(huì)立即發(fā)送一個(gè)Leader.SYNC消息給follower骑丸。 否則舌仍,leader會(huì)等到當(dāng)前最后一個(gè)待commit的決議完成后,再發(fā)送Leader.SYNC消息給Follower通危。
- 其實(shí)這里面有一個(gè)隱含的邏輯铸豁,leader和follower之間的消息通信,是嚴(yán)格按順序來發(fā)送的(TCP保證)菊碟, 因此节芥,當(dāng)follower接收到Leader.SYNC消息時(shí), 說明follower也一定接收到了leader之前(在leader接收到sync請(qǐng)求之前)發(fā)送的所有提案或者commit消息逆害。 這樣就可以確保follower內(nèi)存中的數(shù)據(jù)和leader是同步的了头镊。客戶端就能從連接的follower讀取到最新的數(shù)據(jù)了魄幕。
- 剩下的其他請(qǐng)求相艇,主要是其他learner server端轉(zhuǎn)過來的寫請(qǐng)求,以及client的讀請(qǐng)求纯陨。 交個(gè)下一個(gè)處理器執(zhí)行坛芽,即CommitProcessor。
- AckRequestProcessor:它只是將前一個(gè)處理器的請(qǐng)求作為ACK轉(zhuǎn)發(fā)給leader翼抠。相當(dāng)于對(duì)于寫數(shù)據(jù)咙轩,leader自己給自己投贊同票。
- 這個(gè)處理器將請(qǐng)求分成兩種進(jìn)行處理,
- CommitProcessor
- Leader.ToBeAppliedRequestProcessor
- 該請(qǐng)求處理器僅維護(hù)toBeApplied列表阴颖。將已確定提交活喊,待應(yīng)用到內(nèi)存樹的請(qǐng)求轉(zhuǎn)發(fā)給下一個(gè)處理器執(zhí)行。
- FinalRequestProcessor
- LeaderRequestProcessor
- Observer: