服務註冊與發現的原理和實現

kevwan發表於2021-09-12

什麼是服務註冊發現?

對於搞微服務的同學來說,服務註冊、服務發現的概念應該不會太陌生。

簡單來說,當服務A需要依賴服務B時,我們就需要告訴服務A,哪裡可以呼叫到服務B,這就是服務註冊發現要解決的問題。

  • Service B 把自己註冊到 Service Registry 叫做 服務註冊
  • Service AService Registry 發現 Service B 的節點資訊叫做 服務發現

服務註冊

服務註冊是針對服務端的,服務啟動後需要註冊,分為幾個部分:

  • 啟動註冊
  • 定時續期
  • 退出撤銷

啟動註冊

當一個服務節點起來之後,需要把自己註冊到 Service Registry 上,便於其它節點來發現自己。註冊需要在服務啟動完成並可以接受請求時才會去註冊自己,並且會設定有效期,防止程式異常退出後依然被訪問。

定時續期

定時續期相當於 keep alive,定期告訴 Service Registry 自己還在,能夠繼續服務。

退出撤銷

當程式退出時,我們應該主動去撤銷註冊資訊,便於呼叫方及時將請求分發到別的節點。同時,go-zero 通過自適應的負載均衡來保證即使節點退出沒有主動登出,也能及時摘除該節點。

服務發現

服務發現是針對呼叫端的,一般分為兩類問題:

  • 存量獲取
  • 增量偵聽

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

  • 應對服務發現故障

當服務發現服務(比如 etcd, consul, nacos等)出現問題的時候,我們不要去修改已經獲取到的 endpoints 列表,從而可以更好的確保 etcd 等當機後所依賴的服務依然可以正常互動。

存量獲取

Service A 啟動時,需要從 Service Registry 獲取 Service B 的已有節點列表:Service B1, Service B2, Service B3,然後根據自己的負載均衡演算法來選擇合適的節點傳送請求。

增量偵聽

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

應對服務發現故障

對於服務呼叫方來說,我們都會在記憶體裡快取一個可用節點列表。不管是使用 etcdconsul 或者 nacos 等,我們都可能面臨服務發現叢集故障,以 etcd 為例,當遇到 etcd 故障時,我們就需要凍結 Service B 的節點資訊而不去變更,此時一定不能去清空節點資訊,一旦清空就無法獲取了,而此時 Service B 的節點很可能都是正常的,並且 go-zero 會自動隔離和恢復故障節點。

服務註冊、服務發現的基本原理大致如此,當然實現起來還是比較複雜的,接下來我們一起看看 go-zero 裡支援哪些服務發現的方式。

go-zero 之內建服務發現

go-zero 預設支援三種服務發現方式:

  • 直連
  • 基於 etcd 的服務發現
  • 基於 kubernetes endpoints 的服務發現

直連

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

rpc 的配置檔案裡直接指定 endpoints 即可,比如:

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

zrpc 呼叫端就會分配負載到這兩個節點上,其中一個節點有問題時 zrpc 會自動摘除,等節點恢復時會再次分配負載。

這個方法的缺點是不能動態增加節點,每次新增節點都需要修改呼叫方配置並重啟。

基於 etcd 的服務發現

當我們的服務有一定規模之後,因為一個服務可能會被很多個服務依賴,我們就需要能夠動態增減節點,而無需修改很多的呼叫方配置並重啟。

常見的服務發現方案有 etcd, consul, nacos 等。

go-zero內建整合了基於 etcd 的服務發現方案,具體使用方法如下:

Rpc:
  Etcd:
     Hosts:
     - 192.168.0.111:2379
     - 192.168.0.112:2379
     - 192.168.0.113:2379
     Key: user.rpc
  • Hostsetcd 叢集地址
  • Key 是服務註冊上去的 key

基於 Kubernetes Endpoints 的服務發現

如果我們的服務都是部署在 Kubernetes 叢集上的話,Kubernetes 本身是通過自帶的 etcd 管理叢集狀態的,所有的服務都會把自己的節點資訊註冊到 Endpoints 物件,我們可以直接給 deployment 許可權去讀取叢集的 Endpoints 物件即可獲得節點資訊。

  • Service B 的每個 Pod 啟動時,會將自己註冊到叢集的 Endpoints
  • Service A 的每個 Pod 啟動時,可以從叢集的 Endpoints 裡獲取 Service B 的節點資訊
  • Service B 的節點發生改變時,Service A 可以通過 watch 叢集的 Endpoints 感知到

在這個機制工作之前,我們需要配置好當前 namespacepod 對叢集 Endpoints 訪問許可權,這裡有三個概念:

  • ClusterRole
    • 定義叢集範圍的許可權角色,不受 namespace 控制
  • ServiceAccount
    • 定義 namespace 範圍內的 service account
  • ClusterRoleBinding
    • 將定義好的 ClusterRole 和不同 namespaceServiceAccount 進行繫結

具體的 Kubernetes 配置檔案可以參考 這裡,其中 namespace 按需修改。

注意:當啟動時報沒有許可權獲取 Endpoints 時記得檢查這些配置有沒落實 ?

zrpc 的基於 Kubernetes Endpoints 的服務發現使用方法如下:

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

其中:

  • mynamespace:被呼叫的 rpc 服務所在的 namespace
  • myservice:被呼叫的 rpc 服務的名字
  • 3456:被呼叫的 rpc 服務的埠

在建立 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 建立出來的 pod 用哪個 ServiceAccount

serverclient 都部署到 Kubernetes 叢集裡之後可以通過以下命令滾動重啟所有 server 節點

kubectl rollout restart deploy -n adhoc server-deployment

利用如下命令檢視 client 節點日誌:

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

可以看到我們的服務發現機制完美跟進了 server 節點的變化,並且在服務更新期間沒有出現異常請求。

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

下一篇文章我將講解在 go-zero 裡如何實現基於 consul, nacos 等的服務註冊發現,敬請期待!

專案地址

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

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

微信交流群

關注『微服務實踐』公眾號並點選 交流群 獲取社群群二維碼。

相關文章