天天看點

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

1 為什麼需要服務發現 

        簡單來說,服務化的核心就是将傳統的一站式應用根據業務拆分成一個一個的服務,而微服務在這個基礎上要更徹底地去耦合(不再共享DB、KV,去掉重量級ESB),并且強調DevOps和快速演化。這就要求我們必須采用與一站式時代、泛SOA時代不同的技術棧,而Spring Cloud就是其中的佼佼者。

DevOps是英文Development和Operations的合體,他要求開發、測試、運維進行一體化的合作,進行更小、更頻繁、更自動化的應用釋出,以及圍繞應用架構來建構基礎設施的架構。這就要求應用充分的内聚,也友善運維和管理。這個理念與微服務理念不謀而合。

接下來我們從服務化架構演進的角度來看看為什麼Spring Cloud更适應微服務架構。

1.1 從使用nginx說起

最初的服務化解決方案是給提供相同服務提供一個統一的域名,然後服務調用者向這個域名發送HTTP請求,由Nginx負責請求的分發和跳轉。

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

這種架構存在很多問題:

  • Nginx作為中間層,在配置檔案中耦合了服務調用的邏輯,這削弱了微服務的完整性,也使得Nginx在一定程度上變成了一個重量級的ESB。
  • 服務的資訊分散在各個系統,無法統一管理和維護。每一次的服務調用都是一次嘗試,服務消費者并不知道有哪些執行個體在給他們提供服務。這不符合DevOps的理念。
  • 無法直覺的看到服務提供者和服務消費者目前的運作狀況和通信頻率。這也不符合DevOps的理念。
  • 消費者的失敗重發,負載均衡等都沒有統一政策,這加大了開發每個服務的難度,不利于快速演化。

為了解決上面的問題,我們需要一個現成的中心元件對服務進行整合,将每個服務的資訊彙總,包括服務的元件名稱、位址、數量等。服務的調用方在請求某項服務時首先通過中心元件擷取提供這項服務的執行個體的資訊(IP、端口等),再通過預設或自定義的政策選擇該服務的某一提供者直接進行通路。是以,我們引入了Dubbo。

1.2 基于Dubbo實作微服務

Dubbo是阿裡開源的一個SOA服務治了解決方案,文檔豐富,在國内的使用度非常高。

使用Dubbo建構的微服務,已經可以比較好地解決上面提到的問題:

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

DubboArch.png

  • 調用中間層變成了可選元件,消費者可以直接通路服務提供者。
  • 服務資訊被集中到Registry中,形成了服務治理的中心元件。
  • 通過Monitor監控系統,可以直覺地展示服務調用的統計資訊。
  • Consumer可以進行負載均衡、服務降級的選擇。

但是對于微服務架構而言,Dubbo也并不是十全十美的:

  • Registry嚴重依賴第三方元件(zookeeper或者redis),當這些元件出現問題時,服務調用很快就會中斷。
  • DUBBO隻支援RPC調用。使得服務提供方與調用方在代碼上産生了強依賴,服務提供者需要不斷将包含公共代碼的jar包打包出來供消費者使用。一旦打包出現問題,就會導緻服務調用出錯。
  • 最為重要的是,DUBBO現在已經停止維護了,對于技術發展的新需求,需要由開發者自行拓展更新。這對于很多想要采用微服務架構的中小軟體組織,顯然是不太合适的。
目前Github社群上有一個DUBBO的更新版,叫DUBBOX,提供了更高效的RPC序列化方式和REST調用方式。但是該項目也基本停止維護了。

1.3 新的選擇——Spring Cloud

作為新一代的服務架構,Spring Cloud提出的口号是開發“面向雲環境的應用程式”,它為微服務架構提供了更加全面的技術支援。

結合我們一開始提到的微服務的訴求,我們把Spring Cloud與DUBBO進行一番對比:

微服務需要的功能 Dubbo Spring Cloud
服務注冊和發現 Zookeeper Eureka
服務調用方式 RPC RESTful API
斷路器
負載均衡
服務路由和過濾
分布式配置
分布式鎖 計劃開發
叢集選主
分布式消息
Spring Cloud抛棄了Dubbo的RPC通信,采用的是基于HTTP的REST方式。嚴格來說,這兩種方式各有優劣。雖然從一定程度上來說,後者犧牲了服務調用的性能,但也避免了上面提到的原生RPC帶來的問題。而且REST相比RPC更為靈活,服務提供方和調用方的依賴隻依靠一紙契約,不存在代碼級别的強依賴,這在強調快速演化的微服務環境下,顯得更加合适。

很明顯,Spring Cloud的功能比DUBBO更加強大,涵蓋面更廣,而且作為Spring的拳頭項目,它也能夠與Spring Framework、Spring Boot、Spring Data、Spring Batch等其他Spring項目完美融合,這些對于微服務而言是至關重要的。前面提到,微服務背後一個重要的理念就是持續內建、快速傳遞,而在服務内部使用一個統一的技術架構,顯然比把分散的技術組合到一起更有效率。更重要的是,相比于Dubbo,它是一個正在持續維護的、社群更加火熱的開源項目,這就保證使用它建構的系統,可以持續地得到開源力量的支援。

2. Spring Cloud Netflix 元件

Netflix和Spring Cloud是什麼關系呢?Netflix是一家成功實踐微服務架構的網際網路公司,幾年前,Netflix就把它的幾乎整個微服務架構棧開源貢獻給了社群。Spring背後的Pivotal在2015年推出的Spring Cloud開源産品,主要對Netflix開源元件的進一步封裝,友善Spring開發人員建構微服務基礎架構。

對于微服務的治理而言,核心就是服務的注冊和發現。是以選擇哪個元件,很大程度上要看它對于服務注冊與發現的解決方案。在這個領域,開源架構很多,最常見的是Zookeeper,但這并不是一個最佳選擇。

在分布式系統領域有個著名的CAP定理:C——資料一緻性,A——服務可用性,P——服務對網絡分區故障的容錯性。這三個特性在任何分布式系統中不能同時滿足,最多同時滿足兩個。

Zookeeper是著名Hadoop的一個子項目,很多場景下Zookeeper也作為Service發現服務解決方案。Zookeeper保證的是CP,即任何時刻對

Zookeeper的通路請求能得到一緻的資料結果,同時系統對網絡分割具備容錯性,但是它不能保證每次服務請求的可用性。從實際情況來分析,在使用Zookeeper擷取服務清單時,如果zookeeper正在選主,或者Zookeeper叢集中半數以上機器不可用,那麼将就無法獲得資料了。是以說,Zookeeper不能保證服務可用性。

誠然,對于大多數分布式環境,尤其是涉及到資料存儲的場景,資料一緻性應該是首先被保證的,這也是zookeeper設計成CP的原因。但是對于服務發現場景來說,情況就不太一樣了:針對同一個服務,即使注冊中心的不同節點儲存的服務提供者資訊不盡相同,也并不會造成災難性的後果。因為對于服務消費者來說,能消費才是最重要的——拿到可能不正确的服務執行個體資訊後嘗試消費一下,也好過因為無法擷取執行個體資訊而不去消費。是以,對于服務發現而言,可用性比資料一緻性更加重要——AP勝過CP。而Spring Cloud Netflix在設計Eureka時遵守的就是AP原則。

Eureka本身是Netflix開源的一款提供服務注冊和發現的産品,并且提供了相應的Java封裝。在它的實作中,節點之間是互相平等的,部分注冊中心的節點挂掉也不會對叢集造成影響,即使叢集隻剩一個節點存活,也可以正常提供發現服務。哪怕是所有的服務注冊節點都挂了,Eureka Clients上也會緩存服務調用的資訊。這就保證了我們微服務之間的互相調用是足夠健壯的。

除此之外,Spring Cloud Netflix背後強大的開源力量,也促使我們選擇了Spring Cloud Netflix:

  • 前文提到過,Spring Cloud的社群十分活躍,其在業界的應用也十分廣泛(尤其是國外),而且整個架構也經受住了Netflix嚴酷生産環境的考驗。
  • 除了服務注冊和發現,Spring Cloud Netflix的其他功能也十分強大,包括Ribbon,hystrix,Feign,Zuul等元件,結合到一起,讓服務的調用、路由也變得異常容易。
  • Spring Cloud Netflix作為Spring的重量級整合架構,使用它也意味着我們能從Spring擷取到巨大的便利。Spring Cloud的其他子項目,比如Spring Cloud Stream、Spring Cloud Config等等,都為微服務的各種需求提供了一站式的解決方案。

3. Spring Cloud 服務發現

Spring Cloud Netflix的核心是用于服務注冊與發現的Eureka,接下來我們将以Eureka為線索,介紹Eureka、Ribbon、Hystrix、Feign這些Spring Cloud Netflix主要元件。

3.1 服務注冊與發現——Eureka

Eureka這個詞來源于古希臘語,意為“我找到了!我發現了!”,據傳,阿基米德在洗澡時發現浮力原理,高興得來不及穿上褲子,跑到街上大喊:“Eureka(我找到了)!"。

Eureka由多個instance(服務執行個體)組成,這些服務執行個體可以分為兩種:Eureka Server和Eureka Client。為了便于了解,我們将Eureka client再分為Service Provider和Service Consumer。如下圖所示:

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

EurekaRole.png

  • Eureka Server:服務的注冊中心,負責維護注冊的服務清單。
  • Service Provider:服務提供方,作為一個Eureka Client,向Eureka Server做服務注冊、續約和下線等操作,注冊的主要資料包括服務名、機器ip、端口号、域名等等。
  • Service Consumer:服務消費方,作為一個Eureka Client,向Eureka Server擷取Service Provider的注冊資訊,并通過遠端調用與Service Provider進行通信。
Service Provider和Service Consumer不是嚴格的概念,Service Consumer也可以随時向Eureka Server注冊,來讓自己變成一個Service Provider。
Spring Cloud針對服務注冊與發現,進行了一層抽象,并提供了三種實作:Eureka、Consul、Zookeeper。目前支援得最好的就是Eureka,其次是Consul,最後是Zookeeper。

3.1.1 Eureka Server

Eureka Server作為一個獨立的部署單元,以REST API的形式為服務執行個體提供了注冊、管理和查詢等操作。同時,Eureka Server也為我們提供了可視化的監控頁面,可以直覺地看到各個Eureka Server目前的運作狀态和所有已注冊服務的情況。

3.1.1.1 Eureka Server的高可用叢集

Eureka Server可以運作多個執行個體來建構叢集,解決單點問題,但不同于ZooKeeper的選舉leader的過程,Eureka Server采用的是Peer to Peer對等通信。這是一種去中心化的架構,無master/slave區分,每一個Peer都是對等的。在這種架構中,節點通過彼此互相注冊來提高可用性,每個節點需要添加一個或多個有效的serviceUrl指向其他節點。每個節點都可被視為其他節點的副本。

如果某台Eureka Server當機,Eureka Client的請求會自動切換到新的Eureka Server節點,當當機的伺服器重新恢複後,Eureka會再次将其納入到伺服器叢集管理之中。當節點開始接受用戶端請求時,所有的操作都會進行replicateToPeer(節點間複制)操作,将請求複制到其他Eureka Server目前所知的所有節點中。

一個新的Eureka Server節點啟動後,會首先嘗試從鄰近節點擷取所有執行個體系統資料庫資訊,完成初始化。Eureka Server通過getEurekaServiceUrls()方法擷取所有的節點,并且會通過心跳續約的方式定期更新。預設配置下,如果Eureka Server在一定時間内沒有接收到某個服務執行個體的心跳,Eureka Server将會登出該執行個體(預設為90秒,通過eureka.instance.lease-expiration-duration-in-seconds配置)。當Eureka Server節點在短時間内丢失過多的心跳時(比如發生了網絡分區故障),那麼這個節點就會進入自我保護模式。下圖為Eureka官網的架構圖

什麼是自我保護模式?預設配置下,如果Eureka Server每分鐘收到心跳續約的數量低于一個門檻值(instance的數量*(60/每個instance的心跳間隔秒數)*自我保護系數),就會觸發自我保護。在自我保護模式中,Eureka Server會保護服務系統資料庫中的資訊,不再登出任何服務執行個體。當它收到的心跳數重新恢複到門檻值以上時,該Eureka Server節點就會自動退出自我保護模式。它的設計哲學前面提到過,那就是甯可保留錯誤的服務注冊資訊,也不盲目登出任何可能健康的服務執行個體。該模式可以通過eureka.server.enable-self-preservation = false來禁用,同時eureka.instance.lease-renewal-interval-in-seconds可以用來更改心跳間隔,eureka.server.renewal-percent-threshold可以用來修改自我保護系數(預設0.85)。
SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

EurekaArchitecture.png

3.1.1.2 Eureka Server的Region、Zone

Eureka的官方文檔對Regin、Zone幾乎沒有提及,由于概念抽象,新手很難了解。是以,我們先來了解一下Region、Zone、Eureka叢集三者的關系,如下圖所示:

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

RegionZone.png

region和zone(或者Availability Zone)均是AWS的概念。在非AWS環境下,我們可以先簡單地将region了解為Eureka叢集,zone了解成機房。上圖就可以了解為一個Eureka叢集被部署在了zone1機房和zone2機房中。

3.1.2 Service Provider

3.1.2.1 服務注冊

Service Provider本質上是一個Eureka Client。它啟動時,會調用服務注冊方法,向Eureka Server注冊自己的資訊。Eureka Server會維護一個已注冊服務的清單,這個清單為一個嵌套的hash map:

  • 第一層,application name和對應的服務執行個體。
  • 第二層,服務執行個體及其對應的注冊資訊,包括IP,端口号等。

當執行個體狀态發生變化時(如自身檢測認為Down的時候),也會向Eureka Server更新自己的服務狀态,同時用replicateToPeers()向其它Eureka Server節點做狀态同步。

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

![Uploading EurekaServerEvict_655129.png . . .]

3.1.2.2 續約與剔除

前面提到過,服務執行個體啟動後,會周期性地向Eureka Server發送心跳以續約自己的資訊,避免自己的注冊資訊被剔除。續約的方式與服務注冊基本一緻:首先更新自身狀态,再同步到其它Peer。

如果Eureka Server在一段時間内沒有接收到某個微服務節點的心跳,Eureka Server将會登出該微服務節點(自我保護模式除外)。

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

EurekaServerRegister.png

3.1.3 Service Consumer

Service Consumer本質上也是一個Eureka Client(它也會向Eureka Server注冊,隻是這個注冊資訊無關緊要罷了)。它啟動後,會從Eureka Server上擷取所有執行個體的注冊資訊,包括IP位址、端口等,并緩存到本地。這些資訊預設每30秒更新一次。前文提到過,如果與Eureka Server通信中斷,Service Consumer仍然可以通過本地緩存與Service Provider通信。

實際開發Eureka的過程中,有時會遇見Service Consumer擷取到Server Provider的資訊有延遲,在Eureka Wiki中有這麼一段話:

All operations from Eureka client may take some time to reflect in the Eureka servers and subsequently in other Eureka clients. This is because of the caching of the payload on the eureka server which is refreshed periodically to reflect new information. Eureka clients also fetch deltas periodically. Hence, it may take up to 2 mins for changes to propagate to all Eureka clients.

最後一句話提到,服務端的更改可能需要2分鐘才能傳播到所有用戶端,至于原因并沒有介紹。這是因為Eureka有三處緩存和一處延遲造成的。

  • Eureka Server對注冊清單進行緩存,預設時間為30s。
  • Eureka Client對擷取到的注冊資訊進行緩存,預設時間為30s。
  • Ribbon會從上面提到的Eureka Client擷取服務清單,将負載均衡後的結果緩存30s。
  • 如果不是在Spring Cloud環境下使用這些元件(Eureka, Ribbon),服務啟動後并不會馬上向Eureka注冊,而是需要等到第一次發送心跳請求時才會注冊。心跳請求的發送間隔預設是30s。Spring Cloud對此做了修改,服務啟動後會馬上注冊。

基于Service Consumer擷取到的服務執行個體資訊,我們就可以進行服務調用了。而Spring Cloud也為Service Consumer提供了豐富的服務調用工具:

  • Ribbon,實作用戶端的負載均衡。
  • Hystrix,斷路器。
  • Feign,RESTful Web Service用戶端,整合了Ribbon和Hystrix。

接下來我們就一一介紹。

3.2 服務調用端負載均衡——Ribbon

Ribbon是Netflix釋出的開源項目,主要功能是為REST用戶端實作負載均衡。它主要包括六個元件:

  • ServerList,負載均衡使用的伺服器清單。這個清單會緩存在負載均衡器中,并定期更新。當Ribbon與Eureka結合使用時,ServerList的實作類就是DiscoveryEnabledNIWSServerList,它會儲存Eureka Server中注冊的服務執行個體表。
  • ServerListFilter,伺服器清單過濾器。這是一個接口,主要用于對Service Consumer擷取到的伺服器清單進行預過濾,過濾的結果也是ServerList。Ribbon提供了多種過濾器的實作。
  • IPing,探測服務執行個體是否存活的政策。
  • IRule,負載均衡政策,其實作類表述的政策包括:輪詢、随機、根據響應時間權重等。
我們也可以自己定義負載均衡政策,比如我們就利用自己實作的政策,實作了服務的版本控制和直連配置。實作好之後,将實作類重新注入到Ribbon中即可。
  • ILoadBalancer,負載均衡器。這也是一個接口,Ribbon為其提供了多個實作,比如ZoneAwareLoadBalancer。而上層代碼通過調用其API進行服務調用的負載均衡選擇。一般ILoadBalancer的實作類中會引用一個IRule。
  • RestClient,服務調用器。顧名思義,這就是負載均衡後,Ribbon向Service Provider發起REST請求的工具。

Ribbon工作時會做四件事情:

  1. 優先選擇在同一個Zone且負載較少的Eureka Server;
  2. 定期從Eureka更新并過濾服務執行個體清單;
  3. 根據使用者指定的政策,在從Server取到的服務注冊清單中選擇一個執行個體的位址;
  4. 通過RestClient進行服務調用。

3.3 服務調用端熔斷——Hystrix

Netflix建立了一個名為Hystrix的庫,實作了斷路器的模式。“斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似熔斷保險絲),向調用方傳回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或者抛出調用方無法處理的異常,這樣就保證了服務調用方的線程不會被長時間、不必要地占用,進而避免了故障在分布式系統中的蔓延,乃至雪崩。

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

Hystrix.png

當然,在請求失敗頻率較低的情況下,Hystrix還是會直接把故障傳回給用戶端。隻有當失敗次數達到門檻值(預設在20秒内失敗5次)時,斷路器打開并且不進行後續通信,而是直接傳回備選響應。當然,Hystrix的備選響應也是可以由開發者定制的。

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

HystrixFallback.png

除了隔離依賴服務的調用以外,Hystrix還提供了準實時的調用監控(Hystrix Dashboard),Hystrix會持續地記錄所有通過Hystrix發起的請求的執行資訊,并以統計報表和圖形的形式展示給使用者,包括每秒執行多少請求多少成功,多少失敗等。Netflix通過hystrix-metrics-event-stream項目實作了對以上名額的監控。Spring Cloud也提供了Hystrix Dashboard的整合,對監控内容轉化成可視化界面,Hystrix Dashboard Wiki上詳細說明了圖上每個名額的含義。

SpringCloud微服務架構中的服務注冊與發現——Eureka1 為什麼需要服務發現 2. Spring Cloud Netflix 元件3. Spring Cloud 服務發現4. 參考文獻

3.4 服務調用端代碼抽象和封裝——Feign

Feign是一個聲明式的Web Service用戶端,它的目的就是讓Web Service調用更加簡單。它整合了Ribbon和Hystrix,進而讓我們不再需要顯式地使用這兩個元件。Feign還提供了HTTP請求的模闆,通過編寫簡單的接口和插入注解,我們就可以定義好HTTP請求的參數、格式、位址等資訊。接下來,Feign會完全代理HTTP的請求,我們隻需要像調用方法一樣調用它就可以完成服務請求。

Feign具有如下特性:

  • 可插拔的注解支援,包括Feign注解和JAX-RS注解
  • 支援可插拔的HTTP編碼器和解碼器
  • 支援Hystrix和它的Fallback
  • 支援Ribbon的負載均衡
  • 支援HTTP請求和響應的壓縮

以下是一個Feign的簡單示例:

@SpringBootApplication
@EnableDiscoveryClient //啟用Feign
@EnableFeignClients
public class Application
{
    public static void main(String[] args)
    {
        SpringApplication.run(Application.class, args);
    }
}

@FeignClient(name = "elements", fallback = ElementsFallback.class) //指定feign調用的服務和Hystrix Fallback(name即eureka的application name)
public interface Elements
{
    @RequestMapping(value = "/index")
    String index();
}

//Hystrix Fallback  
@Component
public class ElementsFallback implements Elements
{
    @Override
    public String index()
    {
        return "**************";
    }
}

//測試類
@Component  
public class TestController {
    @Autowired
    Elements elements;

    @RequestMapping(value = "/testEureka", method = RequestMethod.GET)
    public String testeureka()
    {
        return elements.index();
    }
}
           

4. 參考文獻

http://cloud.spring.io/spring-cloud-static/Brixton.SR7

https://github.com/Netflix/eureka/wiki

http://itmuch.com/spring-cloud-sum-eureka

http://nobodyiam.com/2016/06/25/dive-into-eureka

http://blog.csdn.net/neosmith/article/details/53131023

繼續閱讀