服務(wù)注冊與發(fā)現(xiàn)的原理和實現(xiàn)

什么是服務(wù)注冊發(fā)現(xiàn)?

對于搞微服務(wù)的同學來說,服務(wù)注冊、服務(wù)發(fā)現(xiàn)的概念應(yīng)該不會太陌生。

簡單來說,當服務(wù)A需要依賴服務(wù)B時,我們就需要告訴服務(wù)A,哪里可以調(diào)用到服務(wù)B,這就是服務(wù)注冊發(fā)現(xiàn)要解決的問題。

image
  • Service B 把自己注冊到 Service Registry 叫做 服務(wù)注冊
  • Service AService Registry 發(fā)現(xiàn) Service B 的節(jié)點信息叫做 服務(wù)發(fā)現(xiàn)

服務(wù)注冊

服務(wù)注冊是針對服務(wù)端的,服務(wù)啟動后需要注冊,分為幾個部分:

  • 啟動注冊
  • 定時續(xù)期
  • 退出撤銷

啟動注冊

當一個服務(wù)節(jié)點起來之后,需要把自己注冊到 Service Registry 上,便于其它節(jié)點來發(fā)現(xiàn)自己。注冊需要在服務(wù)啟動完成并可以接受請求時才會去注冊自己,并且會設(shè)置有效期,防止進程異常退出后依然被訪問。

定時續(xù)期

定時續(xù)期相當于 keep alive,定期告訴 Service Registry 自己還在,能夠繼續(xù)服務(wù)。

退出撤銷

當進程退出時,我們應(yīng)該主動去撤銷注冊信息,便于調(diào)用方及時將請求分發(fā)到別的節(jié)點。同時,go-zero 通過自適應(yīng)的負載均衡來保證即使節(jié)點退出沒有主動注銷,也能及時摘除該節(jié)點。

服務(wù)發(fā)現(xiàn)

服務(wù)發(fā)現(xiàn)是針對調(diào)用端的,一般分為兩類問題:

  • 存量獲取
  • 增量偵聽

還有一個常見的工程問題是

  • 應(yīng)對服務(wù)發(fā)現(xiàn)故障

當服務(wù)發(fā)現(xiàn)服務(wù)(比如 etcd, consul, nacos等)出現(xiàn)問題的時候,我們不要去修改已經(jīng)獲取到的 endpoints 列表,從而可以更好的確保 etcd 等宕機后所依賴的服務(wù)依然可以正常交互。

存量獲取

image

Service A 啟動時,需要從 Service Registry 獲取 Service B 的已有節(jié)點列表:Service B1, Service B2, Service B3,然后根據(jù)自己的負載均衡算法來選擇合適的節(jié)點發(fā)送請求。

增量偵聽

上圖已經(jīng)有了 Service B1, Service B2, Service B3,如果此時又啟動了 Service B4,那么我們就需要通知 Service A 有個新增的節(jié)點。如圖:

image

應(yīng)對服務(wù)發(fā)現(xiàn)故障

對于服務(wù)調(diào)用方來說,我們都會在內(nèi)存里緩存一個可用節(jié)點列表。不管是使用 etcd,consul 或者 nacos 等,我們都可能面臨服務(wù)發(fā)現(xiàn)集群故障,以 etcd 為例,當遇到 etcd 故障時,我們就需要凍結(jié) Service B 的節(jié)點信息而不去變更,此時一定不能去清空節(jié)點信息,一旦清空就無法獲取了,而此時 Service B 的節(jié)點很可能都是正常的,并且 go-zero 會自動隔離和恢復故障節(jié)點。

image

服務(wù)注冊、服務(wù)發(fā)現(xiàn)的基本原理大致如此,當然實現(xiàn)起來還是比較復雜的,接下來我們一起看看 go-zero 里支持哪些服務(wù)發(fā)現(xiàn)的方式。

go-zero 之內(nèi)置服務(wù)發(fā)現(xiàn)

go-zero 默認支持三種服務(wù)發(fā)現(xiàn)方式:

  • 直連
  • 基于 etcd 的服務(wù)發(fā)現(xiàn)
  • 基于 kubernetes endpoints 的服務(wù)發(fā)現(xiàn)

直連

直連是最簡單的方式,當我們的服務(wù)足夠簡單時,比如單機即可承載我們的業(yè)務(wù),我們可以直接只用這種方式。

image

rpc 的配置文件里直接指定 endpoints 即可,比如:

Rpc:
  Endpoints:
  - 192.168.0.111:3456
  - 192.168.0.112:3456

zrpc 調(diào)用端就會分配負載到這兩個節(jié)點上,其中一個節(jié)點有問題時 zrpc 會自動摘除,等節(jié)點恢復時會再次分配負載。

這個方法的缺點是不能動態(tài)增加節(jié)點,每次新增節(jié)點都需要修改調(diào)用方配置并重啟。

基于 etcd 的服務(wù)發(fā)現(xiàn)

當我們的服務(wù)有一定規(guī)模之后,因為一個服務(wù)可能會被很多個服務(wù)依賴,我們就需要能夠動態(tài)增減節(jié)點,而無需修改很多的調(diào)用方配置并重啟。

常見的服務(wù)發(fā)現(xiàn)方案有 etcd, consul, nacos 等。

image

go-zero內(nèi)置集成了基于 etcd 的服務(wù)發(fā)現(xiàn)方案,具體使用方法如下:

Rpc:
  Etcd:
     Hosts:
     - 192.168.0.111:2379
     - 192.168.0.112:2379
     - 192.168.0.113:2379
     Key: user.rpc
  • Hostsetcd 集群地址
  • Key 是服務(wù)注冊上去的 key

基于 Kubernetes Endpoints 的服務(wù)發(fā)現(xiàn)

如果我們的服務(wù)都是部署在 Kubernetes 集群上的話,Kubernetes 本身是通過自帶的 etcd 管理集群狀態(tài)的,所有的服務(wù)都會把自己的節(jié)點信息注冊到 Endpoints 對象,我們可以直接給 deployment 權(quán)限去讀取集群的 Endpoints 對象即可獲得節(jié)點信息。

image
  • Service B 的每個 Pod 啟動時,會將自己注冊到集群的 Endpoints
  • Service A 的每個 Pod 啟動時,可以從集群的 Endpoints 里獲取 Service B 的節(jié)點信息
  • Service B 的節(jié)點發(fā)生改變時,Service A 可以通過 watch 集群的 Endpoints 感知到

在這個機制工作之前,我們需要配置好當前 namespace 內(nèi) pod 對集群 Endpoints 訪問權(quán)限,這里有三個概念:

  • ClusterRole
    • 定義集群范圍的權(quán)限角色,不受 namespace 控制
  • ServiceAccount
    • 定義 namespace 范圍內(nèi)的 service account
  • ClusterRoleBinding
    • 將定義好的 ClusterRole 和不同 namespaceServiceAccount 進行綁定

具體的 Kubernetes 配置文件可以參考 這里,其中 namespace 按需修改。

注意:當啟動時報沒有權(quán)限獲取 Endpoints 時記得檢查這些配置有沒落實 :)

zrpc 的基于 Kubernetes Endpoints 的服務(wù)發(fā)現(xiàn)使用方法如下:

Rpc:
  Target: k8s://mynamespace/myservice:3456

其中:

  • mynamespace:被調(diào)用的 rpc 服務(wù)所在的 namespace
  • myservice:被調(diào)用的 rpc 服務(wù)的名字
  • 3456:被調(diào)用的 rpc 服務(wù)的端口

在創(chuàng)建 deployment 配置文件時一定要加上 serviceAccountName 來指定使用哪個 ServiceAccount,示例如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: alpine-deployment
  labels:
    app: alpine
spec:
  replicas: 1
  selector:
    matchLabels:
      app: alpine
  template:
    metadata:
      labels:
        app: alpine
    spec:
      serviceAccountName: endpoints-reader
      containers:
      - name: alpine
        image: alpine
        command:
        - sleep
        - infinity

注意其中 serviceAccountName 指定該 deployment 創(chuàng)建出來的 pod 用哪個 ServiceAccount。

serverclient 都部署到 Kubernetes 集群里之后可以通過以下命令滾動重啟所有 server 節(jié)點

kubectl rollout restart deploy -n adhoc server-deployment

利用如下命令查看 client 節(jié)點日志:

kubectl -n adhoc logs -f deploy/client-deployment --all-containers=true

可以看到我們的服務(wù)發(fā)現(xiàn)機制完美跟進了 server 節(jié)點的變化,并且在服務(wù)更新期間沒有出現(xiàn)異常請求。

完整代碼示例見 https://github.com/zeromicro/zero-examples/tree/main/discovery/k8s

下一篇文章我將講解在 go-zero 里如何實現(xiàn)基于 consul, nacos 等的服務(wù)注冊發(fā)現(xiàn),敬請期待!

項目地址

https://github.com/tal-tech/go-zero

歡迎使用 go-zerostar 支持我們!

微信交流群

關(guān)注『微服務(wù)實踐』公眾號并點擊 交流群 獲取社區(qū)群二維碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容