
某日中午,午睡正香的時候,接到系統的報警電話,提示生產某物理機異常宕機了,目前該物理機已恢復,需要重啟上面部署的應用。
這時瞬間沒有了睡意,登上堡壘機,快速重啟了應用,系統恢復正常。本想着繼續午睡,但是已經沒有了睡意。
旁邊的小師弟(我們叫他小灰吧)剛才在我們邊上,目睹這一切,然后向我請教個問題。
小灰:
黑哥,剛才應用突然宕機,會不會對交易有影響啊?
小黑:
影響確實會有,不過也不大,就當時應用正在運行那些那些交易會受到影響。
小灰:
不對啊,我們現在系統架構是下面這樣。

我們這次宕機的是業務邏輯層,那按照目前使用 Dubbo 輪詢的負載均衡方式,不是還會有交易分發到宕機那台應用上,這些交易請求顯然會異常。
運氣差點,不是會有一半交易請求都會有問題嗎?
小黑:
沒錯,我們的系統架構圖確實如說的一樣。
不過你說的這個問題,它是不存在的。
這是因為 Dubbo 內部會自動幫我們的摘除宕機的應用節點。
小灰:
啥?Dubbo 內部還有這功能啊?黑哥你給我講講原理唄!
小黑:
可以啊,不過講這個原理之前,我們首先需要了解 Dubbo 服務注冊發現流程。
我看你最近一直在看『深入理解 Apache Dubbo 與實戰』,這本書確實不錯,里面框架原理,代碼細節都講的很透徹。
你應該已經了解了 Dubbo 服務注冊發現流程,那你先跟我簡單講講原理吧。
小灰拿起紙筆,在上面畫了個圖:

恩,我當前了解的還不是很深,那我先聊聊目前我知道的。
我們目前使用 ZooKeeper 當做服務注冊中心,ZooKeeper 可以簡單理解成是一個 KV系統,內部是一個樹形的數據結構。
Dubbo 默認將會在 ZooKeeper 中創建一個四層的數據結構,從上到下分別為:
- Root
- Service
- Category
- URL
其中 Root 層是注冊中心分組,默認命名為 dubbo。我們可以通過修改 <dubbo:registry> 中的 group 屬性修改默認值,這樣修改之后不同分組的 dubbo 服務不會互相影響,也不會互相調用,可以用於環境隔離。
接下來 Service 就是服務類的全路徑,包括包路徑。
Service 層下面就是 Category 層,這其中總共有四類目錄(上面圖形只畫了兩種),分別為:
- providers:包含服務提供者 URL 元數據信息
- consumers:包含消費者 URL 元數據信息
- routers:包含消費者路由策略的 URL 元數據信息
- configurators:包含動態配置元數據信息
最后一層就是具體 Dubbo 服務 URL,類似如下:
dubbo://2.0.1.13:12345/com.dubbo.example.DemoService?xx=xx
小黑:
沒錯,這個內部結構你理還是蠻清晰的么!
平常使用的情況下,我們重點關注 providers 以及 consumers 就好了。如果我們需要配置服務路由信息以及動態配置,那我們需要在 Dubbo-Admin 服務治理中心下發配置。這時 routers 與 configurators 就會增加相關配置。
小灰:
嘿嘿😝,咱接下來講服務注冊流程。
當服務提供者啟動之后,會向注冊中心寫入自己的元數據信息,其實就是在 providers 節點下創建一個 URL 節點(如果上級節點都不存在,將會逐層創建),存儲值類似如下:
dubbo://10.10.11.22:20880/com.foo/BarService?key=value....
接着啟動服務消費者,消費者第一次連接上 ZooKeeper 時,將會拉取provider 節點下所有服務提供者的 URL 信息,然后與相應的服務提供者建立連接。
同時服務消費者也會將自己信息注冊到在 consumer 節點下,這個目的是為了服務治理中心(Dubbo-Admin)發現自己。
同時消費者將會在 provider 節點上注冊一個 watcher ,當有新的服務提供者啟動成功,provider 節點發生變更,ZooKeeper 將會推送變更信息給 Dubbo 服務,然后 Dubbo 將會重新建立與服務提供者的連接。
小黑:
你說的整個 Dubbo 服務注冊發現流程沒有什么問題,這里消費者與服務提供者建立的連接的流程,我們之前踩過一個坑,你有空可以看看 天啦嚕!生產機器連接數飆升到上萬,背后發生了什么?。
另外,再考你一下:
服務節點變更時,ZooKeeper 推送 provider 下全量子節點數據給消費者嗎?
小灰:
呀,難道不是嗎?
小黑:
不是的。ZooKeeper 的 watch 機制目前只能推送節點變更信息,比如節點內容數據變更,監聽節點下子節點列表變更等,具體如下圖:

進一步從 Zookeeper 客戶端的源碼上來看,watcher 回調通知內容最終轉為 WatchedEvent。

這個類只有三個字段,所以是不會推送子節點列表數據過來。
小灰:
既然不是通過推送獲取子節點列表的信息,那如何拿到變動子節點列表?
有了,在收到推送的時候,我們能獲取到變動節點信息,然后我再拉取一下子節點的列表不就好了!
小黑:
沒錯,Dubbo 就是這么做的。
這一點我們可以具體看下 Dubbo 的源碼,位於 CuratorZookeeperClient。
畫外音:下面的源碼基於 Dubbo 2.6.7

圖中標注的地方,Dubbo 通過拉取獲取了字節點的全量數據,同時再次注冊了一個 watcher 。
不過這么多,有個缺陷的,當微服務節點數量有很多的時候,全量拉取將會占用過多的內網帶寬,很容易造成網絡風暴。
上面我們講到 Zookeeper 的這種方式,是一種典型的 Push 模式,對應的還有一種的模式為 Pull 模式,eureka 就是這種模式的典型的代表。
eureka 客戶端就是通過定期輪詢拉取數據,獲取最新的變更數據。不過拉取模式也存在很大的劣勢,如果輪詢頻率低,那么服務變更信息無法及時獲取,如果輪率太高這就會增加注冊中心的壓力。
小黑:
服務發現流程這下我們已經搞明白了。如果有新增服務節點,Dubbo 消費者通過通知,然后再拉取全量的子節點列表,這樣 Dubbo消費者就會新增與新的服務提供者連接,后續再通過負載均衡使用新的連接。
如果 Dubbo 服務提供者正常停止下線,那么他將會刪除 ZooKeeper 上的自己注冊的節點信息。刪除之后 Dubbo 消費者第一時間收到了通知,通過拉取全量的子節點列表,然后通過比對,發現某個節點下線,然后刪除之前簡歷的連接。這樣后續,就不會再調用這個節點。
小灰:
恩,正常應用上下線,Dubbo 消費者可以感知到,但是像服務提供者宕機的情況,消費者是怎么感知到的?
小黑:
這一點,就與 Zookeeper 的自身特性有關了。
Zookeeper 中我們可以創建四種節點類型的節點:
- 永久節點
- 臨時節點
- 順序節點
- 永久節點
- 臨時節點
臨時節點與永久節點唯一的區別在於,一旦 Zookeeper 客戶端斷開連接,Zookeeper 服務感知到之后就會自動刪除臨時節點。
Dubbo 服務提供者就是在 Zookeeper 注冊了臨時節點,如果服務提供者宕機了,臨時節點自動被刪除,后面的流程就跟 Dubbo 應用正常下線一樣了。
小灰:
すごい!原來如此,這個設計 666 啊。
小黑:
其實應用宕機這種, Dubbo RPC 框架內部都可以自動幫我們處理,這種故障其實很好處理。但是如果碰到下面這這種情況:
- 服務提供者與服務消費者網絡隔離
- 服務提供陷入緩慢
在服務消費者看來,服務提供者其實是「活着」,這是因為服務提供者與 Zookeeper 還是正常連接。
但是實際情況下,服務消費者其實已經不能正常調用服務提供者了,那這種情況就比較棘手了。
不過 Dubbo 內部也提供了解決辦法。馬上就上班了,也來不及講了,我們后面再討論!
小灰:
好的,黑哥!今天學到了!
黑哥🐂🍺!愛你~

幫助鏈接
歡迎關注我的公眾號:程序通事,獲得日常干貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn
