微服務架構中常用的解決方案,總結了傳統服務發現方案
正常情況下當我們要訪問服務時需要知道服務實例地址和端口,如果服務實例地址和端口都是固定的我們可以直接將其配置在文件中使用,但大多數線上生產環境尤其容器部署情況下服務實例地址都是動態分配的,只有當服務實例實際部署之后才能獲得地址,服務調用者根本無法提取獲取服務實例地址和端口,只能在運行時通過服務發現組件解析服務名來獲取服務實例地址和端口。
服務發現簡單來講就是通過服務名找到提供服務的實例地址和端口,主要用于解決如何獲取服務實例地址問題。近年來隨著容器技術的興起,大量服務分散在系統各處,服務彼此之間調用都需要通過服務發現來實現。服務發現是分布式系統中不可或缺的關鍵組件,常用于構建服務發現解決方案的開源框架如Zookeeper、 Etcd、Consul。本文主要介紹如何基于Zookeeper、 Etcd、Consul構建服務發現方案并對其可能出現的問題進行討論。
一個標準的服務發現架構主要有三部分組成分別是服務注冊中心、服務調用者、服務提供者,架構圖如下所示:

服務注冊中心是服務發現的核心組件,其本質上是一個服務名和服務實例地址映射集合,除了提供基本的服務名解析功能外,還需要具備如下能力:
- 容錯(Fault Tolerance):服務注冊中心保存了分布式系統中所有服務名與服務實例地址映射,一旦故障必將導致整個系統不可用,是整個分布式系統核心,必須具備高可用性;
- 服務健康檢查(Service Health Check):服務注冊中心必須要能及時發現故障實例并將其注銷以防止被錯誤訪問;
- 監視器(Watcher):服務注冊中心必須具備及時通知服務調用者服務實例注冊或注銷的能力,以便服務調用者及時采取措施。
注冊或注銷服務實例一般有兩種選擇:
- 服務實例自己注冊即Self-Registration模式,在服務實例啟動成功后主動將自己注冊到服務注冊中心,這種方法好處是架構簡單但需要為服務用到的每種編程語言實現注冊代碼;
- 通過其他組件來注冊服務實例即Thrid-party Registration模式,例如使用一個獨立Agent通過輪詢或監聽事件去跟蹤運行的服務實例變化進行注冊或注銷,好處是服務實例與服務注冊中心解耦但引入第三方組件增加了架構復雜性。
服務發現方案
DNS
DNS(Domain Name System)是一種通過解析域名獲取IP和端口的機制。將SRV記錄注冊到DNS服務器上,通過DNS解析流程進行解析。但是DNS存在兩個問題:一是當服務實例啟動之后將SRV記錄注冊到DNS服務器上比較難,需要手動維護;二是DNS嚴重依賴緩存,服務使用方無法及時知道一個服務實例是否已經停止。
mDNS
mDNS(multicast DNS即組播DNS)是一種零配置的服務發現機制,在內部網絡中經常使用,每個服務都有一個內置的mDNS響應程序,從而不需要單獨的服務注冊中心。mDNS最大的問題就是要求網絡基礎設施支持IP多播(IP multicast),對于云環境來說顯然是無法滿足的,而且mDNS也無法解決DNS緩存問題。
DNS與mDNS都具備良好的容錯能力,但缺乏服務健康檢查和變化通知機制。
Zookeeper
Zookeeper提供分布式協調服務,在分布式系統中常被用于配置管理、名字服務、分布式鎖及組管理,通常運行在一組節點上實現容錯(當運行在n個節點上時能容忍n/2個節點同時故障)。
如何通過Zookeeper來實現服務發現?Zookeeper使用臨時節點(ephemeral node)來實現服務注冊和基本的健康檢查功能。每當服務實例啟動就會在Zookeeper中注冊一個臨時節點,而當服務實例故障或下線該臨時節點會被Zookeeper自動刪除,如果有其他服務依賴這個服務可以設置監聽該服務實例對應的臨時節點,當臨時節點被刪除時,依賴該服務的其他服務會獲得通知。依賴Zookeeper自身的高可用及臨時節點提供的健康檢查和監聽機制來實現具備容錯能力的服務發現機制。
實際開發過程中建議使用Apache Curator來替代Zookeeper原生客戶端庫,Apache Curator通過封裝Zookeeper原生API,提供更高抽象層次API讓Zookeeper使用起來更加容易和可靠,而且提供專用于實現服務發現的API。

Etcd
Etcd是一個基于Raft共識算法具備線性強一致性(linearizable)的Key-Value存儲系統,可以為每個Key設置TTL(time to live),當TTL過后相應Key會自動過期失效。基于Etcd構建服務發現解決方案將Etcd作為服務注冊中心,服務實例注冊就是在Etcd中構建一個Key-Value記錄,由服務實例自身或代理負責設置并定期更新其關聯Key的TTL,如果服務實例故障其對應Key就會在TTL之后過期失效,相當于將該故障服務實例注銷,通過定時心跳以達到監控健康狀態的效果。而且Etcd提供監聽機制,允許為Key設置監聽器當該Key發生變化時,監聽器能及時獲取通知。Etcd自身的高可用特性,基于TTL提供基本的服務健康檢查,基于監聽機制及時感知服務實例變化,使Etcd成為微服務架構中常用服務發現解決方案。

Consul
Consul是一個成熟的服務發現解決方案。其核心是一個基于Raft共識算法具備線性強一致性的Key-Value存儲系統作為服務注冊中心,并提供代理(Agent)機制一方面用于協調服務注冊,一方面提供服務健康檢查。代理(Agent)會在每個運行服務的節點上啟動,獲取節點地址并將該服務實例注冊到服務注冊中心。架構上Consul包括兩類組件:Server、Agent,服務注冊信息保存在Server上,通過Raft共識算法保證多個Server間數據線性強一致,保證服務注冊中心高可用;將所有Agent作為集群節點,使用Gossip協議進行組關系管理和故障探測,當有Agent加入(啟動)或離開(故障)集群時其他Agent會得到通知,實現服務健康檢查和監視功能。
Gossip協議常用于集群組關系管理和故障檢測,每個節點都通過一個或多個引導節點加入集群,引導節點有集群中所有節點列表,每個節點都從自己所知節點列表中隨機選擇一組節點周期性地發送多播消息,最終集群中所有節點都能知道其他節點。這個過程看起來很神奇,實際上Gossip協議能在幾秒內將消息傳遍有上百節點的集群。Akka、Riak、Cassandra都使用Gossip協議維護集群成員列表和故障探測。

此外Consul和Etcd都非常適合容器環境,因為Docker容器啟動、停止都會發送事件(Event),基于事件通知機制非常便于將服務實例從Consul或Etcd上注冊、注銷。
總結
本文總結了傳統服務發現方案如DNS、mDNS以及微服務架構中常用的解決方案,基于Zookeeper、Etcd、Consul框架方案核心思想是通過一組實例(3個或者5個)提供線性強一致性(Linearizable)分布式高可用Key-Value存儲服務,將Key-Value存儲作為服務注冊中心,當相關Key發生變化時監視器能及時通知客戶端,通知機制配合服務健康檢查當有服務實例啟動或故障時客戶端能及時感知服務拓撲變化以實現智能路由,從實現方式上看它們可以看作是中心化的服務發現方案。
其實對于服務發現來說線性強一致性并不是唯一必須的,最終一致性在數據傳播足夠快的情況下一樣能滿足需求,實踐中Gossip協議即使在大型集群也能快速傳播數據并收斂到最終一致,將服務實例作為Gossip集群節點,使用CRDT(conflict-free replicated data type)存儲服務注冊信息通過Gossip快速傳播實現集群中所有節點狀態最終一致,每個節點都存儲全部服務注冊信息,這樣就不需要單獨的服務注冊中心,這種方式實現的方案叫去中心化方案,有關去中心化服務發現方案留作下次分享。