Spring cloud ----- 服務治理


       本文作為系列的第一篇正文,從Spring Cloud中的核心項目Spring Cloud Netflix入手,闡述了Spring Cloud Netflix的優勢,介紹了Spring Cloud Netflix進行服務治理的技術原理。

1. Spring Cloud Netflix的優勢

       對於微服務的治理而言,核心就是服務的注冊和發現。所以選擇哪個組件,很大程度上要看它對於服務注冊與發現的解決方案。在這個領域,開源架構很多,最常見的是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等等,都為微服務的各種需求提供了一站式的解決方案。

2. Spring Cloud Netflix主要組件介紹

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

2.1 服務注冊與發現——Eureka

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

  • 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。

2.1.1 Eureka Server

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

2.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的心跳間隔秒數)*自我保護系數),並且持續15分鍾,就會觸發自我保護。在自我保護模式中,Eureka Server會保護服務注冊表中的信息,不再注銷任何服務實例。當它收到的心跳數重新恢復到閾值以上時,該Eureka Server節點就會自動退出自我保護模式。它的設計哲學前面提到過,那就是寧可保留錯誤的服務注冊信息,也不盲目注銷任何可能健康的服務實例。該模式可以通過eureka.server.enable-self-preservation = false來禁用,同時eureka.instance.lease-renewal-interval-in-seconds可以用來更改心跳間隔,eureka.server.renewal-percent-threshold可以用來修改自我保護系數(默認0.85)。

 

2.1.1.2 Eureka Server的Region、Zone

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

 

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

2.1.2 Service Provider

2.1.2.1 服務注冊

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

  • 第一層,application name和對應的服務實例。
  • 第二層,服務實例及其對應的注冊信息,包括IP,端口號等。

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

2.1.2.2 續約與剔除

       前面提到過,服務實例啟動后,會周期性地向Eureka Server發送心跳以續約自己的信息,避免自己的注冊信息被剔除。續約的方式與服務注冊基本一致:首先更新自身狀態,再同步到其它Peer。

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

 

2.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。

接下來我們就一一介紹。

2.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進行服務調用。

 

2.3 服務調用端熔斷——Hystrix

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

 

       當然,在請求失敗頻率較低的情況下,Hystrix還是會直接把故障返回給客戶端。只有當失敗次數達到閾值(默認在20秒內失敗5次)時,斷路器打開並且不進行后續通信,而是直接返回備選響應。當然,Hystrix的備選響應也是可以由開發者定制的。

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

 

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

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

Feign具有如下特性:

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

 

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

 1 @SpringBootApplication
 2 @EnableDiscoveryClient //啟用Feign
 3 @EnableFeignClients
 4 public class Application
 5 {
 6     public static void main(String[] args)
 7     {
 8         SpringApplication.run(Application.class, args);
 9     }
10 }
11 
12 @FeignClient(name = "elements", fallback = ElementsFallback.class) //指定feign調用的服務和Hystrix Fallback(name即eureka的application name)
13 public interface Elements
14 {
15     @RequestMapping(value = "/index")
16     String index();
17 }
18 
19 //Hystrix Fallback    
20 @Component
21 public class ElementsFallback implements Elements
22 {
23     @Override
24     public String index()
25     {
26         return "**************";
27     }
28 }
29 
30 //測試類
31 @Component    
32 public class TestController {
33     @Autowired
34     Elements elements;
35 
36     @RequestMapping(value = "/testEureka", method = RequestMethod.GET)
37     public String testeureka()
38     {
39          return elements.index();
40     }
41 }

 

 

 原文鏈接:http://tech.lede.com/2017/03/15/rd/server/SpringCloud1/

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM