歡迎光臨
每天分享高質量文章

【RPC 專題】深入理解 RPC 之服務註冊與發現篇

點選上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

摘要: 原創出處 https://www.cnkirito.moe/rpc-registry/ 「老徐」歡迎轉載,保留摘要,謝謝!

  • 註冊中心的抽象

  • 註冊資訊概覽

  • 註冊資訊詳解

  • 感知服務的下線

  • 註冊中心對比

  • 總結


在我們之前 RPC 原理的分析中,主要將筆墨集中在 Client 和 Server 端。而成熟的服務治理框架中不止存在這兩個角色,一般還會有一個 Registry(註冊中心)的角色。一張圖就可以解釋註冊中心的主要職責。

註冊中心的地位

註冊中心的地位

  • 註冊中心,用於服務端註冊遠端服務以及客戶端發現服務

  • 服務端,對外提供後臺服務,將自己的服務資訊註冊到註冊中心

  • 客戶端,從註冊中心獲取遠端服務的註冊資訊,然後進行遠端過程呼叫

目前主要的註冊中心可以藉由 zookeeper,eureka,consul,etcd 等開源框架實現。網際網路公司也會因為自身業務的特性自研,如美團點評自研的 MNS,新浪微博自研的 vintage。

本文定位是對註冊中心有一定瞭解的讀者,所以不過多闡述註冊中心的基礎概念。

註冊中心的抽象

借用開源框架中的核心介面,可以幫助我們從一個較為抽象的高度去理解註冊中心。例如 motan 中的相關介面:

服務註冊介面

public interface RegistryService {
    //1. 向註冊中心註冊服務
    void register(URL url);
    //2. 從註冊中心摘除服務
    void unregister(URL url);
    //3. 將服務設定為可用,供客戶端呼叫
    void available(URL url);
    //4. 禁用服務,客戶端無法發現該服務
    void unavailable(URL url);
      //5. 獲取已註冊服務的集合
    Collection getRegisteredServiceUrls();
}

服務發現介面

public interface DiscoveryService {
    //1. 訂閱服務
    void subscribe(URL url, NotifyListener listener);
    //2. 取消訂閱
    void unsubscribe(URL url, NotifyListener listener);
    //3. 發現服務串列
    List discover(URL url);
}

主要使用的方法是 RegistryService#register(URL) 和 DiscoveryService#discover(URL)。其中這個 URL 引數被傳遞,顯然也是很重要的一個類。

public class URL {
    private String protocol;//協議名稱
    private String host;
    private int port;
    // interfaceName,也代表著路徑
    private String path;
    private Map parameters;
    private volatile transient Map numbers;
}

註冊中心也沒那麼玄乎,其實可以簡單理解為:提供一個儲存介質,供服務提供者和服務消費者共同連線,而儲存的主要資訊就是這裡的 URL。但是具體 URL 都包含了什麼實際資訊,我們還沒有一個直觀的感受。

註冊資訊概覽

以元老級別的註冊中心 zookeeper 為例,看看它實際都儲存了什麼資訊以及它是如何持久化上一節的 URL。

為了測試,我建立了一個 RPC 服務介面 com.sinosoft.student.api.DemoApi ,並且在 6666 埠暴露了這個服務的實現類,將其作為服務提供者。在 6667 埠遠端呼叫這個服務,作為服務消費者。兩者都連線本地的 zookeeper,本機 ip 為 192.168.150.1。

使用 zkClient.bash 或者 zkClient.sh 作為客戶端連線到本地的 zookeeper,執行如下的命令:

[zk: localhost:2181(CONNECTED) 1] ls /motan/demo_group/com.sinosoft.student.api.DemoApi
> [client, server, unavailableServer]

zookeeper 有著和 linux 類似的命令和結構,其中 motan,demo_group,com.sinosoft.student.api.DemoApi,client, server, unavailableServer 都是一個個節點。可以從上述命令看出他們的父子關係。

/motan/demo_group/com.sinosoft.student.api.DemoApi 的結構為 /框架標識/分組名/介面名,其中的分組是 motan 為了隔離不同組的服務而設定的。這樣,介面名稱相同,分組不同的服務無法互相發現。如果此時有一個分組名為 demo_group2 的服務,介面名稱為 DemoApi2,則 motan 會為其建立一個新的節點 /motan/demo_group2/com.sinosoft.student.api.DemoApi2

而 client,server,unavailableServer 則就是服務註冊與發現的核心節點了。我們先看看這些節點都儲存了什麼資訊。

server 節點:

[zk: localhost:2181(CONNECTED) 2] ls /motan/demo_group/com.sinosoft.student.api.DemoApi/server
> [192.168.150.1:6666]

[zk: localhost:2181(CONNECTED) 3] get /motan/demo_group/com.sinosoft.student.api.DemoApi/server/192.168.150.1:6666
> motan://192.168.150.1:6666/com.sinosoft.student.api.DemoApi?serialization=hessian2&protocol;=motan&isDefault;=true&maxContentLength;=1548576&shareChannel;=true&refreshTimestamp;=1515122649835&id;=motanServerBasicConfig&nodeType;=service&export=motan:6666&requestTimeout;=9000000&accessLog;=false&group;=demo_group&

client 節點:

[zk: localhost:2181(CONNECTED) 4] ls /motan/demo_group/com.sinosoft.student.api.DemoApi/client
> [192.168.150.1]
[zk: localhost:2181(CONNECTED) 5] get /motan/demo_group/com.sinosoft.student.api.DemoApi/client/192.168.150.1
> motan://192.168.150.1:0/com.sinosoft.student.api.DemoApi?singleton=true&maxContentLength;=1548576✓=false&nodeType;=service&version;=1.0&throwException;=true&accessLog;=false&serialization;=hessian2&retries;=0&protocol;=motan&isDefault;=true&refreshTimestamp;=1515122631758&id;=motanClientBasicConfig&requestTimeout;=9000&group;=demo_group&

unavailableServer 節點是一個過渡節點,所以在一切正常的情況下不會存在資訊,它的具體作用在下麵會介紹。

從這些輸出資料可以發現,註冊中心承擔的一個職責就是儲存服務呼叫中相關的資訊,server 向 zookeeper 註冊資訊,儲存在 server 節點,而 client 實際和 server 共享同一個介面,介面名稱就是路徑名,所以也到達了同樣的 server 節點去獲取資訊。並且同時註冊到了 client 節點下(為什麼需要這麼做在下麵介紹)。

註冊資訊詳解

Server 節點

server 節點承擔著最重要的職責,它由服務提供者建立,以供服務消費者獲取節點中的資訊,從而定位到服務提供者真正網路拓撲位置以及得知如何呼叫。demo 中我只在本機 [192.168.150.1:6666] 啟動了一個實體,所以在server 節點之下,只存在這麼一個節點,繼續 get 這個節點,可以獲取更詳細的資訊

motan://192.168.150.1:6666/com.sinosoft.student.api.DemoApi?serialization=hessian2&protocol;=motan&isDefault;=true&maxContentLength;=1548576&shareChannel;=true&refreshTimestamp;=1515122649835&id;=motanServerBasicConfig&nodeType;=service&export;=motan:6666&requestTimeout;=9000000&accessLog;=false&group;=demo_group&

作為一個 value 值,它和 http 協議的請求十分相似,不過是以 motan:// 開頭,表達的意圖也很明確,這是 motan 協議和相關的路徑及引數,關於 RPC 中的協議,可以翻看我的上一篇文章《深入理解RPC之協議篇》。

serialization 對應序列化方式,protocol 對應協議名稱,maxContentLength 對應 RPC 傳輸中資料報文的最大長度,shareChannel 是傳輸層用到的引數,netty channel 中的一個屬性,group 對應分組名稱。

上述的 value 包含了 RPC 呼叫中所需要的全部資訊。

Client 節點

在 motan 中使用 zookeeper 作為註冊中心時,客戶端訂閱服務時會向 zookeeper 註冊自身,主要是方便對呼叫方進行統計、管理。但訂閱時是否註冊 client 不是必要行為,和不同的註冊中心實現有關,例如使用 consul 時便沒有註冊。

由於我們使用 zookeeper,也可以分析下 zookeeper 中都註冊了什麼資訊。

motan://192.168.150.1:0/com.sinosoft.student.api.DemoApi?singleton=true&maxContentLength;=1548576✓=false&nodeType;=service&version;=1.0&throwException;=true&accessLog;=false&serialization;=hessian2&retries;=0&protocol;=motan&isDefault;=true&refreshTimestamp;=1515122631758&id;=motanClientBasicConfig&requestTimeout;=9000&group;=demo_group

和 Server 節點的值類似,但也有客戶獨有的一些屬性,如 singleton 代表服務是否單例,check 檢查服務提供者是否存在,retries 代表重試次數,這也是 RPC 中特別需要註意的一點。

UnavailableServer 節點

unavailableServer 節點也不是必須存在的一個節點,它主要用來做 server 端的延遲上線,優雅關機。

延遲上線:一般推薦的服務端啟動流程為:server 向註冊中心的 unavailableServer 註冊,狀態為 unavailable,此時整個服務處於啟動狀態,但不對外提供服務,在服務驗證透過,預熱完畢,此時開啟心跳開關,此時正式提供服務。

優雅關機:當需要對 server 方進行維護升級時,如果直接關閉,則會影響到客戶端的請求。所以理想的情況應當是首先切斷流量,再進行 server 的下線。具體的做法便是:先關閉心跳開關,客戶端感知停止呼叫後,再關閉服務行程。

感知服務的下線

服務上線時自然要註冊到註冊中心,但下線時也得從註冊中心中摘除。註冊是一個主動的行為,這沒有特別要註意的地方,但服務下線卻是一個值得思考的問題。服務下線包含了主動下線和系統宕機等異常方式的下線。

臨時節點+長連線

在 zookeeper 中存在持久化節點和臨時節點的概念。持久化節點一經建立,只要不主動刪除,便會一直持久化存在;臨時節點的生命週期則是和客戶端的連線同生共死的,應用連線到 zookeeper 時建立一個臨時節點,使用長連線維持會話,這樣無論何種方式服務發生下線,zookeeper 都可以感知到,進而刪除臨時節點。zookeeper 的這一特性和服務下線的需求契合的比較好,所以臨時節點被廣泛應用。

主動下線+心跳檢測

並不是所有註冊中心都有臨時節點的概念,另外一種感知服務下線的方式是主動下線。例如在 eureka 中,會有 eureka-server 和 eureka-client 兩個角色,其中 eureka-server 儲存註冊資訊,地位等同於 zookeeper。當 eureka-client 需要關閉時,會傳送一個通知給 eureka-server,從而讓 eureka-server 摘除自己這個節點。但這麼做最大的一個問題是,如果僅僅只有主動下線這麼一個手段,一旦 eureka-client 非正常下線(如斷電,斷網),eureka-server 便會一直存在一個已經下線的服務節點,一旦被其他服務發現進而呼叫,便會帶來問題。為了避免出現這樣的情況,需要給 eureka-server 增加一個心跳檢測功能,它會對服務提供者進行探測,比如每隔30s傳送一個心跳,如果三次心跳結果都沒有傳回值,就認為該服務已下線。

註冊中心對比

Feature Consul zookeeper etcd euerka
服務健康檢查 服務狀態,記憶體,硬碟等 (弱)長連線,keepalive 連線心跳 可配支援
多資料中心 支援
kv儲存服務 支援 支援 支援
一致性 raft paxos raft
cap ca cp cp ap
使用介面(多語言能力) 支援http和dns 客戶端 http/grpc http(sidecar)
watch支援 全量/支援long polling 支援 支援 long polling 支援 long polling/大部分增量
自身監控 metrics metrics metrics
安全 acl /https acl https支援(弱)
spring cloud整合 已支援 已支援 已支援 已支援

一般而言註冊中心的特性決定了其使用的場景,例如很多框架支援 zookeeper,在我自己看來是因為其老牌,易用,但業界也有很多人認為 zookeeper 不適合做註冊中心,它本身是一個分散式協調元件,並不是為註冊服務而生,server 端註冊一個服務節點,client 端並不需要在同一時刻拿到完全一致的服務串列,只要最終一致性即可。在跨IDC,多資料中心等場景下 consul 發揮了很大的優勢,這也是很多網際網路公司選擇使用 consul 的原因。 eureka 是 ap 註冊中心,並且是 spring cloud 預設使用的元件,spring cloud eureka 較為貼近 spring cloud 生態。

總結

註冊中心主要用於解耦服務呼叫中的定位問題,是分散式系統必須面對的一個問題。更多專業性的對比,可以期待 spring4all.com 的註冊中心專題討論,相信會有更為細緻地對比。

666. 彩蛋

如果你對 RPC 感興趣,歡迎加入我的知識一起交流。


知識星球



目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:

01. 除錯環境搭建
02. 專案結構一覽
03. API 配置(一)之應用
04. API 配置(二)之服務提供者
05. API 配置(三)之服務消費者
06. 屬性配置
07. XML 配置
08. 核心流程一覽

09. 拓展機制 SPI

10. 執行緒池

11. 服務暴露(一)之遠端暴露(Injvm)

12. 服務暴露(二)之遠端暴露(Dubbo)


一共 60 篇++

贊(0)

分享創造快樂