設想一下,咱們正在寫代碼使用了提供REST API或者Thrift API的服務,爲了完成一次服務請求,代碼須要知道服務實例的網絡位置(IP地址和端口)。傳統應用都運行在物理硬件上,服務實例的網絡位置都是相對固定的。例如,代碼能夠從一個常常變動的配置文件中讀取網絡位置。nginx
而對於一個現代的,基於雲微服務的應用來講,這倒是一個很麻煩的問題。其架構如圖所示:算法
服務實例的網絡位置都是動態分配的,並且由於擴展、失效和升級等需求,服務實例會常常動態改變,所以,客戶端代碼須要使用一種更加複雜的服務發現機制。數據庫
目前有兩大類服務發現模式:客戶端發現和服務端發現。編程
咱們先來來討論一下客戶端發現。緩存
當使用客戶端發現模式時,客戶端負責決定相應服務實例的網絡位置,而且對請求實現負載均衡。客戶端從一個服務註冊服務中查詢,其中是全部可用服務實例的庫。客戶端使用負載均衡算法從多個服務實例中選擇出一個,而後發出請求。服務器
下圖顯示的是這種模式的架構圖:網絡
服務實例的網絡位置是在啓動時註冊到服務註冊表中,而且在服務終止時從註冊表中刪除。服務實例註冊信息通常是使用心跳機制來按期刷新的。架構
Netflix OSS提供了一種很是棒的客戶端發現模式。Netflix Eureka是一個服務註冊表,爲服務實例註冊管理和查詢可用實例提供了REST API接口。Netflix Ribbon是一種IPC客戶端,與Eureka合同工做實現對請求的負載均衡。咱們會在後面詳細討論Eureka。app
客戶端發現模式也是優缺點分明。這種模式相對比較直接,並且除了服務註冊表,沒有其它改變的因素。除此以外,由於客戶端知道可用服務註冊表信息,所以客戶端能夠經過使用哈希一致性(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服務。
另一些服務註冊表例子包括:
另外,前面強調過,某些系統,例如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。