主頁 > 後端開發 > 【圖解原始碼】Zookeeper3.7原始碼分析,包含服務啟動流程原始碼、網路通信原始碼、RequestProcessor處理請求原始碼

【圖解原始碼】Zookeeper3.7原始碼分析,包含服務啟動流程原始碼、網路通信原始碼、RequestProcessor處理請求原始碼

2022-06-24 11:10:18 後端開發

Zookeeper3.7原始碼剖析

能力目標

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

1 Zookeeper原始碼匯入

file

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

file

我們當前課程主要是研究Zookeeper原始碼,需要將Zookeeper工程匯入到IDEA中,老版的zk是通過ant進行編譯的,但最新的zk(3.7)原始碼中已經沒了build.xml,而多了pom.xml,也就是說構建方式由原先的Ant變成了Maven,原始碼下下來后,直接編譯、運行是跑不起來的,有一些配置需要調整,

1.1 工程匯入

Zookeeper各個版本原始碼下載地址https://github.com/apache/zookeeper,我們可以在該倉庫下選擇不同的版本,我們選擇最新版本,當前最新版本為3.7,如下圖:

file

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

file

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

file

克隆專案到本地:

file

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

file

專案運行的時候,缺一個版本物件,創建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并啟動該類,啟動前做如下配置:

file

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

file

file

file

為了解決上面的錯誤,我們需要手動引入一些包,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,如下圖:

file

啟動后,日志如下:

file

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

file

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

file

連接后,Zookeeper資訊如下:

file

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

file

1.5 Zookeeper案例應用

file

我們將資料中工程\dubbo工程匯入到IDEA中,上圖是他們的呼叫關系,那么問題來了:

  • 生產者向Zookeeper注冊服務資訊,Zookeeper把資料存哪兒了?
  • 集群環境下,如果某個節點資料變更了,Zookeeper如何監聽到的?
  • 集群環境下各個節點的資料如何同步?
  • 如果某個節點掛了,Zookeeper如何選舉呢?
  • ........

file

帶著上面的疑問,我們開始研究Zookeeper原始碼,

2 ZK服務啟動流程原始碼剖析

ZooKeeper可以以standalone、分布式的方式部署,standalone模式下只有一臺機器作為服務器,ZooKeeper會喪失高可用特性,分布式是使用多個機器,每臺機器上部署一個ZooKeeper服務器,即使有服務器宕機,只要少于半數,ZooKeeper集群依然可以正常對外提供服務,集群狀態下Zookeeper是具備高可用特性,

我們接下來對ZooKeeperstandalone模式啟動以及集群模式做一下原始碼分析,

2.1 ZK單機/集群啟動流程

file

如上圖,上圖是Zookeeper單機/集群啟動流程,每個細節所做的事情都在上圖有說明,我們接下來按照流程圖對原始碼進行分析,

2.2 ZK啟動入口分析

啟動入口類:QuorumPeerMain

該類是zookeeper單機/集群的啟動入口類,是用來加載配置、啟動QuorumPeer(選舉相關)執行緒、創建ServerCnxnFactory等,我們可以把代碼切換到該類的主方法(main)中,從該類的主方法開始分析,main方法代碼分析如下:

file

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

file

如果啟動單機版,會呼叫ZooKeeperServerMain.main(args);,如果啟動集群版,會呼叫QuorumPeerMain.runFromConfig(config);,我們接下來對單機版啟動做原始碼詳細剖析,集群版在后面章節中講解選舉機制時詳細講解,

2.3 ZK單機啟動原始碼剖析

針對ZK單機啟動原始碼方法呼叫鏈,我們已經提前做了一個方法呼叫關系圖,我們講解ZK單機啟動原始碼,將和該圖進行一一匹對,如下圖:

file

1)單機啟動入口

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

file

2)組態檔決議

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

file

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:防止主執行緒結束,阻塞主執行緒,

方法原始碼如下:

file

4)網路通信物件創建

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

file

5)單機啟動

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

file

啟動后,日志如下:

file

3 ZK網路通信原始碼剖析

Zookeeper作為一個服務器,自然要與客戶端進行網路通信,如何高效的與客戶端進行通信,讓網路IO不成為ZooKeeper的瓶頸是ZooKeeper急需解決的問題,ZooKeeper中使用ServerCnxnFactory管理與客戶端的連接,其有兩個實作,一個是NIOServerCnxnFactory,使用Java原生NIO實作;一個是NettyServerCnxnFactory,使用netty實作;使用ServerCnxn代表一個客戶端與服務端的連接,

從單機版啟動中可以發現Zookeeper默認通信組件為NIOServerCnxnFactory,他們和ServerCnxnFactory的關系如下圖:

file

3.1 NIOServerCnxnFactory作業流程

一般使用Java NIO的思路為使用1個執行緒組監聽OP_ACCEPT事件,負責處理客戶端的連接;使用1個執行緒組監聽客戶端連接的OP_READOP_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類上有說明,如下圖:

file

ZooKeeper中對執行緒需要處理的作業做了更細的拆分,解決了有大量客戶端連接的情況下,selector.select()會成為性能瓶頸,將selector.select()拆分出來,交由selector thread處理,

3.2 NIOServerCnxnFactory原始碼

NIOServerCnxnFactory的原始碼分析我們將按照上面所介紹的4個執行緒實作相關分析,并實作資料操作,在程式中獲取指定資料,

3.2.1 AcceptThread剖析

為了讓大家更容易理解AcceptThread,我們把它的結構和方法呼叫關系畫了一個詳細的流程圖,如下圖:

file

NIOServerCnxnFactory類中有一個AccpetThread執行緒,為什么說它是一個執行緒?我們看下它的繼承關系:AcceptThread > AbstractSelectThread > ZooKeeperThread > Thread,該執行緒接收來自客戶端的連接,并將其分配給selector thread(啟動一個執行緒),

該執行緒執行流程:run執行selector.select(),并呼叫doAccept()接收客戶端連接,因此我們可以著重關注doAccept()方法,該類原始碼如下:

file

doAccept()方法用于處理客戶端鏈接,當客戶端鏈接Zookeeper的時候,首先會呼叫該方法,呼叫該方法執行程序如下:

1:和當前服務建立鏈接,
2:獲取遠程客戶端計算機地址資訊,
3:判斷當前鏈接是否超出最大限制,
4:調整為非阻塞模式,
5:輪詢獲取一個SelectorThread,將當前鏈接分配給該SelectorThread,
6:將當前請求添加到該SelectorThread的acceptedQueue中,并喚醒該SelectorThread,

doAccept()方法原始碼如下:

file

上面代碼中addAcceptedConnection方法如下:

file

我們把專案中的分布式案例服務啟動,可以看到如下日志列印:

AcceptThread----------鏈接服務的IP:127.0.0.1

3.2.2 SelectorThread剖析

同樣為了更容易梳理SelectorThread,我們也把它的結構和方法呼叫關系梳理成了流程圖,如下圖:

file

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

file

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

file

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

file

3.2.3 WorkerThread剖析

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

file

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

file

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

file

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

file

IOWorkRequestdoWork原始碼如下:

file

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

file

添加測驗代碼如下:

//==========測驗 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

控制臺資料如下:
file

測驗完成后,不要忘了將該測驗注釋掉,我們可以執行其他增刪改查操作,可以輸出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,執行緒中無限回圈,執行作業如下:

file

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啟動的時候,會確立各個節點的角色特性,即leaderfollowerobserver,每個角色確立后,就會初始化它的作業責任鏈;

4.1 RequestProcessor結構

客戶端請求過來,每次執行不同事務操作的時候,Zookeeper也提供了一套業務處理流程RequestProcessorRequestProcessor的處理流程如下圖:

file

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

file

它的創建步驟:

1:創建finalProcessor,
2:創建syncProcessor,并將finalProcessor作為它的下一個業務鏈,
3:啟動syncProcessor,
4:創建firstProcessor(PrepRequestProcessor),將syncProcessor作為firstProcessor的下一個業務鏈,
5:啟動firstProcessor,

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

file

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

file

PrepRequestProcessor/SyncRequestProcessor關系圖:

file

PrepRequestProcessorSyncRequestProcessor的結構一樣,都是實作了Thread的一個執行緒,所以在這里初始化時便啟動了這兩個執行緒,

4.2 PrepRequestProcessor剖析

PrepRequestProcessor是請求處理器的第1個處理器,我們把之前的請求業務處理銜接起來,一步一步分析,ZooKeeperServer.processPacket()>submitRequest()>enqueueRequest()>RequestThrottler.submitRequest() ,我們來看下RequestThrottler.submitRequest()原始碼,它將當前請求添加到submittedRequests佇列中了,原始碼如下:

file

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

file

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

file

ZooKeeperServer.submitRequestNow()方法呼叫了firstProcessor.processRequest()方法,而這里的firstProcessor就是初始化業務處理鏈中的PrepRequestProcessor,也就是說三個RequestProecessor中最先呼叫的是PrepRequestProcessor

PrepRequestProcessor.processRequest()方法將當前請求添加到了佇列submittedRequests中,原始碼如下:

file

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

file

首先先執行pRequestHelper()方法,該方法是PrepRequestProcessor處理核心業務流程,主要是一些過濾操作,操作完成后,會將請求交給下一個業務鏈,也就是SyncRequestProcessor.processRequest()方法處理請求,

我們來看一下PrepRequestProcessor.pRequestHelper()方法做了哪些事,原始碼如下:

file

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

file

從上面代碼可以看出pRequest2Txn()方法主要做了權限校驗、快照記錄、事務資訊記錄相關的事,還并未涉及資料處理,也就是說PrepRequestProcessor其實是做了操作前權限校驗、快照記錄、事務資訊記錄相關的事,

我們DEBUG除錯一次,看看業務處理流程是否和我們上面所分析的一致,

添加節點:

create /zkdemo itheima

DEBUG測驗如下:

客戶端請求先經過ZooKeeperServer.submitRequestNow()方法,并呼叫firstProcessor.processRequest()方法,而firstProcessor=PrepRequestProcessor,如下圖:

file

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

file

4.3 SyncRequestProcessor剖析

分析了PrepRequestProcessor處理器后,接著來分析SyncRequestProcessor,該處理器主要是將請求資料高效率存入磁盤,并且請求在寫入磁盤之前是不會被轉發到下個處理器的,

我們先看請求被添加到佇列的方法:

file

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

file

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

file

flush()方法實作了資料提交,并且會將請求交給下一個業務鏈,下一個業務鏈為FinalRequestProcessor

4.4 FinalRequestProcessor剖析

前面分析了SyncReqeustProcessor,接著分析請求處理鏈中最后的一個處理器FinalRequestProcessor,該業務處理物件主要用于回傳Response,

file

4.5 ZK業務鏈處理優劣總結

Zookeeper業務鏈處理,思想遵循了AOP思想,但并未采用相關技術,為了提升效率,仍然大幅使用到了多執行緒,正因為有了業務鏈路處理先后順序,使得Zookeeper業務處理流程更清晰更容易理解,但大量混入了多執行緒,也似的學習成本增加,

本文由傳智教育博學谷 - 狂野架構師教研團隊發布
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力
轉載請注明出處!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/495364.html

標籤:Java

上一篇:Java Iterator簡介說明

下一篇:如何過濾物件并獲取Javascript中的某些欄位

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more