Zookeeper3.7原始碼剖析
能力目標
- 能基于Maven匯入最新版Zookeeper原始碼
- 能說出Zookeeper單機啟動流程
- 理解Zookeeper默認通信中4個執行緒的作用
- 掌握Zookeeper業務處理原始碼處理流程
- 能夠在Zookeeper原始碼中Debug測驗通信程序
1 Zookeeper原始碼匯入

Zookeeper是一個高可用的分布式資料管理和協調框架,并且能夠很好的保證分布式環境中資料的一致性,在越來越多的分布式系,在越來越多的分布式系統(Hadoop、HBase、Kafka)中,Zookeeper都作為核心組件使用,

我們當前課程主要是研究Zookeeper原始碼,需要將Zookeeper工程匯入到IDEA中,老版的zk是通過ant進行編譯的,但最新的zk(3.7)原始碼中已經沒了build.xml,而多了pom.xml,也就是說構建方式由原先的Ant變成了Maven,原始碼下下來后,直接編譯、運行是跑不起來的,有一些配置需要調整,
1.1 工程匯入
Zookeeper各個版本原始碼下載地址https://github.com/apache/zookeeper,我們可以在該倉庫下選擇不同的版本,我們選擇最新版本,當前最新版本為3.7,如下圖:

找到專案下載地址,我們選擇https地址,并復制該地址,通過該地址把專案匯入到IDEA中,

點擊IDEA的VSC>Checkout from Version Controller>GitHub,操作如下圖:

克隆專案到本地:

專案匯入本地后,效果如下:

專案運行的時候,缺一個版本物件,創建org.apache.zookeeper.version.Info,代碼如下:
public interface Info {
public static final int MAJOR=3;
public static final int MINOR=4;
public static final int MICRO=6;
public static final String QUALIFIER=null;
public static final int REVISION=-1;
public static final String REVISION_HASH = "1";
public static final String BUILD_DATE="2020-12-03 09:29:06";
}
1.2 Zookeeper原始碼錯誤解決
在zookeeper-server中找到org.apache.zookeeper.server.quorum.QuorumPeerMain并啟動該類,啟動前做如下配置:

啟動的時候會會報很多錯誤,比如缺包、缺物件,如下幾幅圖:



為了解決上面的錯誤,我們需要手動引入一些包,pom.xml引入如下依賴:
<!--引入依賴-->
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
<version>1.1.7.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
</dependency>
1.3 Zookeeper命令(自學)
我們要想學習Zookeeper,需要先學會使用Zookeeper,它有很多豐富的命令,借助這些命令可以深入理解Zookeeper,我們啟動原始碼中的客戶端就可以使用Zookeeper相關命令,
啟動客戶端org.apache.zookeeper.ZooKeeperMain,如下圖:

啟動后,日志如下:

1)節點串列:ls /
ls /
[dubbo, zookeeper]
ls /dubbo
[com.itheima.service.CarService]
2)查看節點狀態:stat /dubbo
stat /dubbo
cZxid = 0x3
ctime = Thu Dec 03 09:19:29 CST 2020
mZxid = 0x3
mtime = Thu Dec 03 09:19:29 CST 2020
pZxid = 0x4
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 13
numChildren = 1
節點資訊引數說明如下:
| key | value |
|---|---|
cZxid = 0x3 |
創建節點時的事務ID |
ctime = Thu Dec 03 09:19:29 CST 2020 |
最后修改節點時的事務ID |
mZxid = 0x31 |
最后修改節點時的事務ID |
mtime = Sat Mar 16 15:38:34 CST 2019 |
最后修改節點時的時間 |
pZxid = 0x31 |
表示該節點的子節點串列最后一次修改的事務ID,添加子節點或洗掉子節點就會影響子節點串列,但是修改子節點的資料內容則不影響該ID(注意,只有子節點串列變更了才會變更pzxid,子節點內容變更不會影響pzxid) |
cversion = 0 |
子節點版本號,子節點每次修改版本號加1 |
dataVersion = 0 |
資料版本號,資料每次修改該版本號加1 |
aclVersion = 0 |
權限版本號,權限每次修改該版本號加1 |
ephemeralOwner = 0x0 |
創建該臨時節點的會話的sessionID,(如果該節點是持久節點,那么這個屬性值為0) |
dataLength = 22 |
該節點的資料長度 |
numChildren = 0 |
該節點擁有子節點的數量(只統計直接子節點的數量) |
3)創建節點:create /dubbo/code java
create /dubbo/code java
Created /dubbo/code
其中code表示節點,java表示節點下的內容,
4)查看節點資料:get /dubbo/code
get /dubbo/code
java
5)洗掉節點:delete /dubbo/code || deleteall /dubbo/code
洗掉沒有子節點的節點:delete /dubbo/code
洗掉所有子節點:deleteall /dubbo/code
6)歷史操作命令:history
history
1 - ls /dubbo
2 - ls /dubbo/code
3 - get /dubbo/code
4 - get /dubbo/code
5 - create /dubbo/code java
6 - get /dubbo/code
7 - get /dubbo/code
8 - delete /dubbo/code
9 - get /dubbo/code
10 - listquota path
11 - history
1.4 Zookeeper分析工具
Zookeeper安裝比較方便,在安裝一個集群以后,查看資料卻比較麻煩,下面介紹Zookeeper的資料查看工具——ZooInspector,
下載地址:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip
下載壓縮包后,解壓后,我們需要運行zookeeper-dev-ZooInspector.jar:

輸入賬號密碼,就可以連接Zookeeper了,如下圖:

連接后,Zookeeper資訊如下:

節點操作:增加節點、修改節點、洗掉節點

1.5 Zookeeper案例應用

我們將資料中工程\dubbo工程匯入到IDEA中,上圖是他們的呼叫關系,那么問題來了:
- 生產者向Zookeeper注冊服務資訊,Zookeeper把資料存哪兒了?
- 集群環境下,如果某個節點資料變更了,Zookeeper如何監聽到的?
- 集群環境下各個節點的資料如何同步?
- 如果某個節點掛了,Zookeeper如何選舉呢?
- ........

帶著上面的疑問,我們開始研究Zookeeper原始碼,
2 ZK服務啟動流程原始碼剖析
ZooKeeper可以以standalone、分布式的方式部署,standalone模式下只有一臺機器作為服務器,ZooKeeper會喪失高可用特性,分布式是使用多個機器,每臺機器上部署一個ZooKeeper服務器,即使有服務器宕機,只要少于半數,ZooKeeper集群依然可以正常對外提供服務,集群狀態下Zookeeper是具備高可用特性,
我們接下來對ZooKeeper以standalone模式啟動以及集群模式做一下原始碼分析,
2.1 ZK單機/集群啟動流程

如上圖,上圖是Zookeeper單機/集群啟動流程,每個細節所做的事情都在上圖有說明,我們接下來按照流程圖對原始碼進行分析,
2.2 ZK啟動入口分析
啟動入口類:QuorumPeerMain
該類是zookeeper單機/集群的啟動入口類,是用來加載配置、啟動QuorumPeer(選舉相關)執行緒、創建ServerCnxnFactory等,我們可以把代碼切換到該類的主方法(main)中,從該類的主方法開始分析,main方法代碼分析如下:

上面main方法雖然只是做了初始化配置,但呼叫了initializeAndRun()方法,initializeAndRun()方法中會根據配置來決定啟動單機Zookeeper還是集群Zookeeper,原始碼如下:

如果啟動單機版,會呼叫ZooKeeperServerMain.main(args);,如果啟動集群版,會呼叫QuorumPeerMain.runFromConfig(config);,我們接下來對單機版啟動做原始碼詳細剖析,集群版在后面章節中講解選舉機制時詳細講解,
2.3 ZK單機啟動原始碼剖析
針對ZK單機啟動原始碼方法呼叫鏈,我們已經提前做了一個方法呼叫關系圖,我們講解ZK單機啟動原始碼,將和該圖進行一一匹對,如下圖:

1)單機啟動入口
按照上面的原始碼分析,我們找到ZooKeeperServerMain.main(args)方法,該方法呼叫了ZooKeeperServerMain的initializeAndRun方法,在initializeAndRun方法中執行初始化操作,并運行Zookeeper服務,main方法如下:

2)組態檔決議
initializeAndRun()方法會注冊JMX,同時決議zoo.cfg組態檔,并呼叫runFromConfig()方法啟動Zookeeper服務,原始碼如下:

3)單機啟動主流程
runFromConfig方法是單機版啟動的主要方法,該方法會做如下幾件事:
1:初始化各類運行指標,比如一次提交資料最大花費多長時間、批量同步資料大小等,
2:初始化權限操作,例如IP權限、Digest權限,
3:創建事務日志操作物件,Zookeeper中每次增加節點、修改資料、洗掉資料都是一次事務操作,都會記錄日志,
4:定義Jvm監控變數和常量,例如警告時間、告警閥值次數、提示閥值次數等,
5:創建ZookeeperServer,這里只是創建,并不在ZooKeeperServerMain類中啟動,
6:啟動Zookeeper的控制臺管理物件AdminServer,該物件采用Jetty啟動,
7:創建ServerCnxnFactory,該物件其實是Zookeeper網路通信物件,默認使用了NIOServerCnxnFactory,
8:在ServerCnxnFactory中啟動ZookeeperServer服務,
9:創建并啟動ContainerManager,該物件通過Timer定時執行,清理過期的容器節點和TTL節點,執行周期為分鐘,
10:防止主執行緒結束,阻塞主執行緒,
方法原始碼如下:

4)網路通信物件創建
上面方法在創建網路通信物件的時候呼叫了ServerCnxnFactory.createFactory(),該方法其實是根據系統配置創建Zookeeper通信組件,可選的有NIOServerCnxnFactory(默認)和NettyServerCnxnFactory,關于通信物件我們會在后面進行詳細講解,該方法原始碼如下:

5)單機啟動
cnxnFactory.startup(zkServer);方法其實就是啟動了ZookeeperServer,它呼叫NIOServerCnxnFactory的startup方法,該方法中會呼叫ZookeeperServer的startup方法啟動服務,ZooKeeperServerMain運行到shutdownLatch.await();主執行緒會阻塞住,原始碼如下:

啟動后,日志如下:

3 ZK網路通信原始碼剖析
Zookeeper作為一個服務器,自然要與客戶端進行網路通信,如何高效的與客戶端進行通信,讓網路IO不成為ZooKeeper的瓶頸是ZooKeeper急需解決的問題,ZooKeeper中使用ServerCnxnFactory管理與客戶端的連接,其有兩個實作,一個是NIOServerCnxnFactory,使用Java原生NIO實作;一個是NettyServerCnxnFactory,使用netty實作;使用ServerCnxn代表一個客戶端與服務端的連接,
從單機版啟動中可以發現Zookeeper默認通信組件為NIOServerCnxnFactory,他們和ServerCnxnFactory的關系如下圖:

3.1 NIOServerCnxnFactory作業流程
一般使用Java NIO的思路為使用1個執行緒組監聽OP_ACCEPT事件,負責處理客戶端的連接;使用1個執行緒組監聽客戶端連接的OP_READ和OP_WRITE事件,處理IO事件(netty也是這種實作方式).
但ZooKeeper并不是如此劃分執行緒功能的,NIOServerCnxnFactory啟動時會啟動四類執行緒:
1:accept thread:該執行緒接收來自客戶端的連接,并將其分配給selector thread(啟動一個執行緒),
2:selector thread:該執行緒執行select(),由于在處理大量連接時,select()會成為性能瓶頸,因此啟動多個selector thread,使用系統屬性zookeeper.nio.numSelectorThreads配置該類執行緒數,默認個數為 核心數/2,
3:worker thread:該執行緒執行基本的套接字讀寫,使用系統屬性zookeeper.nio.numWorkerThreads配置該類執行緒數,默認為核心數?2核心數?2.如果該類執行緒數為0,則另外啟動一執行緒進行IO處理,見下文worker thread介紹,
4:connection expiration thread:若連接上的session已過期,則關閉該連接,
這四個執行緒在NIOServerCnxnFactory類上有說明,如下圖:

ZooKeeper中對執行緒需要處理的作業做了更細的拆分,解決了有大量客戶端連接的情況下,selector.select()會成為性能瓶頸,將selector.select()拆分出來,交由selector thread處理,
3.2 NIOServerCnxnFactory原始碼
NIOServerCnxnFactory的原始碼分析我們將按照上面所介紹的4個執行緒實作相關分析,并實作資料操作,在程式中獲取指定資料,
3.2.1 AcceptThread剖析
為了讓大家更容易理解AcceptThread,我們把它的結構和方法呼叫關系畫了一個詳細的流程圖,如下圖:

在NIOServerCnxnFactory類中有一個AccpetThread執行緒,為什么說它是一個執行緒?我們看下它的繼承關系:AcceptThread > AbstractSelectThread > ZooKeeperThread > Thread,該執行緒接收來自客戶端的連接,并將其分配給selector thread(啟動一個執行緒),
該執行緒執行流程:run執行selector.select(),并呼叫doAccept()接收客戶端連接,因此我們可以著重關注doAccept()方法,該類原始碼如下:

doAccept()方法用于處理客戶端鏈接,當客戶端鏈接Zookeeper的時候,首先會呼叫該方法,呼叫該方法執行程序如下:
1:和當前服務建立鏈接,
2:獲取遠程客戶端計算機地址資訊,
3:判斷當前鏈接是否超出最大限制,
4:調整為非阻塞模式,
5:輪詢獲取一個SelectorThread,將當前鏈接分配給該SelectorThread,
6:將當前請求添加到該SelectorThread的acceptedQueue中,并喚醒該SelectorThread,
doAccept()方法原始碼如下:

上面代碼中addAcceptedConnection方法如下:

我們把專案中的分布式案例服務啟動,可以看到如下日志列印:
AcceptThread----------鏈接服務的IP:127.0.0.1
3.2.2 SelectorThread剖析
同樣為了更容易梳理SelectorThread,我們也把它的結構和方法呼叫關系梳理成了流程圖,如下圖:

該執行緒的主要作用是從Socket讀取資料,并封裝成workRequest,并將workRequest交給workerPool作業執行緒池處理,同時將acceptedQueue中未處理的鏈接取出,并未每個鏈接系結OP_READ讀事件,并封裝對應的背景關系物件NIOServerCnxn,SelectorThread的run方法如下:

run()方法中會呼叫select(),而select()中的核心呼叫地方是handleIO(),我們看名字其實就知道這里是處理客戶端請求的資料,但客戶端請求資料并非在SelectorThread執行緒中處理,我們接著看handleIO()方法,

handleIO()方法會封裝當前SelectorThread為IOWorkRequest,并將IOWorkRequest交給workerPool來調度,而workerPool調度才是讀資料的開始,原始碼如下:

3.2.3 WorkerThread剖析
WorkerThread相比上面的執行緒而言,呼叫關系頗為復雜,設計到了多個物件方法呼叫,主要用于處理IO,但并未對資料做出處理,資料處理將有業務鏈物件RequestProcessor處理,呼叫關系圖如下:

ZooKeeper中通過WorkerService管理一組worker thread執行緒,前面我們在看SelectorThread的時候,能夠看到workerPool的schedule方法被執行,如下圖:

我們跟蹤workerPool.schedule(workRequest);可以發現它呼叫了WorkerService.schedule(workRequest) > WorkerService.schedule(WorkRequest, long),該方法創建了一個新的執行緒ScheduledWorkRequest,并啟動了該執行緒,原始碼如下:

ScheduledWorkRequest實作了Runnable介面,并在run()方法中呼叫了IOWorkRequest中的doWork方法,在該方法中會呼叫doIO執行IO資料處理,原始碼如下:

IOWorkRequest的doWork原始碼如下:

接下來的呼叫鏈路比較復雜,我們把核心步驟列出,在能直接看到資料讀取的地方詳細分析原始碼,上面方法呼叫鏈路:NIOServerCnxn.doIO()>readPayload()>readRequest() >ZookeeperServer.processPacket() ,最后一步方法是獲取核心資料的地方,我們可以修改下代碼讀取資料:

添加測驗代碼如下:
//==========測驗 Start===========
//定義接收輸入流物件(輸出流)
ByteArrayOutputStream os = new ByteArrayOutputStream();
//將網路輸入流讀取到輸出流中
byte[] buffer = new byte[1024];
int len=0;
while ((len=bais.read(buffer))!=-1){
os.write(buffer,0,len);
}
String result = new String(os.toByteArray(),"UTF-8");
System.out.println("processPacket---------------讀到的資料:"+result);
//==========測驗 End===========
我們啟動客戶端創建一個demo節點,并添加資料為 abcdefg
create /demo abcdefg
控制臺資料如下:

測驗完成后,不要忘了將該測驗注釋掉,我們可以執行其他增刪改查操作,可以輸出RequestHeader.type查看操作型別,操作型別代碼在ZooDefs中有標識,常用的操作型別如下:
int create = 1;
int delete = 2;
int exists = 3;
int getData = https://www.cnblogs.com/jiagooushi/p/4;
int setData = 5;
int getACL = 6;
int setACL = 7;
int getChildren = 8;
int sync = 9;
int ping = 11;
2.3.4 ConnectionExpirerThread剖析
后臺啟動ConnectionExpirerThread清理執行緒清理過期的session,執行緒中無限回圈,執行作業如下:

2.3 ZK通信優劣總結
Zookeeper在通信方面默認使用了NIO,并支持擴展Netty實作網路資料傳輸,相比傳統IO,NIO在網路資料傳輸方面有很多明顯優勢:
1:傳統IO在處理資料傳輸請求時,針對每個傳輸請求生成一個執行緒,如果IO例外,那么執行緒阻塞,在IO恢復后喚醒處理執行緒,在同時處理大量連接時,會實體化大量的執行緒物件,每個執行緒的實體化和回收都需要消耗資源,jvm需要為其分配TLAB,然后初始化TLAB,最后系結執行緒,執行緒結束時又需要回收TLAB,這些都需要CPU資源,
2:NIO使用selector來輪詢IO流,內部使用poll或者epoll,以事件驅動形式來相應IO事件的處理,同一時間只需實體化很少的執行緒物件,通過對執行緒的復用來提高CPU資源的使用效率,
3:CPU輪流為每個執行緒分配時間片的形式,間接的實作單物理核處理多執行緒,當執行緒越多時,每個執行緒分配到的時間片越短,或者回圈分配的周期越長,CPU很多時間都耗費在了執行緒的切換上,執行緒切換包含執行緒上個執行緒資料的同步(TLAB同步),同步變數同步至主存,下個執行緒資料的加載等等,他們都是很耗費CPU資源的,
4:在同時處理大量連接,但活躍連接不多時,NIO的事件回應模式相比于傳統IO有著極大的性能提升,NIO還提供了FileChannel,以zero-copy的形式傳輸資料,相較于傳統的IO,資料不需要拷貝至用戶空間,可直接由物理硬體(磁盤等)通過內核緩沖區后直接傳遞至網關,極大的提高了性能,
5:NIO提供了MappedByteBuffer,其將檔案直接映射到記憶體(這里的記憶體指的是虛擬記憶體,并不是物理記憶體),能極大的提高IO吞吐能力,
ZK在使用NIO通信雖然大幅提升了資料傳輸能力,但也存在一些代碼詬病問題:
1:Zookeeper通信原始碼部分學習成本高,需要掌握NIO和多執行緒
2:多執行緒使用頻率高,消耗資源多,但性能得到提升
3:Zookeeper資料處理呼叫鏈路復雜,多處存在內部類,代碼結構不清晰,寫法比較經典
4 RequestProcessor處理請求原始碼剖析
zookeeper 的業務處理流程就像作業流一樣,其實就是一個單鏈表;在zookeeper啟動的時候,會確立各個節點的角色特性,即leader、follower和observer,每個角色確立后,就會初始化它的作業責任鏈;
4.1 RequestProcessor結構
客戶端請求過來,每次執行不同事務操作的時候,Zookeeper也提供了一套業務處理流程RequestProcessor,RequestProcessor的處理流程如下圖:

我們來看一下RequestProcessor初始化流程,ZooKeeperServer.setupRequestProcessors()方法原始碼如下:

它的創建步驟:
1:創建finalProcessor,
2:創建syncProcessor,并將finalProcessor作為它的下一個業務鏈,
3:啟動syncProcessor,
4:創建firstProcessor(PrepRequestProcessor),將syncProcessor作為firstProcessor的下一個業務鏈,
5:啟動firstProcessor,
syncProcessor創建時,將finalProcessor作為引數傳遞進來原始碼如下:

firstProcessor創建時,將syncProcessor作為引數傳遞進來原始碼如下:

PrepRequestProcessor/SyncRequestProcessor關系圖:

PrepRequestProcessor和SyncRequestProcessor的結構一樣,都是實作了Thread的一個執行緒,所以在這里初始化時便啟動了這兩個執行緒,
4.2 PrepRequestProcessor剖析
PrepRequestProcessor是請求處理器的第1個處理器,我們把之前的請求業務處理銜接起來,一步一步分析,ZooKeeperServer.processPacket()>submitRequest()>enqueueRequest()>RequestThrottler.submitRequest() ,我們來看下RequestThrottler.submitRequest()原始碼,它將當前請求添加到submittedRequests佇列中了,原始碼如下:

而RequestThrottler繼承了 ZooKeeperCriticalThread > ZooKeeperThread > Thread,也就是說當前RequestThrottler是個執行緒,我們看看它的run方法做了什么事,原始碼如下:

RequestThrottler呼叫了ZooKeeperServer.submitRequestNow()方法,而該方法又呼叫了firstProcessor的方法,原始碼如下:

ZooKeeperServer.submitRequestNow()方法呼叫了firstProcessor.processRequest()方法,而這里的firstProcessor就是初始化業務處理鏈中的PrepRequestProcessor,也就是說三個RequestProecessor中最先呼叫的是PrepRequestProcessor,
PrepRequestProcessor.processRequest()方法將當前請求添加到了佇列submittedRequests中,原始碼如下:

上面方法中并未從submittedRequests佇列中獲取請求,如何執行請求的呢,因為PrepRequestProcessor是一個執行緒,因此會在run中執行,我們查看run方法原始碼的時候發現它呼叫了pRequest()方法,pRequest()方法原始碼如下:

首先先執行pRequestHelper()方法,該方法是PrepRequestProcessor處理核心業務流程,主要是一些過濾操作,操作完成后,會將請求交給下一個業務鏈,也就是SyncRequestProcessor.processRequest()方法處理請求,
我們來看一下PrepRequestProcessor.pRequestHelper()方法做了哪些事,原始碼如下:

從上面原始碼可以看出PrepRequestProcessor.pRequestHelper()方法判斷了客戶端操作型別,但無論哪種操作型別幾乎都呼叫了pRequest2Txn()方法,我們來看看原始碼:

從上面代碼可以看出pRequest2Txn()方法主要做了權限校驗、快照記錄、事務資訊記錄相關的事,還并未涉及資料處理,也就是說PrepRequestProcessor其實是做了操作前權限校驗、快照記錄、事務資訊記錄相關的事,
我們DEBUG除錯一次,看看業務處理流程是否和我們上面所分析的一致,
添加節點:
create /zkdemo itheima
DEBUG測驗如下:
客戶端請求先經過ZooKeeperServer.submitRequestNow()方法,并呼叫firstProcessor.processRequest()方法,而firstProcessor=PrepRequestProcessor,如下圖:

進入PrepRequestProcessor.pRequest()方法,執行完pRequestHelper()方法后,開始執行下一個業務鏈的方法,而下一個業務鏈nextProcessor=SyncRequestProcessor,如下測驗圖:

4.3 SyncRequestProcessor剖析
分析了PrepRequestProcessor處理器后,接著來分析SyncRequestProcessor,該處理器主要是將請求資料高效率存入磁盤,并且請求在寫入磁盤之前是不會被轉發到下個處理器的,
我們先看請求被添加到佇列的方法:

同樣SyncRequestProcessor是一個執行緒,執行佇列中的請求也在執行緒中觸發,我們看它的run方法,原始碼如下:

run方法會從queuedRequests佇列中獲取一個請求,如果獲取不到就會阻塞等待直到獲取到一個請求物件,程式才會繼續往下執行,接下來會呼叫Snapshot Thread執行緒實作將客戶端發送的資料以快照的方式寫入磁盤,最終呼叫flush()方法實作資料提交,flush()方法原始碼如下:

flush()方法實作了資料提交,并且會將請求交給下一個業務鏈,下一個業務鏈為FinalRequestProcessor,
4.4 FinalRequestProcessor剖析
前面分析了SyncReqeustProcessor,接著分析請求處理鏈中最后的一個處理器FinalRequestProcessor,該業務處理物件主要用于回傳Response,

4.5 ZK業務鏈處理優劣總結
Zookeeper業務鏈處理,思想遵循了AOP思想,但并未采用相關技術,為了提升效率,仍然大幅使用到了多執行緒,正因為有了業務鏈路處理先后順序,使得Zookeeper業務處理流程更清晰更容易理解,但大量混入了多執行緒,也似的學習成本增加,
本文由傳智教育博學谷 - 狂野架構師教研團隊發布
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力
轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/495364.html
標籤:Java
