Nacos下線一臺引發線上故障問題排查記

背景

產線nacos集群硬件比較差,cpu只有2核,出現了一次cpu滿,導致服務調用失敗,復盤后,要進行硬件升級,升到4核,先停1臺,觀察,剛開始沒有影響,以為穩了,沒有想到過了3分鐘,業務監控指標就不行了,服務出現調用失敗,報No provider 異常,立即重新拉起來,因為下線一臺,就導致1分多鐘的線上故障,一身冷汗啊!!!

nacos作為服務注冊中心,不是AP嗎,怎么停一臺就有問題。不合理啊。
為啥剛停沒有問題,等了3分鐘才出現問題,為啥啊

帶著這兩個問題和錯誤日志,花了幾天的時間排查,總算是把問題弄清楚了,也順便把nacos的同步機制整明白了。

日志現象

重新拉起來后,就立即開始了故障分析,因為沒有其他異常,就從錯誤日志開始排查,我們集群三個節點,發現另外兩臺在停掉一臺3分鐘后,都出現大批量的鏈接超時,刪除了鏈接對應的client,從而觸發了推送,導致客戶端沒有了節點可用。

客戶端空保護

dubbo consumr在路由時,如果對打tag的機器做路由不能給正常的機器使用,即使只剩下一臺打tag的機器,也不能用,反而對tag的流量可用用非tag的機器,即做了降級,是不是很難理解,不為正常的流量做降級,反而為灰度的這點流量做了兜底,個人覺得這個設計很坑,因為我們線上都會有一臺灰度的機器,nacos推送的時候,最后存活的是這個灰度的機器,consumer 端就game over了,即出現no provider錯誤。

同步原理

經過日志分析,最終定位是同步延遲導致的,但為啥同步會延遲了,同步的機制和原理又是怎么樣的,你一定又這些疑問,下面我們就來說明下同步的原理,只有搞明白了同步的原理,才能理解為啥在下線一臺后,同步就會延遲。

集群同步架構

nacos 注冊中心是最終一致性,2.x的版本和客戶端是通過grpc長鏈接來實現的,所以集群集群的每個節點會把注冊到自己節點的client,同步給集群其他幾臺節點,具體的架構圖如下:

image.png
  • client :就是provdier和nacos 建立鏈接后 nacos維護的一個記錄,用來標記一個客戶端,分兩種:

    • native client:即直接鏈接在本節點的provider,也該節點是client的責任節點。
    • 非native client,即是集群其他節點同步過來的,也就是非責任節點,
  • Verify 任務
    同步給其他節點,怎么知道該client是否活的呢,因為鏈接又不是和它建立的,為了解決這個問題,nacos 會為所有要同步的節點,生成一個verify 任務,去更新下同步過去的client的活躍時間,來讓集群其他節點,認為是活的,不要給刪了,nacos會有一個定時執行的超時檢查任務,如果在3分鐘內這個client 沒有更新活躍時間,就認為該同步過來的節點超時了,要干掉了

  • Delete 任務
    正常情況下,通過verify 任務來續約,如果nacos發現這個client鏈接端了,異常了,比如pod重啟了,nacos 就會通過delete task 來告訴其他節點,該client已經掛了,不需要維護了。

超時機制

上面介紹verify 任務時,說了超時檢查,為了更好的理解,再提供一張圖:


image.png

同步原理

通過上面同步,verify,delete 任務,nacos讓provider在集群里保持一致,做到了服務注冊無論鏈接到那個nacos節點,consumer 鏈接誰,都能消費到全量的數據,就是這個同步來保證的。

上面只是一個概括同步機制,那同步是怎么實現的呢,寫再多,不如一張圖好理解,真正實現同步的原理在下面:

nacos-sync-arch.drawio.png

知識點:

  • 集群又幾個節點,就同步幾次,每次都生成一個同步任務。
  • 當前同步是添加到任務集合ConcurrentMap就結束,異步化。
  • 有一個線程專門負責從ConcurrentMap取任務,因為任務支持延遲,就是在這里判斷的,超過延遲時間,就刪除,同時生成一個新的提交到同步線程引擎隊列。
  • 同步引擎線程池,線程數和cpu核數保持一致,每個線程匹配一個大小為2的15次方的隊列,執行線程就不斷的從這個隊列取任務來發給目前節點。
  • 網絡發送是通過grpc異步發送。
  • 失敗處理機制,因為不能丟任務,所以失敗了是要把這個任務重新加回到隊列。

根因分析

通過這個圖,特地標記紅色的地方,就是我們這次停了一臺機器導致服務調用失敗的根因,我們用的是2.0.3的版本,我們集群3臺機器,所以停了一臺時,這臺機器上的鏈接全部斷開,客戶端會重連,這時就產生了很多的同步任務,要同步,好了這么多任務要同步,但是一個任務要同步給兩臺機器,也就是兩個任務,同步給這個故障的節點由于在前面沒有判斷,到最后執行同步的線程檢查的時候,肯定是失敗的,就等100ms,還重試3次,也就是往這臺故障的節點同步一個任務,這個同步線程要阻塞300ms,才能去處理下一個任務,這個地方的代碼貼下,更直觀點:

image.png

所以在停掉一臺節點的情況,一個任務要延遲300ms,哪超過600個任務要同步時,這地600以后的任務同步延遲就在3分鐘以上,就超過了前面我們說的同步client的超時時間,就觸發了刪除操作,push了錯誤的數據給客戶端。

所以如果你是2.0.3的版本,平時重啟時一定要快,如果要長時間下線的,一定要升級到新的版本,否則給你的大禮就是故障了。

解決方案

上面的問題, 是因為同步延遲導致超時,被刪除,觸發推送,但是服務本身是沒有任何問題的,而且除了這個場景外,還會有其他的問題導致錯誤推送,比如nacos 節點cpu100%,fugc等,網絡問題等,都會觸發該問題,所以我們需要一個完整的解決方案,確保nacos 無論出現了啥問提,都不能影響服務的調用,服務都是正常的,你nacos 有問題,為啥影響服務調用。

所以我們從兩個方便入手,一個是增加一個反向檢查,確保服務正常時,不再亂推,還有一個就時推拉空保護。兩個措施結合,能把nacos穩住。

反向檢查

在超時任務觸發時,如果超時了,就對client做一次健康檢查,通過類似tcp telnet機制,因為我們有服務的ip和端口,通過建立鏈接來判斷是否成功,現在服務注冊的都有這個機制,比如consul都支持,不過這里需要注意的事,檢查通過了,需要做一些臟數據清理。

推拉空保護

客戶端從nacos獲取數據,就兩個地方,一個是客戶端主動拉,一個是nacos推送,所以在這兩個地方都要加一個保護,防止推送空的實例,我們是定義一個空的發展,計算實例數小于該閥值時,就不推送或者飯后空的集合。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容