深入理解RPC 服務發現

2021-10-09 17:34:27 字數 3763 閱讀 9816

先舉個例子,假如你要給一位以前從未合作過的同事發郵件請求幫助,但你卻沒有他的郵箱位址。這個時候你會怎麼辦呢?如果是我,我會選擇去看公司的企業「通訊錄」。

同理,為了高可用,在生產環境中服務提供方都是以集群的方式對外提供服務,集群裡面的這些 ip 隨時可能變化,我們也需要用一本「通訊錄」及時獲取到對應的服務節點,這個獲取的過程我們一般叫作「服務發現」。

對於服務呼叫方和服務提供方來說,其契約就是介面,相當於「通訊錄」中的姓名,服務節點就是提供該契約的乙個具體例項。服務 ip 集合作為「通訊錄」中的位址,從而可以通過介面獲取服務 ip 的集合來完成服務的發現。這就是我要說的 prc 框架的服務發現機制,如下圖所示:

服務訂閱:在服務呼叫方啟動的時候,去註冊中心查詢並訂閱服務提供方的 ip,然後快取到本地,並用於後續的遠端呼叫。

既然服務發現這麼「厲害」,那是不是很難實現啊?其實類似機制一直在我們身邊,我們回想下服務發現的本質,就是完成了介面跟服務提供者 ip 的對映。那我們能不能把服務提供者 ip 統一換成乙個網域名稱啊,利用已經成熟的 dns 機制來實現?

好,先帶著這個問題,簡單地看下 dns 的流程:

這兩個問題的答案都是:「不能」。這是因為為了提公升效能和減少 dns 服務的壓力,dns採取了多級快取機制,一般配置的快取時間較長,特別是 jvm 的預設快取是永久有效的,所以說服務呼叫者不能及時感知到服務節點的變化。

這時你可能會想,我是不是可以加乙個負載均衡裝置呢?將網域名稱繫結到這台負載均衡裝置上,通過 dns 拿到負載均衡的 ip。這樣服務呼叫的時候,服務呼叫方就可以直接跟 vip建立連線,然後由 vip 機器完成 tcp **,如下圖所示:

由此可見,dns 或者 vip 方案雖然可以充當服務發現的角色,但在 rpc 場景裡面直接用還是很難的。

那麼在 rpc 裡面我們該如何實現呢?我們還是要回到服務發現的本質,就是完成介面跟服務提供者 ip 之間的對映。這個對映是不是就是一種命名服務?當然,我們還希望註冊中心能完成實時變更推送,是不是像開源的 zookeeper、etcd 就可以實現?我很肯定地說「確實可以」。下面我就來介紹下一種基於 zookeeper 的服務發現方式。

整體的思路很簡單,就是搭建乙個 zookeeper 集群作為註冊中心集群,服務註冊的時候只需要服務節點向 zookeeper 節點寫入註冊資訊即可,利用 zookeeper 的 watcher 機制完成服務訂閱與服務下發功能,整體流程如下圖:

當服務提供方發起註冊時,會在服務提供方目錄中建立乙個臨時節點,節點中儲存該服務提供方的註冊資訊。

當服務呼叫方發起訂閱時,則在服務呼叫方目錄中建立乙個臨時節點,節點中儲存該服務呼叫方的資訊,同時服務呼叫方 watch 該服務的服務提供方目錄(/service/com.demo.xxservice/provider)中所有的服務節點資料。

當服務提供方目錄下有節點資料發生變更時,zookeeper 就會通知給發起訂閱的服務呼叫方。

我所在的技術團隊早期使用的 rpc 框架服務發現就是基於 zookeeper 實現的,並且還平穩執行了一年多,但後續團隊的微服務化程度越來越高之後,zookeeper 集群整體壓力也越來越高,尤其在集中上線的時候越發明顯。「集中爆發」是在一次大規模上線的時候,當時有超大批量的服務節點在同時發起註冊操作,zookeeper 集群的 cpu 突然飆公升,導致zookeeper 集群不能工作了,而且我們當時也無法立馬將 zookeeper 集群重新啟動,一直到 zookeeper 集群恢復後業務才能繼續上線。

經過我們的排查,引發這次問題的根本原因就是 zookeeper 本身的效能問題,當連線到zookeeper 的節點數量特別多,對 zookeeper 讀寫特別頻繁,且 zookeeper 儲存的目錄達到一定數量的時候,zookeeper 將不再穩定,cpu 持續公升高,最終宕機。而宕機之後,由於各業務的節點還在持續傳送讀寫請求,剛一啟動,zookeeper 就因無法承受瞬間的讀寫壓力,馬上宕機。

這次「意外」讓我們意識到,zookeeper 集群效能顯然已經無法支撐我們現有規模的服務集群了,我們需要重新考慮服務發現方案。

我們知道,zookeeper 的一大特點就是強一致性,zookeeper 集群的每個節點的資料每次發生更新操作,都會通知其它 zookeeper 節點同時執行更新。它要求保證每個節點的資料能夠實時的完全一致,這也就直接導致了 zookeeper 集群效能上的下降。這就好比幾個人在玩傳遞東西的遊戲,必須這一輪每個人都拿到東西之後,所有的人才能開始下一輪,而不是說我只要獲得到東西之後,就可以直接進行下一輪了。

而 rpc 框架的服務發現,在服務節點剛上線時,服務呼叫方是可以容忍在一段時間之後(比如幾秒鐘之後)發現這個新上線的節點的。畢竟服務節點剛上線之後的幾秒內,甚至更長的一段時間內沒有接收到請求流量,對整個服務集群是沒有什麼影響的,所以我們可以犧牲掉cp(強制一致性),而選擇 ap(最終一致),來換取整個註冊中心集群的效能和穩定性。

那麼是否有一種簡單、高效,並且最終一致的更新機制,能代替 zookeeper 那種資料強一致的資料更新機制呢?

因為要求最終一致性,我們可以考慮採用訊息匯流排機制。註冊資料可以全量快取在每個註冊中心記憶體中,通過訊息匯流排來同步資料。當有乙個註冊中心節點接收到服務節點註冊時,會產生乙個訊息推送給訊息匯流排,再通過訊息匯流排通知給其它註冊中心節點更新資料並進行服務下發,從而達到註冊中心間資料最終一致性,具體流程如下圖所示:

另外,你也可能會想到,服務呼叫方拿到的服務節點不是最新的,所以目標節點存在已經下線或不提供指定介面服務的情況,這個時候有沒有問題?這個問題我們放到了 rpc 框架裡面去處理,在服務呼叫方傳送請求到目標節點後,目標節點會進行合法性驗證,如果指定介面服務不存在或正在下線,則會拒絕該請求。服務呼叫方收到拒絕異常後,會安全重試到其它節點。

通過訊息匯流排的方式,我們就可以完成註冊中心集群間資料變更的通知,保證資料的最終一致性,並能及時地觸發註冊中心的服務下發操作。在 rpc 領域精耕細作後,你會發現,服務發現的特性是允許我們在設計超大規模集群服務發現系統的時候,捨棄強一致性,更多地考慮系統的健壯性。最終一致性才是分布式系統設計中更為常用的策略。

本篇文章主要分享了 rpc 框架服務發現機制,以及如何用 zookeeper 完成「服務發現」,還有zookeeper 在超大規模集群下作為註冊中心所存在的問題。

通常我們可以使用 zookeeper、etcd 或者分布式快取(如 hazelcast)來解決事件通知問題,但當集群達到一定規模之後,依賴的zookeeper 集群、etcd 集群可能就不穩定了,無法滿足我們的需求。

在超大規模的服務集群下,註冊中心所面臨的挑戰就是超大批量服務節點同時上下線,註冊中心集群接受到大量服務變更請求,集群間各節點間需要同步大量服務節點資料,最終導致如下問題:

rpc 框架依賴的註冊中心的服務資料的一致性其實並不需要滿足 cp,只要滿足 ap 即可。我們就是採用「訊息匯流排」的通知機制,來保證註冊中心資料的最終一致性,來解決這些問題的。

另外,在今天的內容中,很多知識點不只可以應用到 rpc 框架的「服務發現」中。例如服務節點資料的推送採用增量更新的方式,這種方式提高了註冊中心「服務下發」的效率,而這種方式,你還可以利用在其它地方,比如統一配置中心,用此方式可以提公升統一配置中心下發配置的效率。

深入理解 RPC 互動流程

們講解 rpc 的訊息互動流程,目的是搞清楚乙個簡單的 rpc 方法呼叫背後究竟發生了怎樣複雜曲折的故事,以看透 rpc 的本質。上圖是資訊系統互動模型巨集觀示意圖,rpc 的訊息互動則會深入到底層。rpc 是兩個子系統之間進行的直接訊息互動,它使用作業系統提供的套接字來作為訊息的載體,以特定的訊息...

Spring Cloud 雲服務深入理解

一.spring cloud的理解 1 微服務的特性 史上最簡單的 springcloud 教程 終章 2 微服務元件架構 圖中漏掉了 config server 和鏈路追蹤元件,config server 是乙個與所有服務相連的服務集群,鏈路追蹤元件則整合在每個服務中。3 spring cloud...

RPC服務註冊與發現

rpc遠端過程呼叫中,存在2個角色,乙個服務提供者 另乙個服務消費者。那如何讓呼叫者知道,存在哪些服務可以呼叫呢?即如何讓別人使用我們的服務呢?有同學說很簡單嘛,告訴使用者服務的ip以及埠就可以了啊。確實是這樣,這裡問題的關鍵在於是自動告知還是人肉告知。人肉告知的方式 如果你發現你的服務一台機器不夠...