服務探活的五種方式
幾個月前,我在《4 個實驗,徹底搞懂 TCP 連接的斷開》這篇文章中給自己挖了個坑:
文中提到的實際問題
就是服務探活,今天來填上這個坑。
在微服務架構下,服務提供方(Provider)的節點一般不止一個,消費方(Consumer)根據負載均衡算法挑選一個健康
的節點進行調用。識別 Provider 節點是否健康,這便是服務探活 要討論的內容。
健康的節點
可定義爲能正常響應 Consumer 請求的節點,不健康自然是不能正常響應 Consumer 請求的節點
不健康
的原因可能是物理上的斷電、斷網、硬件故障,也可能是網絡延遲、進程異常退出或進程無法處理請求。
總之一句話總結起來就是 Provider 節點沒有摘除流量前,就無法處理請求了。可以分爲三類:
-
系統異常:如斷電、斷網、其他硬件故障、或操作系統異常退出
-
進程異常退出:進程異常退出,端口掛掉,如有註銷機制但沒來得及註銷,如執行了 kill -9
-
進程無法處理請求:端口還在,但服務無法正常響應,如 Full GC 期間
一個 Provider 節點的狀態只有健康和不健康,由健康到不健康稱之爲探死
,由不健康到健康稱之爲探活
,一般我們不分這麼細,統一叫探活
。
至於是誰來探,可能是 Consumer,也可能是註冊中心,甚至是某個單獨的探活組件。我們就從探活的發起者來列舉目前主流的探活方式。
Consumer 被動探活
最簡單的是在 Consumer 側進行探活,如果 Consumer 調用 Provider 報錯,則 Consumer 將該 Provider 剔掉,爲了防止偶發的網絡抖動或其他干擾,可設置一個時間窗口,窗口內失敗達 N 次則剔除,當過了這段時間,再把這個 Provider 重置爲正常。
這種方式的典型代表是 Nginx,Nginx 可配置多長時間內,失敗多少次則認爲該 Provider 不可用,其失敗可以是連接失敗、也可以是某些 http 狀態碼(如 4xx,5xx)
這種方案的缺點很明顯,需要真實流量去檢測,如果配置了失敗繼續轉發給下一個 Provider,則時間窗口的開始的一段時間內耗時上升,未配置則直接報錯,所以無論怎麼配置,對服務都是有影響的。
Consumer 主動探活
Consumer 被動健康檢查的主要問題在於使用了真實流量檢測,其實只要稍微改一改,使用旁路的方式去檢測即可避免。
阿里的 Tengine 開源了一個nginx_upstream_check_module
模塊來做主動健康檢查。
這個旁路可以一直去探測 Provider,當檢測到異常時,將其標記爲不可用狀態,請求不再發往該 Provider,若檢測到 Provider 健康時,再將其標記爲健康。
Consumer 側的探活在 RPC 框架實現的比較少,不知道是基於怎樣的一種考慮,其實有些企業內會在 Consumer 側已經加入了探活機制,比如愛奇藝
在 Dubbo 的 Consumer 側增加了探活機制,其實我所在的公司內部 RPC 框架也是有 Consumer 側的服務探活。
參考《愛奇藝在 Dubbo 生態下的微服務架構實踐》https://developer.aliyun.com/article/771495
但 Dubbo 官方沒有集成,至於爲什麼,我也去 github 上問過,不過沒人回覆~
Provider 上報心跳
當有一個註冊中心時,探活這項任務就可以交給註冊中心了。
最簡單的,我們想到了心跳保持這個策略,Provider 註冊成功後,一直向註冊中心發送心跳包,註冊中心定時檢查 Provider,如果長時間未發送心跳包,就將其置爲不可用,並告知 Consumer,如果心跳恢復,則將其恢復並通知。
某些組件也支持這種續約
的特性,如 etcd、redis 等都可以構建類似的系統。
這種方式的代表是 Nacos 1.x 版本中的臨時實例
。
慢慢你會發現這種方式摘除故障節點的時效性和資源的使用成正相關,如果你想要更好的時效性,就必須縮短心跳間隔,從而會增加心跳請求量,每次心跳得更新每個節點的上次心跳時間
,佔用了大量資源。
Provider 與註冊中心會話保持
爲了解決心跳請求佔用大量資源的問題,我們想到了 TCP 連接不是一個天然的健康檢查機制嗎?如果僅僅依靠 TCP 連接可以嗎?
這就是之前文章埋下的坑,再次總結一下這篇文章《4 個實驗,徹底搞懂 TCP 連接的斷開》中關於 TCP 連接斷開的場景:
-
如果是進程終止、無論是正常或者是異常,只要操作系統還在,TCP 連接就會正確斷開
-
如果是斷電、斷網或其他因素導致操作系統掛掉,則網絡不一定能正確斷開,還得分情況
-
如果此時註冊中心有往 Provider 發送數據,那麼是能及時感知到 Provider 的異常,並斷開連接的
-
如果註冊中心沒有往 Provider 發送數據,是不能及時感知連接的斷開,即使配置了 TCP 的 KeepAlive,也需要大概 2 小時才能感知到
2 小時肯定不能接受,爲了防止這種情況,光靠 TCP 是不夠的,還得在應用層實現一個心跳檢測,爲了節省資源,可以將心跳包設計的很小,發送頻率不需要那麼高,通常 1 分鐘內能感知即可。
因爲只有斷電、斷網或硬件故障纔會導致無法感知連接的斷開,這個比例很小。
可以參考下圖,雖然圖中的數據是我杜撰的,但八九不離十吧,可以看到系統異常只佔 1%,這 1% 中未發數據可能更少,所以可以認爲這個概率很小。
這種方式比較常見,像 Dubbo 使用的 Zookeeper,Nacos 2.x 版本(gRPC)的臨時實例,SOFARegistry 等等都用了這這種方式。
其中 SOFARegistry 比較有意思,它提出了一種連接敏感
的長連接,乍一看以爲用了什麼黑科技,後來看了代碼發現就是 TCP 連接加應用層的心跳檢測,這些被他們封裝在SOFABolt
通信框架中。
參考《海量數據下的註冊中心 - SOFARegistry 架構介紹》https://mp.weixin.qq.com/s/mZo7Dg6gfNqXoetaqgwMww
但這個方式也有一個明顯的缺點,如果網絡狀況不好的情況下,TCP 連接比較容易斷開,會導致節點頻繁上下線。
註冊中心主動探測
除了上述的方式,還有一種註冊中心(甚至是一個單獨的組件)主動探測 Provider 的方式,與 Consumer 主動探測類似,只不過把探測任務移交給了註冊中心或一個單獨的組件。
主動探測有個最大的優勢是可以擴展非常豐富的探測方式,比如最常見的探測端口是否存活,又或者探測 Provider 的一個 http 接口返回是否符合預期,甚至可以擴展爲 MySQL、Redis 等等協議的探測。
這也是種能解決服務假死的探活方式,Nacos 中的永久實例
探活就是採用的這種方式。
但這種方式在實際使用的時候要考慮主動探測組件的高可用,高可用就得存在副本,可採取主備方式。
如果單機存在性能瓶頸,還得分佈式探活,主備可能就不適合,得有一個分佈式協調者,這要說又得長篇大論。但這裏你知道有這麼個事兒就可以了。
考量探活的指標有三個:
-
能不能探出來?——功能性
-
什麼時候探出來?——時效性
-
會不會探錯了?——穩定性
功能上最強的是帶語義的主動探測,時效性最強的要屬長連接會話保持。
穩定性不好說誰強誰弱,但一般會給一個集羣設置一個探活摘除的比例,比如最多摘除 50% 機器,防止探活錯誤導致節點全部下線,這也算是一種兜底策略吧。
搜索關注微信公衆號 "捉蟲大師",回覆關鍵字「Nacos」送你一本《Nacos 架構與原理》電子書,Dubbo 資料也在準備中,不想錯過可以點個關注。
另外我也準備組建一個技術交流羣,但現在也不知道會有多少人,所以大家先加我微信 MrRoshi,備註加羣,一起交流技術,超過一定人數我就拉一個~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/tw4-BIUZqpJLLEd0n09GNg