為什麼要使用服務發現?
設想一下,我們正在寫程式碼使用了提供REST API或者Thrift API的服務,為了完成一次服務請求,程式碼需要知道服務例項的網路位置(IP地址和埠)。傳統應用都執行在物理硬體上,服務例項的網路位置都是相對固定的。例如,程式碼可以從一個經常變更的配置檔案中讀取網路位置。
而對於一個現代的,基於雲微服務的應用來說,這卻是一個很麻煩的問題。其架構如圖所示:
服務例項的網路位置都是動態分配的,而且因為擴充套件、失效和升級等需求,服務例項會經常動態改變,因此,客戶端程式碼需要使用一種更加複雜的服務發現機制。
目前有兩大類服務發現模式:客戶端發現和服務端發現。
我們先來來討論一下客戶端發現。
客戶端發現模式
當使用客戶端發現模式時,客戶端負責決定相應服務例項的網路位置,並且對請求實現負載均衡。客戶端從一個服務註冊服務中查詢,其中是所有可用服務例項的庫。客戶端使用負載均衡演算法從多個服務例項中選擇出一個,然後發出請求。
下圖顯示的是這種模式的架構圖:
服務例項的網路位置是在啟動時註冊到服務登錄檔中,並且在服務終止時從登錄檔中刪除。服務例項註冊資訊一般是使用心跳機制來定期重新整理的。
Netflix OSS提供了一種非常棒的客戶端發現模式。Netflix Eureka是一個服務登錄檔,為服務例項註冊管理和查詢可用例項提供了REST API介面。Netflix Ribbon是一種IPC客戶端,與Eureka合同工作實現對請求的負載均衡。我們會在後面詳細討論Eureka。
客戶端發現模式也是優缺點分明。這種模式相對比較直接,而且除了服務登錄檔,沒有其它改變的因素。除此之外,因為客戶端知道可用服務登錄檔資訊,因此客戶端可以通過使用雜湊一致性(hashing consistently)變得更加聰明,更加有效的負載均衡。
而這種模式一個最大的缺點是需要針對不同的程式語言註冊不同的服務,在客戶端需要為每種語言開發不同的服務發現邏輯。
我們分析過客戶端發現後,再看看服務端發現。
服務端發現模式
另外一種服務發現的模式是服務端發現模式(server-side discovery pattern),下圖展現了這種模式的架構圖:
客戶端通過負載均衡器向某個服務提出請求,負載均衡器向服務登錄檔發出請求,將每個請求轉發往可用的服務例項。跟客戶端發現一樣,服務例項在服務登錄檔中註冊或者登出。
AWS Elastic Load Balancer(ELB)是一種服務端發現路由的例子,ELB一般用於均衡從網路來的訪問流量,也可以使用ELB來均衡VPC內部的流量。客戶端使用DNS,通過ELB發出請求(HTTP或者TCP)。ELB負載均衡器負責在註冊的EC2例項或者ECS容器之間均衡負載,並不存在一個分離的服務登錄檔,而EC2例項和ECS例項也向ELB註冊。
HTTP服務和類似NGINX和NGINX Plus的負載均衡器都可以作為服務端發現均衡器。例如,這篇博文就描述如何使用Consul Template來動態配置NGINX反向代理。Consul Template是週期性從存放在Consul Template登錄檔中配置資料重建配置檔案的工具。當檔案發生變化時,會執行一個命令。在如上部落格中,Consul Template產生了一個nginx.conf檔案,用於配置反向代理,然後執行一個命令,告訴NGINX重新調入配置檔案。更復雜的例子可以用HTTP API或者DNS動態重新配置NGINX Plus。
某些部署環境,例如Kubernetes和Marathon在叢集每個節點上執行一個代理,此代理作為服務端發現負載均衡器。為了向服務發出請求,客戶端使用主機IP地址和分配的埠通過代理將請求路由出去。代理將次請求透明的轉發到叢集中可用的服務例項。
服務端發現模式也有優缺點。最大的優點是客戶端無需關注發現的細節,客戶端只需要簡單的向負載均衡器傳送請求,實際上減少了程式語言框架需要完成的發現邏輯。而且,如上說所,某些部署環境免費提供以上功能。
這種模式也有缺陷,除非部署環境提供負載均衡器,否則負載均衡器是另外一個需要配置管理的高可用系統功能。
服務登錄檔
服務登錄檔是服務發現很重要的部分,它是包含服務例項網路地址的資料庫。服務登錄檔需要高可用而且隨時更新。客戶端可以快取從服務登錄檔獲得的網路地址。然而,這些資訊最終會變得過時,客戶端也無法發現服務例項。因此,服務登錄檔由若干使用複製協議保持同步的伺服器構成。
如前所述,Netflix Eureka是一個服務登錄檔很好地例子,提供了REST API註冊和請求服務例項。 服務例項使用POST請求註冊網路地址,每30秒必須使用PUT方法更新登錄檔,使用HTTP DELETE請求或者例項超時來登出。可以想見,客戶端可以使用HTTP GET請求接受註冊服務例項資訊。
Netflix通過在每個AWS EC2域執行一個或者多個Eureka服務實現高可用性,每個Eureka伺服器都執行在擁有彈性IP地址的EC2例項上。DNS TEXT記錄用於儲存Eureka叢集配置,其中存放從可用域到一系列Eureka伺服器網路地址的列表。當Eureka服務啟動時,向DNS請求接受Eureka叢集配置,確認同伴位置,給自己分配一個未被使用的彈性IP地址。
Eureka客戶端—服務和服務客戶端—向DNS請求發現Eureka服務的網路地址,客戶端首選使用同一域內的服務。然而,如果沒有可用服務,客戶端會使用另外一個可用域的Eureka服務。
另外一些服務登錄檔例子包括:
- etcd – 是一個高可用,分散式的,一致性的,鍵值表,用於共享配置和服務發現。兩個著名案例包括Kubernetes和Cloud Foundry。
- consul – 是一個用於發現和配置的服務。提供了一個API允許客戶端註冊和發現服務。Consul可以用於健康檢查來判斷服務可用性。
- Apache ZooKeeper – 是一個廣泛使用,為分散式應用提供高效能整合的服務。Apache ZooKeeper最初是Hadoop的子專案,現在已經變成頂級專案。
另外,前面強調過,某些系統,例如Kubernetes、Marathon和AWS並沒有獨立的服務登錄檔,對他們來說,服務登錄檔只是一個內建的功能。
現在我們來看看服務登錄檔的概念,看看服務例項是如何在登錄檔中註冊的。
服務註冊選項
如前所述,服務例項必須向登錄檔中註冊和登出,如何註冊和登出也有一些不同的方式。一種方式是服務例項自己註冊,也叫自注冊模式(self-registration pattern);另外一種方式是為其它系統提供服務例項管理的,也叫第三方註冊模式(third party registration pattern)。我們來看看自注冊模式。
自注冊方式
當使用自注冊模式時,服務例項負責在服務登錄檔中註冊和登出。另外,如果需要的話,一個服務例項也要傳送心跳來保證註冊資訊不會過時。下圖描述了這種架構:
一個很好地例子是 Netflix OSS Eureka client。Eureka客戶端負責處理服務例項的註冊和登出。Spring Cloud project,實現了多種模式,包括服務發現,使得向Eureka服務例項自動註冊時更容易。可以用@EnableEurekaClient註釋Java配置類。
自注冊模式也有優缺點。一個優點是,相對簡單,不需要其他系統功能。而一個主要缺點則是,把服務例項跟服務登錄檔聯絡起來。必須在每種程式語言和框架內部實現註冊程式碼。
另外一個方法,不需要連線服務和登錄檔,則是第三方註冊模式。
第三方註冊模式
當使用第三方註冊模式時,服務例項並不負責向服務登錄檔註冊,而是由另外一個系統模組,叫做服務管理器,負責註冊。服務管理器通過查詢部署環境或訂閱事件來跟蹤執行服務的改變。當管理器發現一個新可用服務,會向登錄檔註冊此服務。服務管理器也負責登出終止的服務例項。下圖是這種模式的架構圖。
一個服務管理器的例子是開源專案Registrator,負責自動註冊和登出被部署為Docker容器的服務例項。Reistrator支援多種服務管理器,包括etcd和Consul。
另外一個服務管理器例子是NetflixOSS Prana,主要面向非JVM語言開發的服務,也稱為附帶應用(sidecar application),Prana使用Netflix Eureka註冊和登出服務例項。
服務管理器是部署環境內建的模組。有自動擴充組建立的EC2例項可以自向ELB自動註冊,Kubernetes服務自動註冊並且對發現服務可用。
第三方註冊模式也是優缺點都有。主要的優點是服務跟服務登錄檔是分離的,不需要為每種程式語言和架構完成服務註冊邏輯,替代的,服務例項是通過一個集中化管理的服務進行管理的。
一個缺點是,除非這種服務被內建於部署環境中,否則也需要配置管理一個高可用的系統。
總結
在一個微服務應用中,服務例項執行環境是動態變化的。例項網路地址也是動態變化的,因此,客戶端為了訪問服務必須使用服務發現機制。
服務發現關鍵部分是服務登錄檔,也就是可用服務例項的資料庫。服務登錄檔提供一種註冊管理API和請求API。服務例項使用註冊管理API來實現註冊和登出。
請求API用於發現可用服務例項,相對應的,有兩種主要服務發現模式:客戶端發現和服務端發現。
在使用客戶端發現的系統中,客戶端向服務登錄檔發起請求,選擇可用例項,然後發出服務請求
而在使用服務端發現的系統中,客戶端通過路由轉發請求,路由器向服務登錄檔發出請求,轉發此請求到某個可用例項。
服務例項註冊和登出主要有兩類方式。一種是服務例項自動註冊到服務登錄檔中,也就是自注冊模式;另外一種則是某個系統模組負責處理註冊和登出,也就是第三方註冊模式。
在某些部署環境中,需要配置自己的服務發現架構,例如:Netflix Eureka、etcd或者Apache ZooKeeper。而在另外一些部署環境中,則自帶了這種功能,例如Kubernetes和Marathon 負責處理服務例項的註冊和登出。他們也在每個叢集節點上執行代理,來實現服務端發現路由器的功能。
HTTP反向代理和負載據衡器(例如NGINX)可以用於服務發現負載均衡器。服務登錄檔可以將路由資訊推送到NGINX,啟用一個實時配置更新;例如,可以使用 Consul Template。NGINX Plus 支援額外的動態重新配置機制,可以使用DNS,將服務例項資訊從登錄檔中拉下來,並且提供遠端配置的API。