幾個月前,我在《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資料也在準備中,不想錯過可以點個關注。