<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手摸手教你閱讀和調(diào)試大型開源項(xiàng)目 ZooKeeper

          共 15338字,需瀏覽 31分鐘

           ·

          2021-04-13 09:51

          本文作者:HelloGitHub-老荀

          Hi,這里是 HelloGitHub 推出的 HelloZooKeeper 系列,免費(fèi)開源、有趣、入門級(jí)的 ZooKeeper 教程,面向有編程基礎(chǔ)的新手。

          項(xiàng)目地址:https://github.com/HelloGitHub-Team/HelloZooKeeper

          今兒就帶大家打入 ZooKeeper 的源碼內(nèi)部!

          一、源碼調(diào)試

          授人以魚不如授人以漁

          我始終相信 “紙上得來(lái)終覺淺”,最終讀者想要自己真正了解到 ZK 內(nèi)部原理,閱讀源碼還是必不可少的,如果你們和我一樣也擁有肉眼 Debug 的能力,那其實(shí)可以不用大費(fèi)周章搭建源碼調(diào)試環(huán)境,直接正面硬剛。

          但是如果沒有的話,把 ZK 源碼下載下來(lái),使用稱手的 IDE 直接跑起來(lái),然后在需要學(xué)習(xí)的地方直接打斷點(diǎn),豈不是美滋滋

          1.1 下載源碼

          ZooKeeper 3.6.2 源碼下載頁(yè)面

          上面的鏈接中隨便選一個(gè)下載速度快的,點(diǎn)擊下載壓縮包即可,下載完成后解壓縮就會(huì)得到如下的目錄結(jié)構(gòu)

          .
          ├── zookeeper-server
          ├── zookeeper-recipes
          ├── zookeeper-metrics-providers
          ├── zookeeper-jute
          ├── zookeeper-it
          ├── zookeeper-docs
          ├── zookeeper-contrib
          ├── zookeeper-compatibility-tests
          ├── zookeeper-client
          ├── zookeeper-assembly
          ├── zk-merge-pr.py
          ├── pom.xml
          ├── owaspSuppressions.xml
          ├── excludeFindBugsFilter.xml
          ├── dev
          ├── conf
          ├── checkstyleSuppressions.xml
          ├── checkstyle-strict.xml
          ├── checkstyle-simple.xml
          ├── bin
          ├── README_packaging.md
          ├── README.md
          ├── NOTICE.txt
          ├── LICENSE.txt
          ├── Jenkinsfile-PreCommit
          └── Jenkinsfile

          目錄中是有 pom.xml 所以 ZK 需要通過 maven 編譯整個(gè)項(xiàng)目,先確保自己的 maven 是安裝好的

          $ mvn --version
          Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T02:33:14+08:00)
          Maven home: /your/maven/home/apache-maven-3.5.4
          Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre
          Default locale: zh_CN, platform encoding: UTF-8
          OS name: "mac os x", version: "10.16", arch: "x86_64", family: "mac"

          如果有這樣的輸出說明 maven 是安裝成功的,具體安裝過程我這里就略過了,如果你有困難的話,可以留言給我們

          1.2 編譯項(xiàng)目

          進(jìn)入和 pom.xml 同級(jí)目錄中并輸入

          $ mvn install -DskipTests=true

          就會(huì)看到項(xiàng)目在進(jìn)行編譯了,等到最后的輸出 BUILD SUCCESS,就說明項(xiàng)目編譯完成了

          [INFO] Reactor Summary:
          [INFO]
          [INFO] Apache ZooKeeper 3.6.2 ............................. SUCCESS [  3.621 s]
          [INFO] Apache ZooKeeper - Documentation ................... SUCCESS [  2.086 s]
          [INFO] Apache ZooKeeper - Jute ............................ SUCCESS [ 10.633 s]
          [INFO] Apache ZooKeeper - Server .......................... SUCCESS [ 19.246 s]
          [INFO] Apache ZooKeeper - Metrics Providers ............... SUCCESS [  0.108 s]
          [INFO] Apache ZooKeeper - Prometheus.io Metrics Provider .. SUCCESS [  1.286 s]
          [INFO] Apache ZooKeeper - Client .......................... SUCCESS [  0.083 s]
          [INFO] Apache ZooKeeper - Recipes ......................... SUCCESS [  0.092 s]
          [INFO] Apache ZooKeeper - Recipes - Election .............. SUCCESS [  0.244 s]
          [INFO] Apache ZooKeeper - Recipes - Lock .................. SUCCESS [  0.259 s]
          [INFO] Apache ZooKeeper - Recipes - Queue ................. SUCCESS [  0.295 s]
          [INFO] Apache ZooKeeper - Assembly ........................ SUCCESS [  5.425 s]
          [INFO] Apache ZooKeeper - Compatibility Tests ............. SUCCESS [  0.072 s]
          [INFO] Apache ZooKeeper - Compatibility Tests - Curator 3.6.2 SUCCESS [  0.432 s]
          [INFO] ------------------------------------------------------------------------
          [INFO] BUILD SUCCESS
          [INFO] ------------------------------------------------------------------------
          [INFO] Total time: 44.263 s
          [INFO] Finished at: 2021-01-22T13:49:30+08:00
          [INFO] ------------------------------------------------------------------------

          1.3 打開并配置項(xiàng)目

          之后就可以通過你的 IDE 打開這個(gè)目錄了,我這里使用的是 idea

          然后開始配置 Run/Debug Configurations

          點(diǎn)擊 + 添加新的配置

          選擇 Application

          1.3.1 單機(jī)版啟動(dòng)配置

          然后配置按照下圖去填寫或選擇

          1. 先給這個(gè)配置起一個(gè)牛逼的名字
          2. 選擇 Modify options 打開子菜單
          3. 確保圖中菜單中的三個(gè)子選項(xiàng)都被選中(前面有 √)

          然后我們看具體的配置

          在我電腦上解壓縮后的項(xiàng)目路徑為 /Users/junjiexun/Desktop/apache-zookeeper-3.6.2 讀者請(qǐng)根據(jù)自己情況修改

          1. 選擇你本地 jdk (我本地是 1.8 其他版本的不知道行不行,低版本肯定是不行,因?yàn)樵创a中用到了 1.8 的一些寫法)
          2. 選擇 zookeeper
          3. 配置 VM options,內(nèi)容為 -Dlog4j.configuration=file:/Users/junjiexun/Desktop/apache-zookeeper-3.6.2/conf/log4j.properties,如果不配置的話,無(wú)法輸出日志
          4. 指定啟動(dòng)類 org.apache.zookeeper.server.ZooKeeperServerMain
          5. 單機(jī)版啟動(dòng)需要命令行參數(shù),內(nèi)容為 2181 /Users/junjiexun/Desktop/apache-zookeeper-3.6.2/data
          6. 這個(gè)應(yīng)該是不用修改,自動(dòng)就會(huì)填上的,反正內(nèi)容就是 /Users/junjiexun/Desktop/apache-zookeeper-3.6.2
          7. 點(diǎn)擊中間的 + 添加包路徑,內(nèi)容為 org.apache.zookeeper.server.*

          然后點(diǎn)擊 Apply 以及 OK 完成保存。

          然后點(diǎn)擊這個(gè)小蟲子就可以啟動(dòng)了

          2021-01-22 15:12:16,319 [myid:] - INFO  [main:NIOServerCnxnFactory@674] - binding to port 0.0.0.0/0.0.0.0:2181
          2021-01-22 15:12:16,413 [myid:] - INFO  [main:WatchManagerFactory@42] - Using org.apache.zookeeper.server.watch.WatchManager as watch manager
          2021-01-22 15:12:16,413 [myid:] - INFO  [main:WatchManagerFactory@42] - Using org.apache.zookeeper.server.watch.WatchManager as watch manager
          2021-01-22 15:12:16,413 [myid:] - INFO  [main:ZKDatabase@132] - zookeeper.snapshotSizeFactor = 0.33
          2021-01-22 15:12:16,413 [myid:] - INFO  [main:ZKDatabase@152] - zookeeper.commitLogCount=500
          2021-01-22 15:12:16,429 [myid:] - INFO  [main:SnapStream@61] - zookeeper.snapshot.compression.method = CHECKED
          2021-01-22 15:12:16,432 [myid:] - INFO  [main:FileSnap@85] - Reading snapshot /Users/junjiexun/Desktop/apache-zookeeper-3.6.2/data/version-2/snapshot.2
          2021-01-22 15:12:16,444 [myid:] - INFO  [main:DataTree@1737] - The digest value is empty in snapshot
          2021-01-22 15:12:16,480 [myid:] - INFO  [main:ZKDatabase@289] - Snapshot loaded in 67 ms, highest zxid is 0x2, digest is 1371985504
          2021-01-22 15:12:16,481 [myid:] - INFO  [main:FileTxnSnapLog@470] - Snapshotting: 0x2 to /Users/junjiexun/Desktop/apache-zookeeper-3.6.2/data/version-2/snapshot.2
          2021-01-22 15:12:16,488 [myid:] - INFO  [main:ZooKeeperServer@529] - Snapshot taken in 6 ms
          2021-01-22 15:12:16,544 [myid:] - INFO  [ProcessThread(sid:0 cport:2181)::PrepRequestProcessor@136] - PrepRequestProcessor (sid:0) started, reconfigEnabled=false
          2021-01-22 15:12:16,546 [myid:] - INFO  [main:RequestThrottler@74] - zookeeper.request_throttler.shutdownTimeout = 10000
          2021-01-22 15:12:16,623 [myid:] - INFO  [main:ContainerManager@83] - Using checkIntervalMs=60000 maxPerMinute=10000 maxNeverUsedIntervalMs=0
          2021-01-22 15:12:16,628 [myid:] - INFO  [main:ZKAuditProvider@42] - ZooKeeper audit is disabled.

          看到日志輸出,如果沒有報(bào)錯(cuò)的話就是成功了!

          然后我們可以用客戶端測(cè)試下

          ZooKeeper client = new ZooKeeper("127.0.0.1:2181"3000null);
          List<String> children = client.getChildren("/"false);
          System.out.println(children);
          client.close();

          輸出為

          [zookeeper]

          單機(jī)版的搞定了!我們下面試試集群版

          1.3.2 集群版啟動(dòng)配置

          我們有時(shí)候需要調(diào)試集群版 ZK 才有的邏輯,那之前的單機(jī)版就不夠用了,并且我這里推薦將之前的源碼壓縮包,解壓到兩個(gè)不同的目錄下,然后通過 IDE 分別打開這兩個(gè)目錄,去完全模擬兩個(gè)不同的節(jié)點(diǎn)。集群版的和單機(jī)版配置是差不多的,我們來(lái)看看有哪些不一樣的吧?我這里演示就啟動(dòng)兩個(gè)節(jié)點(diǎn) myid 分別是 1 和 2。

          1. 首先將默認(rèn)的 zoo_sample.cfg 復(fù)制并重命名成 zoo.cfg,也可以直接重命名
          2. 新建 data 目錄(如果沒有的話),并在其下新建一個(gè)文本文件 myid 文本內(nèi)容是 1

          然后編輯下 zoo.cfg

          # 修改
          dataDir=/Users/junjiexun/Desktop/apache-zookeeper-3.6.2/data
          # 新增下面兩行
          server.1=127.0.0.1:2888:3888
          server.2=127.0.0.1:2887:3887

          具體的配置如下:

          1. 啟動(dòng)類不同,集群的為 org.apache.zookeeper.server.quorum.QuorumPeerMain
          2. 命令行參數(shù)不同,傳入的是 zoo.cfg 路徑,我的路徑是 /Users/junjiexun/Desktop/apache-zookeeper-3.6.2/conf/zoo.cfg

          然后是配置第二個(gè)節(jié)點(diǎn),我這里假設(shè)第二個(gè)節(jié)點(diǎn)的項(xiàng)目目錄是 /Users/junjiexun/Desktop/apache-zookeeper-3.6.2-bak

          第二個(gè)節(jié)點(diǎn)把 myid 文件中的內(nèi)容修改為 2

          zoo.cfg 中內(nèi)容是

          # 修改
          dataDir=/Users/junjiexun/Desktop/apache-zookeeper-3.6.2-bak/data
          # 修改,因?yàn)槲覂蓚€(gè)節(jié)點(diǎn)是在一臺(tái)機(jī)器中的,所以端口是不能重復(fù)的
          clientPort=2182
          # 同樣新增下面兩行
          server.1=127.0.0.1:2888:3888
          server.2=127.0.0.1:2887:3887

          命令行的參數(shù)是 /Users/junjiexun/Desktop/apache-zookeeper-3.6.2-bak/conf/zoo.cfg

          其他我沒提到的和節(jié)點(diǎn) 1 是一樣的。

          我們啟動(dòng)兩個(gè)節(jié)點(diǎn)試試

          2021-01-22 15:44:08,461 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):WatchManagerFactory@42] - Using org.apache.zookeeper.server.watch.WatchManager as watch manager
          2021-01-22 15:44:08,461 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):WatchManagerFactory@42] - Using org.apache.zookeeper.server.watch.WatchManager as watch manager
          2021-01-22 15:44:08,471 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):Learner@677] - Learner received NEWLEADER message
          2021-01-22 15:44:08,471 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@1811] - Dynamic reconfig is disabled, we don't store the last seen config.
          2021-01-22 15:44:08,471 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):FileTxnSnapLog@470] - Snapshotting: 0x28100000001 to /Users/junjiexun/Desktop/apache-zookeeper-3.6.2/data/version-2/snapshot.28100000001
          2021-01-22 15:44:08,472 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):ZooKeeperServer@529] - Snapshot taken in 1 ms
          2021-01-22 15:44:08,525 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):Learner@661] - Learner received UPTODATE message
          2021-01-22 15:44:08,525 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@868] - Peer state changed: following - synchronization
          2021-01-22 15:44:08,537 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):CommitProcessor@476] - Configuring CommitProcessor with readBatchSize -1 commitBatchSize 1
          2021-01-22 15:44:08,537 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):CommitProcessor@438] - Configuring CommitProcessor with 4 worker threads.
          2021-01-22 15:44:08,544 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):RequestThrottler@74] - zookeeper.request_throttler.shutdownTimeout = 10000
          2021-01-22 15:44:08,567 [myid:1] - INFO  [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@863] - Peer state changed: following - broadcast

          最后的 Peer state changed 代表選舉完成了,貼出來(lái)的這個(gè)節(jié)點(diǎn) 1 是 Follower,大功告成!

          之后當(dāng)你想要學(xué)習(xí)源碼的流程的時(shí)候,直接本地啟動(dòng)服務(wù)端即可,是不是美滋滋呢~

          1.4 源碼閱讀指北

          • 服務(wù)端啟動(dòng),集群 QuorumPeerMain#main,單機(jī) ZooKeeperServerMain#main
          • 客戶端 ZooKeeper
          • 解析配置相關(guān),QuorumPeerConfig#parse
          • 內(nèi)存模型(小紅本)DataTree
          • 回調(diào)通知(小黃本)IWatchManager 查看該接口實(shí)現(xiàn)
            • 默認(rèn)實(shí)現(xiàn) WatchManager
            • 優(yōu)化方案 WatchManagerOptimized
          • 選舉 FastLeaderElection#lookForLeader
          • 服務(wù)端實(shí)例,設(shè)置流水線 setupRequestProcessors 方法
            • Leader 節(jié)點(diǎn) LeaderZooKeeperServer
            • Follower 節(jié)點(diǎn) FollowerZooKeeperServer
            • Observer 節(jié)點(diǎn) ObserverZooKeeperServer
          • 各個(gè)流水線員工 RequestProcessor 查看該接口的實(shí)現(xiàn)
          • 持久化 log FileTxnLog,snapshot FileSnap
          • 會(huì)話管理 SessionTrackerImpl#run
          • 協(xié)議 Record 查看該接口的實(shí)現(xiàn)

          1.5 源碼閱讀心得

          閱讀大型項(xiàng)目的源碼一定是一個(gè)費(fèi)時(shí)費(fèi)心費(fèi)力的工作,我這里也講一下我閱讀 ZK 源碼的心得:

          • 不要死摳細(xì)節(jié)!大型項(xiàng)目的源碼數(shù)量通常比較多,如果盯著邏輯中的每一個(gè)細(xì)節(jié),就會(huì)迷失在源碼的汪洋大海中。
          • 通常閱讀源碼都要帶著一個(gè)目的。例如:ZK 是怎么進(jìn)行協(xié)議轉(zhuǎn)換的,ZK 是怎么選舉的等等。有了目的以后,看相關(guān)源碼是要選擇性的忽略一些其他不相關(guān)的細(xì)節(jié),可以通過方法名或者注釋,來(lái)對(duì)具體的代碼塊先有一個(gè)感性的認(rèn)識(shí)。
          • 碰到讀不懂的地方,可以先去網(wǎng)上看看有沒有人寫過類似的博客,站在巨人的肩膀上,很可能別人一點(diǎn)你就通了。
          • 在 ZK 中一般間接或者直接繼承 ZooKeeperThread 都是線程對(duì)象,主要邏輯可以查看 run 方法。
          • 任何一個(gè)類重要的屬性肯定是在成員字段中,通過查看成員字段是可以大致推測(cè)出該類背后的數(shù)據(jù)結(jié)構(gòu)。
          • 成員屬性中如果有阻塞隊(duì)列的字段,大概率會(huì)是生產(chǎn)者-消費(fèi)者模式的體現(xiàn),可以重點(diǎn)關(guān)注該阻塞隊(duì)列的使用,何時(shí)放入以及取出元素。

          1.6 小結(jié)

          我用一些圖文的篇幅介紹了如何在本地調(diào)試 ZK 源碼,以及如何科學(xué)的閱讀源碼。我本地的環(huán)境是 Mac,用的 IDE 是 idea,如果你的環(huán)境或者工具和我不一樣,碰到了困難的話,也可以給我們留言哦~

          二、ZK 中應(yīng)用到的設(shè)計(jì)模式

          ZK 本身就是分布式的應(yīng)用,也是優(yōu)秀的開源項(xiàng)目,我這里就簡(jiǎn)單聊聊我在閱讀源碼中看到的應(yīng)用在 ZK 里的設(shè)計(jì)模式吧

          2.1 生產(chǎn)者消費(fèi)者

          這個(gè)是 ZK 中非常有代表性的設(shè)計(jì)模式應(yīng)用了,ZK 本身是 C/S 架構(gòu)的設(shè)計(jì),請(qǐng)求就是客戶端發(fā)送給服務(wù)端數(shù)據(jù),響應(yīng)則是服務(wù)端發(fā)送給客戶端數(shù)據(jù),而 ZK 實(shí)現(xiàn)一些功能并不是通過線性順序的去調(diào)用不同的方法去完成的,通常會(huì)由生產(chǎn)者線程,阻塞隊(duì)列和消費(fèi)者線程組成,生產(chǎn)者線程將上游收到的一些請(qǐng)求對(duì)象放入阻塞隊(duì)列,當(dāng)前的方法就返回了,之后由消費(fèi)者線程通過循環(huán)不停的從阻塞隊(duì)列中獲取,再完成之后的業(yè)務(wù)邏輯。舉例:

          • PrepRequestProcessor,阻塞隊(duì)列是 submittedRequests
          • SyncRequestProcessor,阻塞隊(duì)列是 queuedRequests

          2.2 工廠模式

          有一些接口的實(shí)現(xiàn),ZK 本身提供了默認(rèn)的選擇,但是如果使用者在配置中配置了其他的實(shí)現(xiàn)的話,ZK 的工廠就會(huì)自動(dòng)去創(chuàng)建那些其他的實(shí)現(xiàn)。舉例:

          • 在創(chuàng)建 ClientCnxnSocket 時(shí),會(huì)根據(jù) zookeeper.clientCnxnSocket 的配置去選擇客戶端的 IO 實(shí)現(xiàn)
          • 在創(chuàng)建 IWatchManager 時(shí),會(huì)根據(jù) zookeeper.watchManagerName 的配置去選擇服務(wù)端的 watch 管理實(shí)現(xiàn)
          • 在創(chuàng)建 ServerCnxnFactory 時(shí),會(huì)根據(jù) zookeeper.serverCnxnFactory 的配置去選擇服務(wù)端的 IO 工廠實(shí)現(xiàn)

          2.3 責(zé)任鏈模式

          之前有學(xué)習(xí)過,ZK 服務(wù)端業(yè)務(wù)邏輯處理是通過將一個(gè)個(gè) XxxProcessor 串起來(lái)實(shí)現(xiàn)的,Processor 彼此不關(guān)心調(diào)用順序,僅僅通過 nextProcessor 關(guān)聯(lián),不同的服務(wù)端角色也可以通過這種方式極大的復(fù)用代碼

          • 單機(jī)模式下:PrepRequestProcessor -> SyncRequestProcessor -> FinalRequestProcessor
          • 集群模式下 Leader :LeaderRequestProcessor -> PrepRequestProcessor -> ProposalRequestProcessor -> CommitProcessor -> Leader.ToBeAppliedRequestProcessor -> FinalRequestProcessor
          • 集群模式下 Follower :FollowerRequestProcessor -> CommitProcessor -> FinalRequestProcessor
          • 集群模式下 Observer :ObserverRequestProcessor -> CommitProcessor -> FinalRequestProcessor

          2.4 策略模式

          zookeeper.snapshot.compression.method 可以配置成不同的 snapshot 壓縮算法,當(dāng)需要生成 snapshot 文件的時(shí)候,會(huì)根據(jù)不同的壓縮算法去執(zhí)行:

          • gzGZIPInputStream
          • snappySnappyInputStream
          • 默認(rèn):BufferedInputStream

          2.5 裝飾器模式

          還是剛剛的壓縮算法,對(duì)外提供的是 CheckedInputStream 的統(tǒng)一處理對(duì)象,使用 CheckedInputStream 將上面三種壓縮實(shí)現(xiàn)包裝起來(lái),這些對(duì)象全部都是 InputStream 的子類

          switch (根據(jù)不同的配置) {
            // 策略模式的體現(xiàn)
            case GZIP:
              is = new GZIPInputStream(fis);
              break;
            case SNAPPY:
              is = new SnappyInputStream(fis);
              break;
            case CHECKED:
            default:
              is = new BufferedInputStream(fis);
          }
          // 都被包裝進(jìn)了 CheckedInputStream
          // 裝飾器模式的體現(xiàn)
          return new CheckedInputStream(is, new Adler32()); 

          三、總結(jié)

          今天我講了如何直接從 ZK 源碼 DEBUG,介紹了一些 ZK 中用到的設(shè)計(jì)模式,大家有閱讀源碼問題的話,歡迎給我留言哦

          下一期介紹 ZK 的高級(jí)用法純實(shí)戰(zhàn),期待一下吧~

          老規(guī)矩,如果你有任何對(duì)文章中的疑問也可以是建議或者是對(duì) ZK 原理部分的疑問,歡迎來(lái)倉(cāng)庫(kù)中提問,或者閱讀原文來(lái)語(yǔ)雀話題討論。

          地址:https://github.com/HelloGitHub-Team/HelloZooKeeper

          ??「點(diǎn)擊關(guān)注」更多驚喜等待你!


          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美a片免费视频 | 北条麻妃在线观看 | 国产操B视频 | 破处毛片 | 青青草AV导航 |