Dubbo 穩定性案例:Nacos 註冊中心可用性問題覆盤

Kirito的部落格發表於2022-12-05

問題描述

上週四晚剛回到家,就接到了軟負載同學的電話,說是客戶線上出了故障,我一聽”故障“兩個字,立馬追問是什麼情況,經過整理,還原出線上問題的原貌:

客戶使用了 Dubbo,註冊中心使用的是 Nacos,在下午開始不斷有呼叫報錯,檢視日誌,發現了 Nacos 心跳請求返回 502

2019-11-15 03:02:41.973 [com.alibaba.nacos.client.naming454] -ERROR [com.alibaba.nacos.naming.beat.sender] request xx.xx.xx.xx failed.
com.alibaba.nacos.api.exception.NacosException: failed to req  API: xx.xx.xx.xx:8848/nacos/v1/ns/instance/beat. code:502 msg:

此時還沒有大範圍的報錯。隨後,使用者對部分機器進行了重啟,開始出現大規模的 Nacos 連線不上的報錯,並且呼叫開始出現大量 no provider 的報錯。

問題分析

Nacos 出現心跳報錯,一般會有兩種可能:

  • 使用者機器出現問題,如網路不通

  • Nacos Server 當機

但由於是大面積報錯,所以很快定位到是 Nacos Server 本身出了問題:由於磁碟老舊導致 IO 效率急劇下降,Nacos Server 無法響應客戶端的請求,客戶端直接接收到 502 錯誤響應。這個事件本身並不複雜,是一起註冊中心磁碟故障引發的血案,但從這起事件,卻可以窺探到很多高可用的問題,下面來跟大家一起聊聊這當中的細節。

問題復現

Dubbo 版本:2.7.4

Nacos 版本:1.1.4

復現目標:在本地模擬 Nacos Server 當機,檢查 Dubbo 的呼叫是否會受到影響。

復現步驟

  1. 本地啟動 Nacos Server、Provider、Consumer,觸發 Consumer 呼叫 Provider

  2. kill -9 Nacos Server,模擬 Nacos Server 當機,觸發 Consumer 呼叫 Provider

  3. 重啟 Consumer,觸發 Consumer 呼叫 Provider

期望

3 個步驟均可以呼叫成功

實際結果:

1、2 呼叫成功,3 呼叫失敗

問題成功復現,重啟 Consumer 之後,沒有呼叫成功,客戶恰好遇到了這個問題。大家可能對這其中的細節還是有一些疑問,我設想了一些疑惑點,來和大家一起進行探討。

為什麼 Nacos 當機後,仍然可以呼叫成功

我們都知道,一般聊到 Dubbo,有三個角色是必須要聊到的:服務提供者、服務消費者、註冊中心。他們的關係不用我贅述,可以從下面的連通性列表得到一個比較全面的認識:

  • 註冊中心負責服務地址的註冊與查詢,相當於目錄服務,服務提供者和消費者只在啟動時與註冊中心互動,註冊中心不轉發請求,壓力較小

  • 服務提供者向註冊中心註冊其提供的服務,此時間不包含網路開銷

  • 服務消費者向註冊中心獲取服務提供者地址列表,並根據負載演算法直接呼叫提供者,此時間包含網路開銷

  • 註冊中心,服務提供者,服務消費者三者之間均為長連線

  • 註冊中心透過長連線感知服務提供者的存在,服務提供者當機,註冊中心將立即推送事件通知消費者

  • 註冊中心當機,不影響已執行的提供者和消費者,消費者在本地快取了提供者列表

  • 註冊中心可選的,服務消費者可以直連服務提供者


重點關注倒數第二條,Dubbo 其實在記憶體中快取了一份提供者列表,這樣可以方便地在每次呼叫時,直接從本地記憶體拿地址做負載均衡,而不避免每次呼叫都訪問註冊中心。只有當服務提供者節點發生上下線時,才會推送到本地,進行更新。所以,Nacos 當機後,Dubbo 仍然可以呼叫成功。

Nacos 當機不影響服務呼叫,為什麼日誌中仍然有呼叫報錯

當機期間,已有的服務提供者節點可能突然下線,但由於註冊中心無法通知給消費者,所以客戶端呼叫到下線的 IP 就會出現報錯。

對於此類問題,Dubbo 也可以進行兜底

  • Dubbo 會在連線級別進行心跳檢測,當 channel 本身不可用時,即使沒有註冊中心通知,也會對其進行斷連,並設定定時器,當該連線恢復後,再恢復其可用性

  • 在阿里雲商業版的 Dubbo -- EDAS 中,提供了「離群摘除」功能,可以在呼叫層面即時摘除部分有問題的節點,保證服務的可用性。

為什麼期望 Consumer 重啟之後,呼叫成功

Nacos Server 當機後,Consumer 依舊可以呼叫成功,這個大家應該都比較清楚。但是為什麼期望 Consumer 重啟之後,依舊呼叫成功,有些人可能就會有疑問了,註冊中心都當機了,重啟之後一定連不上,理應呼叫失敗,怎麼會期望成功呢?這就要涉及到 Nacos 的本地快取了。

Nacos 本地快取的作用:當應用與服務註冊中心發生網路分割槽或服務註冊中心完全當機後,應用進行了重啟操作,記憶體裡沒有資料,此時應用可以透過讀取本地快取檔案的資料來獲取到最後一次訂閱到的內容。

例如在 Dubbo 應用中定義瞭如下服務:

<dubbo:service interface="com.alibaba.edas.xml.DemoService" group="DUBBO" version="1.0.0" ref="demoService" />

可以在本機的 /home/${user}/nacos/naming/ 下看到各個名稱空間釋出的所有服務的資訊,其內容格式如下:

{"metadata":{},"dom":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","cacheMillis":10000,"useSpecifiedURL":false,"hosts":[{"valid":true,"marked":false,"metadata":{"side":"provider","methods":"sayHello","release":"2.7.4","deprecated":"false","dubbo":"2.0.2","pid":"5275","interface":"com.alibaba.edas.xml.DemoService","version":"1.0.0","generic":"false","revision":"1.0.0","path":"com.alibaba.edas.xml.DemoService","protocol":"dubbo","dynamic":"true","category":"providers","anyhost":"true","bean.name":"com.alibaba.edas.xml.DemoService","group":"DUBBO","timestamp":"1575355563302"},"instanceId":"30.5.122.3#20880#DEFAULT#DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","port":20880,"healthy":true,"ip":"30.5.122.3","clusterName":"DEFAULT","weight":1.0,"ephemeral":true,"serviceName":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","enabled":true}],"name":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","checksum":"69c4eb7e03c03d4b18df129829a486a","lastRefTime":1575355563862,"env":"","clusters":""}

為什麼期望重啟後呼叫成功?因為經過檢查,發現線上出現問題的機器上,快取檔案一切正常。雖然 Nacos Server 當機了,本地的快取檔案依舊可以作為一個兜底,所以期望呼叫成功。

為什麼 Consumer 重啟後,沒有按照預期載入本地快取檔案

快取檔案正常,問題只有可能出現在讀取快取檔案的邏輯上。

  • 可能是 nacos-client 出了問題

  • 可能是 Dubbo 的 nacos-registry 出了問題

一番排查,在 Nacos 研發的協助下,找到了 naocs-client 的一個引數: namingLoadCacheAtStart,該配置引數控制啟動時是否載入快取檔案,預設值為 false。也就是說,使用 nacos-client,預設是不會載入本地快取檔案的。終於定位到線上問題的原因了:需要手動開啟載入本地快取,才能讓 Nacos 載入本地快取檔案。

該引數設定為 true 和 false 的利弊:

  • 設定為 true,認為可用性 & 穩定性優先,寧願接受可能出錯的資料,也不能因為沒有資料導致呼叫完全出錯

  • 設定為 false,則認為 Server 的可用性較高,更能夠接受沒有資料,也不能接受錯誤的資料

無論是 true 還是 false,都是對一些極端情況的兜底,而不是常態。對於註冊發現場景,設定成 true,可能更合適一點,這樣可以利用 Nacos 的本地快取檔案做一個兜底。

Dubbo 傳遞註冊中心引數

Dubbo 中使用統一 URL 模型進行引數的傳遞,當我們需要在配置檔案傳遞註冊中心相關的配置引數時,可以透過鍵值對的形式進行拼接,當我們想要在 Dubbo 中開啟載入註冊中心快取的開關時,可以如下配置:

<dubbo:registry address="nacos://127.0.0.1:8848?namingLoadCacheAtStart=true"/>

遺憾的是,最新版本的 Dubbo 只傳遞了部分引數給 Nacos Server,即使使用者配置了 namingLoadCacheAtStart 也不會被服務端識別,進而無法載入本地快取。我在本地修改了 Dubbo 2.7.5-SNAPSHOT,傳遞上述引數後,可以使得 1、2、3 三個階段都呼叫成功,證明了 namingLoadCacheAtStart 的確可以使得 Dubbo 載入本地快取檔案。該問題將會在 Dubbo 2.7.5 得到修復,屆時 Dubbo 中使用 Nacos 的穩定性將會得到提升。

問題總結

該線上問題反映出了 Nacos 註冊中心可用性對 Dubbo 應用的影響,以及系統在某個元件當機時,整體系統需要進行的一些兜底邏輯,不至於因為某個元件導致整個系統的癱瘓。

總結下現有程式碼的缺陷以及一些最佳實踐:

  • Dubbo 傳遞註冊中心引數給 Nacos 時,只能夠識別部分引數,這會導致使用者的部分配置失效,在接下來的版本會進行修復。

  • nacos-client 載入本地快取檔案的開關等影響到系統穩定性的引數最好設計成 -D 啟動引數,或者環境變數引數,這樣方便發現問題,及時止血。例如此次的事件,有缺陷的 Dubbo 程式碼僅僅依賴於引數的傳遞,無法載入本地快取檔案,而如果有 -D 引數,可以強行開始載入快取,大大降低了問題的影響面。

  • namingLoadCacheAtStart 是否預設開啟,還需要根據場景具體確定,但 nacos-server 當機等極端場景下,開啟該引數,可以儘可能地降低問題的影響面。順帶一提,Nacos 本身還提供了一個本地災備檔案,與本地快取檔案有一些差異,有興趣的朋友也可以去了解一下。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556476/viewspace-2670112/,如需轉載,請註明出處,否則將追究法律責任。

相關文章