天天看點

Spring Cloud 學習筆記

一、Eureka注冊中心

在Eureka的服務治理體系中, 主要分為服務端與用戶端兩個不同的角色, 服務端為服務注冊中心, 而用戶端為各個提供接口的微服務應用。

對于服務注冊中心、 服務提供者、 服務消費者這三個主要元素來說, 後兩者(也就是 Eureka 用戶端)在整個運作機制中是大部分通信行為的主動發起者, 而注冊中心主要是處理請求的接收者。

在Eureka的服務治理體系中, 主要分為服務端與用戶端兩個不同的角色, 服務端為服務注冊中心, 而用戶端為各個提供接口的微服務應用。 當我們建構了高可用的注冊中心之後, 該叢集中所有的微服務應用和後續将要介紹的一些基礎類應用(如配置中心、 API網關等)都可以視作該體系下的一個微服務(Eureka用戶端)。 服務注冊中心也一樣, 隻是高可用環境下的服務注冊中心除了作為用戶端之外, 還為叢集中的其他用戶端提供了服務注冊的特殊功能。 是以,Eureka用戶端的配置對象存在于所有Eureka服務治理體系下的應用執行個體中。

Eureka用戶端的配置主要分為以下兩個方面。• 服務注冊相關的配置資訊, 包括服務注冊中心的位址、 服務擷取的間隔時間、 可用區域等。• 服務執行個體相關的配置資訊, 包括服務執行個體的名稱、IP位址、 端口号、 健康檢查路徑等。

中繼資料

它是Eureka 用戶端在向服務注冊 中心發送注冊請求時, 用來描述自身服務資訊的對象, 其中包含了一些标準化的中繼資料, 比如 服務名稱、 執行個體名稱、 執行個體IP、 執行個體端口等用于服務治理的重要資訊;以及一些用 千負載均衡政策或是其他特殊用途的自定義 中繼資料資訊。

執行個體名配置

我們可以直接通過設定server.part=0 或者使用随機數 server.port=${randorn.int[10000,19999]} 來讓Tomcat 啟動的時候采用随機端口。 但是這個時候我們會發現注冊到 Eureka Server 的執行個體名都是相同的, 這會使得隻有一個服務執行個體能夠正常提供服務。 對于這個問題, 我們就可以通過設定執行個體名規則來輕松解決:eureka.instance.instanceid=${spring.applica七ion.name}:${random.int}}通過上面的配置, 利用應用名加随機數的方式來區分不同的執行個體, 進而實作在同一主機上, 不指定端口就能輕松啟動多個執行個體的效果。

通信協定

    預設情況下, Eureka 使用Jersey和XStream配合JSON作為Server與Client之間的通信協定。 你也可以選擇實作自己的協定來代替。

Jersey是JAX-RS的參考實作, 它包含三個主要部分。. 核心伺服器C Core Server): 通過提供 JSR311中标準化的注釋和API标準化,你可以用直覺的方式開發RESTfulWeb服務。. 核心用戶端C Core Client): Jersey用戶端API幫助你與REST服務 輕松通信。. 內建C Integration): Jersey還提供可以輕松內建 Spring、Guice、A pache A bdera的庫。• XStream是 用來将對象 序列化成XML CJSON)或 反序列化為對象的一 個Java類庫。XStream在運作時使用Java反射機制對要進行序列化的對象樹的結構進行探索, 并不需要對對象做出修改。XStream可以序列化内部 字段, 包括private和final字段,并且支援非公開類以及内部類。 預設情況下,XStream 不需要配置映射關系, 對象和字段将映射為同名XML元素。 但是當對象和字段名與XML中的元素名不同時,XStream支援指定别名。XStream支援以方法調用的方式, 或是Java标注的方式 指定别名。XStream 在進行資料類型轉換時,使用系統預設的類型轉換器。 同時, 也支援使用者自定義的類型轉換器。JAX求S 是将在Java EE 6中引入的一種新技術。JAX-RS即Java API forRESTful Web Services, 是一個Java程式設計語言的應用程式接口,支援按照表述性狀态轉移(REST)架構鳳格建立Web服務。JAX—RS使用了Java SE 5引入的Java标注未簡化Web服務的用戶端和服務瑞的開發和部署。包括:• @Path, 标注資原類或者方法的相對路徑 。• @GET、 @PUT、 @POST、 @DELETE, 标注方法是HTTP請求的類型。• @Produces, 标注傳回的MIME媒體類型。• @Consun1es , 标注可接受請求的MIME媒體類型。• @PathParam、 @QueryPararn 、 @HeaderParam 、 @CookieParam 、 @MatrixParam 、@FormParam, 标注方法的參數未自HTTP請求的不同位置,例如,@PathParam來自URL 的路徑, @QueryParam來自URL的查詢參數, @HeaderParam未自HTTP請求的頭資訊, @CookieParam未自HTTP請求的Cookie。

跨平台支援

    除了 Eureka 的Java 用戶端之外, 還有很多其他 語言平台對其的支待, 比如eureka-js-client、python-eureka等

DiscoveryClient 類

      這個類用于幫助與Eureka Server互相協作。Eureka Client負責下面的任務:-向Eureka Server注冊服務執行個體-向Eureka Server服務租約- 當服務關閉期間, 向Eureka Server取消租約-查詢Eureka Server中的服務執行個體清單Eureka Client還需要配置一個Eureka Server的 URL清單。

      注冊操作也是通過REST請求的方式進行的。同時, 我們能看到發起注冊請求的時候, 傳入了 一 個 com.nettfix.appinfo.Instanceinfo 對象, 該對象就是注冊時用戶端給服務端的服務的中繼資料。

為了定期更新用戶端的服務清單, 以保證用戶端能夠通路确實健康的服務執行個體, “服務擷取 ” 的請求不會隻限于服務啟動, 而是一個定時執行的任務,它預設為 30秒。“服務續約” 的實作較為簡單, 直接以REST請求的方式進行續約;而“服務擷取” 則複雜一些, 會根據是否是第一次擷取發起不同的 REST 請求和相應的處理。

二、Ribbon用戶端負載均衡

     Spring Cloud Ribbon 是一個基于 HTTP 和 TCP 的用戶端負載均衡工具,它基于 NetflixRibbon 實作。 通過 Spring Cloud 的封裝, 可以讓我們輕松地将面向服務的 REST 模闆請求自動轉換成用戶端負載均衡的服務調用。 Spring Cloud Rbbon 雖然隻是一個工具類架構,它不像服務注冊中心、 配置中心、 API 網關那樣需要獨立部署, 但是它幾乎存在于每一個Spring Cloud 建構的微服務和基礎設施中。 因為微服務間的調用,API 網關的請求轉發等内容實際上都是通過Ribbon 來實作的,包括後續我們将要介紹的 Feign, 它也是基于 Ribbon實作的工具。 是以, 對 Spring Cloud Ribbon 的了解和使用, 對于我們使用 Spring Cloud 來建構微服務非常重要。

負載均衡在系統架構中是一個非常重要,并且是不得不去實施的内容。 因為負載均衡是對系統的高可用、 網絡壓力的緩解和處理能力擴容的重要手段之一。 我們通常所說的負載均衡都指的是服務端負載均衡, 其中分為硬體負載均衡和軟體負載均衡。 硬體負載均衡主要通過在伺服器節點之間安裝專門用于負載均衡的裝置,比如 F5 等;而軟體負載均衡則是通過在伺服器上安裝一些具有均衡負載功能或子產品的軟體來完成請求分發工作, 比如Nginx 等。 不論采用硬體負載均衡還是軟體負載均衡,隻要是服務端負載均衡都能以類似下圖的架構方式建構起來,

硬體負載均衡的裝置或是軟體負載均衡的軟體子產品都會維護一個下挂可用的服務端清單,通過心跳檢測來剔除故障的服務端節點以保證清單中都是可以正常通路的服務端節點。當用戶端發送請求到負載均衡裝置的時候, 該裝置按某種算法(比如線性輪詢、 按權重負載、 按流量負載等)從維護的可用服務端清單中取出一台服務端的位址, 然後進行轉發。而用戶端負載均衡和服務端負載均衡最大的不同點在于上面所提到的服務清單所存儲的位置。 在用戶端負載均衡中, 所有用戶端節點都維護着自己要通路的服務端清單, 而這些服務端的清單來自于服務注冊中心,比如上一章我們介紹的Eureka服務端。同服務端負載均衡的架構類似, 在用戶端負載均衡中也需要心跳去維護服務端清單的健康性, 隻是這個步驟需要與服務注冊中心配合完成。 在Spring Cloud實作的服務治理架構中, 預設會建立針對各個服務治理架構的Ribbon自動化整合配置,

通過Spring Cloud Ribon的封裝, 我們在微服務架構中使用用戶端負載均衡調用非常簡單, 隻需要如下兩步:

1、服務提供者隻需要啟動多個服務執行個體并注冊到一個注冊中心或是多個相關聯的服務注冊中心;2、 服務消費者直接通過調用被@LoadBalanced 注解修飾過的 RestTemplate 來實作面向服務的接口調用。這樣,我們就可以将服務提供者的高可用以及服務消費者的負載均衡調用一起實作了。

    從@LoadBalanced注解 源碼的注釋中可以知道, 該 注解用來給RestTemplate做标記, 以使用負載均衡的用戶端(LoadBalancerClient)來配置它。(Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient);

    通過源碼以及之前的自動化配置類, 我們可以看到在攔截器中注入了 LoadBalancerClient的實作。 當一個被@LoadBalanced 注解修飾的 RestTemplate 對象向外發起 HTTP 請求時, 會被 LoadBalancerinterceptor 類的 intercept 函數所攔截。 由于我們在使用 RestTemplate 時采用了服務名作為 host, 是以直接從 HttpRequest 的URI對象中通過 getHost ()就可以拿到服務名,然後調用 execute 函數去根據服務名來選擇執行個體并發起實際的請求。

Spring Cloud Ribon中實作用戶端負載均衡的基本脈絡,了解了它是如何通過 LoadBalancerinterceptor攔截器對 RestTemplate的請求進行攔截, 并利用Spring Cloud的負載均衡器 LoadBalancerClient将以邏輯服務名為host的 URI轉換成具 體的服務 實 例位址的過程。 同 時 通 過分 析LoadBalancerClient 的Ribbon實作RibbonLoadBalancerClient, 可以知道在使用Ribbon實作負載均衡器的時候,實際使用的還是Ribbon中定義的ILoadBalancer接口的實作,自動化配置會采用ZoneAwareLoadBalancer的執行個體來實作用戶端負載均衡。

    以定時任務的方式進行服務清單的更新。

Ribbon它可以在通過用戶端中配置的 ribbonServerList服務端清單去輪詢通路以達到均衡負載的作用。

當Ribbon與Eureka聯合使用時,Rbbon的服務執行個體清單RibbonServerList會被DiscoveryEnabledNIWSServerList重寫, 擴充成從Eureka注冊中心中擷取服務端清單。 同時它也會用 NIWSDiscoveryPing來取代工貯ng, 它将職責委托給Eureka 來确定服務端是否已經啟動 。

為了實驗Ribbon的用戶端負載均衡功能, 我們通過 java-jar指令行的方式來啟動兩個不同端口的hello-service, 具體如下:

java -jar hello-service-0.0.1-SNAPSHOT.jar --server.port=8081

java -jar hello-service-0.0.1-SNAPSHOT.jar --server.port=8082

三、服務容錯保護: Spring CloudHystrix

      在微服務架構中, 我們将系統拆分成了很多服務單元, 各單元的應用間通過服務注冊與訂閱的方式互相依賴。 由于每個單元都在不同的程序中運作, 依賴通過遠端調用的方式執行, 這樣就有可能因為網絡原因或是依賴服務自身間題出現調用故障或延遲, 而這些問題會直接導緻調用方的對外服務也出現延遲, 若此時調用方的請求不斷增加, 最後就會因等待出現故障的依賴方響應形成任務積壓,最終導緻自身服務的癱瘓。

      在微服務架構中, 存在着那麼多的服務單元, 若一個單元出現故障, 就很容易因依賴關系而引發故障的蔓延,最終導緻整個系統的癱瘓,這樣的架構相較傳統架構更加不穩定。為了解決這樣的問題, 産生了斷路器等一系列的服務保護機制。斷路器模式源于 Martin Fowler 的 Circuit Breaker 一文。“斷路器”本身是一種開關裝置,用于在電路上保護線路過載, 當線路中有電器發生短路時, "WT路器” 能夠及時切斷故障電路, 防止發生過載、 發熱甚至起火等嚴重後果。在分布式架構中, 斷路器模式的作用也是類似的, 當某個服務單元發生故障(類似用電器發生短路) 之後, 通過斷路器的故障監控(類似熔斷保險絲), 向調用方傳回 一個錯誤響應, 而不是長時間的等待。 這樣就不會使得線程因調用故障服務被長時間占用不釋放,避免了故障在分布式系統中的蔓延。針對上述問題, Spring Cloud Hystrix實作了斷路器、 線程隔離等一系列服務保護功能。它也是基于Netflix的開源架構Hystrix實作的, 該架構的目标在于通過控制那些通路遠端系統、 服務和第三方庫的節點, 進而對延遲和故障提供更強大的容錯能力。Hystrix具備服務降級、 服務熔斷、 線程和信号隔離、 請求緩存、 請求合并以及服務監控等強大功能。

    注意:這裡還可以使用 Spring Cloud 應用中的@SpringCloudApplication 注解來修飾應用主類, 該注解的具體定義如下所示。 可以看到, 該注解中包含了上述我們所引用的三個注解, 這也意味着—個 Spring Cloud 标準應用應包含服務發現以及斷路器。@Target ({ Elemen七Type.TYPE})@Retention(RetentionPolicy.RUNTIME)@[email protected]@[email protected]@EnableCircuitBreakerpublic釭nterface SpringCloudApplication {

   Hystrix 預設逾時時間為 2000 毫秒

4、斷路器是否打開

在指令結果沒有緩存命中的時候, Hystrix在執行指令前需要檢查斷路器是否為打開狀态:

• 如果斷路器是打開的,那麼Hystrix不會執行指令,而是轉接到fallback處理邏輯(對應下面第8步)。

• 如果斷路器是關閉的, 那麼Hystrix跳到第5步,檢查是否有可用資源來 執行指令。

5、線程池I請求隊列I信号量是否占滿

如果與指令相關的線程池和請求隊列,或者信号量(不使用線程池的時候)已經被占滿, 那麼Hystrix也不會執行指令,而是轉接到fallback處理邏輯(對應下面第8步)。需要注意的是,這裡Hystrix所判斷的線程池并非容器的線程池,而是每個依賴服務的專有線程池。 Hystrix為了保證不會因為某個依賴服務的間題影響到其他依賴服務而采用了“艙壁模式" (Bulkhead Pattern)來 隔離每個依賴的服務。

6.HystrixObservableCommand.construct()或HystrixCommand.run()Hystrix會根據我們編寫的方法來決定采取什麼樣的方式去請求依賴服務。

• HystrixCommand.run(): 傳回一個單一 的結果,或者抛出異常。

• HystrixObservableCommand.construct(): 傳回一個Observable對象來發射多個結果,或通過onError發送錯誤通知。如果run()或construe七()方法的執行時間超過了指令設定的逾時阙值, 目前處理線程将會抛出一個Timeou七Exception (如果該指令不在其自身的線程中執行,則會通過單獨的計時線程來 抛出)。在這種情況下,Hystrix會轉接到fallback處理邏輯(第8步)。同時, 如果目前指令沒有被取消或中斷, 那麼它最終會忽略run()或者construct ()方法的傳回。如果指令沒有抛出異常并傳回了結果,那麼Hystrix在記錄一些日志并采集監控報告之後将該結果傳回。在使用run()的情況下,Hystrix會傳回一個Observable, 它發射單個結果并産生onCompleted的結束通知; 而在使用construct ()的情況下,Hystrix會直接傳回該方法産生的Observable對象。

7. 計算斷路器的健康度Hystrix會将 “ 成功 ”、“ 失敗”、“ 拒絕”、“ 逾時 ” 等資訊報告給斷路器,而斷路器會維護一組計數器來統計這些資料。斷路器會使用這些統計資料來決定是否要将斷路器打開,來對某個依賴服務的請求進行 “熔斷/短路 ”,直到恢複期結束。 若在恢複期結束後, 根據統計資料判斷如果還是未達到健康名額,就再次 “ 熔斷/短路 ”。

8. fallback處理當指令執行失敗的時候, Hystrix會進入fallback嘗試回退處理, 我們通常也稱該操作為“ 服務降級”。而能夠引起服務降級處理的清況有下面幾種:• 第4步, 目前指令處于 “熔斷I短路 ” 狀态, 斷路器是打開的時候。• 第5步, 目前指令的線程池、 請求隊列或 者信号量被占滿的時候。• 第6步,HystrixObservableCommand.construct()或HystrixCommand.run()抛出異常的時候。在服務降級邏輯中, 我們需要實作一個通用的響應結果,并且該結果的處理邏輯應當是從緩存或是根據一些靜态邏輯來擷取,而不是依賴網絡請求擷取。如果一定要在降級邏輯中包含網絡請求,那麼該請求也必須被包裝在HystrixCommand或是HystxObservableCommand中, 進而形成級聯的降級政策, 而最終的降級邏輯一定不是一個依賴網絡請求的處理, 而是一個能夠穩定地傳回結果的處理邏輯。

如果我們沒有為指令實作降級邏輯或 者降級處理邏輯中抛出了異常, Hystrix 依然會傳回一個Observable對象, 但是它不會發射任何結果資料, 而是通過onError 方法通知指令立即中斷請求,并通過onError()方法将引起指令失敗的異常發送給調用者。實作一個有可能失敗的降級邏輯是一種非常糟糕的做法, 我們應該在實作降級政策時盡可能避免失敗的情況。

斷路器原理

斷路器在 HystrixCommand 和 HystrixObservableCommand 執行過程中起到了舉足輕重的作用,它是 Hystrix 的核心部件。

可以看到它的接口定義并不複雜, 主要定義了三個斷路器的抽象方法。• allowRequest (): 每個 Hystrix 指令的請求都通過它判斷是否被執行。• isOpen(): 傳回目前斷路器是否打開。• markSuccess(): 用來閉合斷路器。

isOpen (): 判斷斷路器的打開/關閉狀态。 詳細邏輯如下所示。. 如果斷路器打開辨別為true, 則直接傳回true, 表示斷路器處千打開狀态。否則,就從度量名額對象 metrics 中擷取 HealthCounts 統計對象做進一步判斷(該對象記錄了 一個滾動時間窗内的請求資訊快照,預設時間窗為10秒)。o 如果它的請求總數(QPS)在預設的阙值範圍内就傳回 false, 表示斷路器處于未打開狀态。該阙值的配置參數為circuitBreakerRequestVolumeThreshold,預設值為20。口如果錯誤百分比在闌值範圍内就傳回 false, 表示斷路器處于未打開狀态。該阙值的配置參數為 circuitBreakerErrorThresholdPercentage, 預設值為50。口如果上面的兩個條件都不滿足,則将斷路器設定為打開狀态 (熔斷/短路)。 同時,如果是從關閉狀态切換到打開狀态的話,就将目前時間記錄到上面提到的circuitOpenedOrLastTestedTirne 對象中。

markSuccess(): 該函數用來在 “ 半開路” 狀态時使用。若Hystrix 指令調用成功,通過調用它将打開的斷路器關閉, 并重置度量名額對象。

allowRequest(): 判斷請求是否被允許,這個實作非常簡單。 先根據配置 對象propertes中的斷路器判斷強制打開或關閉屬性是否被設定。 如果強制打開,就直接傳回false, 拒絕請求。 如果強制關閉,它會允許所有請求,但是同時也會調用isOpen ()來執行斷路器的計算邏輯, 用來模拟斷路器打開/關閉的行為。 在預設情況下,斷路器并不會進入這兩個強制打開或關閉的分支中去,而是通過!isOpen ()I I allowSingleTest ()來判斷是否允許請求通路。

從allowSingleTest()的實作中我們可以看到,這裡使用了在isOpen()函數中當斷路器從閉合到打開時候所記錄的時間戳。 當斷路器在打開狀态的時候,這裡會判斷斷開時的時間戳+配置中的circuitBreakerSleep陽ndowinM旦liseconds時間是否小于目前時間, 是的話,就将目前時間更新到記錄斷路器打開的時間對象circuitOpenedOrLas七Tested霆me 中,并且允許此次請求。 簡單地說, 通過circuitBreakerSleepWindowinM口巨seconds 屬性設定了 一個斷路器打開之後的休眠時間(預設為5秒),在該休眠時間到達之後,将再次允許請求嘗試通路,

此時斷路器處于 “ 半開” 狀态,若此時請求繼續失敗, 斷路器又進入打開狀态, 并繼續等待下一個休眠視窗過去之後再次嘗試;若請求成功, 則将斷路器重新置于關閉狀态。是以通過 allowSingleTest()與isOpen ()方法的配合,實作了斷路器打開和關閉狀态的切換。

markSuccess(): 該函數用來在 “ 半開路” 狀态時使用。若Hystrix 指令調用成功,通過調用它将打開的斷路器關閉, 并重置度量名額對象。

依賴隔離

    “ 艙壁模式” 對于熟悉 Docker 的讀者一定不陌生, Docker 通過 “ 艙壁模式” 實作程序的隔離, 使得容器與容器之間不會互相影響。 而 Hystrix 則使用該模式實作線程池的隔離,它會為每一個依賴服務建立一個獨立的線程池, 這樣就算某個依賴服務出現延遲過高的情況, 也隻是對該依賴服務的調用産生影響, 而不會拖慢其他的依賴服務。通過實作對依賴服務的線程池隔離, 可以帶來如下優勢:• 應用自身得到完全保護, 不會受不可控的依賴服務影響。 即便給依賴服務配置設定的線程池被填滿, 也不會影響應用自身的其餘部分。• 可以有效降低接入新服務的風險。 如果新服務接入後運作不穩定或存在問題, 完全不會影響應用其他的請求。

• 當依賴的服務從失效恢複正常後, 它的線程池會被清理并且能夠馬上恢複健康的服務, 相比之下, 容器級别的清理恢複速度要慢得多。• 當依賴的服務出現配置錯誤的時候, 線程池會快速反映出此問題(通過失敗次數、延遲、逾時、拒絕等名額的增加情況)。 同時, 我們可以在不影響應用功能的情況下通過實時的動态屬性重新整理(後續會通過Spring Cloud Config與Spring Cloud Bus的聯合使用來介紹) 來處理它。• 當依賴的服務因實作機制調整等原因造成其性能出現很大變化的時候, 線程池的監控名額資訊會反映出這樣的變化。 同時, 我們也可以通過實時動态重新整理自身應用對依賴服務的阙值進行調整以适應依賴方的改變。• 除了上面通過線程池隔離服務發揮的優點之外, 每個專有線程池都提供了内置的并發實作, 可以利用它為同步的依賴服務建構異步通路。總之, 通過對依賴服務實作線程池隔離, 可讓我們的應用更加健壯, 不會因為個别依賴服務出現問題而引起非相關服務的異常。 同時, 也使得我們的應用變得更加靈活, 可以在不停止服務的情況下, 配合動态配置重新整理實作性能配置上的調整。雖然線程池隔離的方案帶來如此多的好處, 但是很多使用者可能會擔心為每一個依賴服務都配置設定一個線程池是否會過多地增加系統的負載和開銷。 對于這一點, 使用者不用過于擔心, 因為這些顧慮也是大部分工程師們會考慮到的,Net和x在設計Hystrix的時候,認為線程池上的開銷相對于隔離所帶來的好處是無法比拟的。 同時, Netflix也針對線程池的開銷做了相關的測試, 以用結果打消Hystrix實作對性能影響的顧慮。

定義服務降級

    fallback 是Hystrix指令執行失敗時使用的後備方法, 用來實作服務的降級處理邏輯。在HystrixCommand中可以通過重載 getFallback ()方法來實作服務降級邏輯, Hystrix會在 run() 執行過程中出現錯誤、 逾時、 線程池拒絕、 斷路器熔斷等情況時, 執行getFallback ()方法内的邏輯, 比如我們可以用如下方式實作服務降級邏輯:

在實際使用時,我們需要為大多數執行過程中可能會失敗的Hystrix指令實作服務降級邏輯, 但是也有一些情況可以不去實作降級邏輯, 如下所示。

• 執行寫操作的指令:當Hystrix指令是用來執行寫操作而不是傳回一些資訊的時候,通常情況下這類操作的傳回類型是 void 或是為空的 Observable, 實作服務降級的意義不是很大。 當寫入操作失敗的時候, 我們通常隻需要通知調用者即可。

• 執行批處理或離線計算的指令:當Hystrix指令是用來執行批處理程式生成一份報告或是進行任何類型的離線計算時, 那麼通常這些操作隻需要将錯誤傳播給調用者,然後讓調用者稍後重試而不是發送給調用者一個靜默的降級處理響應。

異常擷取

當 Hystrix 指令因為異常(除了 HystrixBadRequestException 的異常)進入服務降級邏輯之後, 往往需要對不同異常做針對性的處理, 那麼我們如何來擷取目前抛出的異常呢?在以傳統繼承方式實作的 Hystrix 指令中, 我們可以用 getFallback ()方法通過Throwable getExecutionException() 方法來擷取具體的異常, 通過判斷來進入不同的處理邏輯。除了傳統的實作方式之外,注解配置方式也同樣可以實作異常的擷取。 它的實作也非常簡單, 隻需要在 fallback 實作方法的參數中增加 Throwable e 對象的定義, 這樣在方法内部就可以擷取觸發服務降級的具體異常内容了

@HystrixCommand(fallbackMethod = "fallbackl")User getUserByid(String id) {throw new RuntimeException("getUserByid command failed");User fallbackl{String id, Throwable e) {assert "ge七UserByid command failed". equals {e. getMessage {));}

Spring Cloud Feign

    它基于 Netflix Feign 實作,整合了 Spring Cloud Ribbon 與 Spring Cloud Hystrix, 除了提供這兩者的強大功能之外,它還提供了一種聲明式的 Web 服務用戶端定義方式。

    我們在使用 Spring Cloud Ribbon 時, 通常都會利用它對 RestTemplate 的請求攔截來實作對依賴服務的接口調用, 而 RestTemplate 已經實作了對 HTTP 請求的封裝處理, 形成了一套模闆化的調用方法。在之前的例子中,我們隻是簡單介紹了 RestTemplate 調用的實作,但是在實際開發中,由于對服務依賴的調用可能不止于一處,往往一個接口會被多處調用,是以我們通常都會針對各個微服務自行封裝一些用戶端類來包裝這些依賴服務的調用。 這個時候我們會發現, 由于 RestTemplate 的封裝, 幾乎每一個調用都是簡單的模闆化内容。綜合上述這些情況, Spring Cloud Feign 在此基礎上做了進一步封裝, 由它來幫助我們定義和實作依賴服務接口的定義。在 Spring Cloud Feign 的實作下, 我們隻需建立一個接口并用注解的方式來配置它, 即可完成對服務提供方的接口綁定, 簡化了在使用 Spring Cloud Ribbon 時自行封裝服務調用用戶端的開發量。 Spring Cloud Feign 具備可插拔的注解支援,包括 Feign 注解和 JAX-RS 注解。 同時, 為了适應 Spring 的廣大使用者,它在 Netflix Feign的基礎上擴充了對 Spring MVC 的注解支待。

對于 Feign 自身的一些主要元件, 比如編碼器和解碼器等, 它也以可插拔的方式提供, 在有需求的時候我們可以友善地擴充和替換它們。

Spring Cloud Config

為分布式系統中的基礎設施和微服務應用提供集中化的外部配置支援, 它分為服務端與用戶端兩個部分。 其中服務端也稱為分布式配置中心, 它是一個獨立的微服務應用, 用來連接配接配置倉庫并為用戶端提供擷取配置資訊、 加密/解密資訊等通路接口;而用戶端則是微服務架構中的各個微服務應用或基礎設施, 它們通過指定的配置中心來管理應用資源與業務相關的配置内容,并在啟動的時候從配置中心擷取和加載配置資訊。

由于 Spring Cloud Config 實作的配置中心預設采用 Git 來存儲配置資訊, 是以使用 Spring Cloud Config 建構的配置伺服器, 天然就支援對微服務應用配置資訊的版本管理, 并且可以通過 Git 用戶端工具來友善地管理和通路配置内容。

配置伺服器在從 Git 中擷取配置資訊後, 會存儲一份在 config-server 的檔案系統中, 實質上config-server 是通過 git clone 指令将配置内容複制了 一份在本地存儲, 然後讀取這些内容并傳回給微服務應用進行加載。config-server 通過Git 在本地倉庫暫存,可以有效防止當 Git 倉庫出現故障而引起無法加載配置資訊的情況

本地Git倉庫: 在Config Server的檔案系統中, 每次用戶端請求擷取配置資訊時,Config Server從Git倉庫中擷取最新配置到本地,然後在本地Git倉庫中讀取并傳回 。當遠端倉庫無法擷取時, 直接将本地内容傳回。

用戶端應用從配置管理中擷取配置資訊遵從下面的執行流程:

1. 應用啟動時,根據bootstrap.propertes中配置的應用名{application}、環境名{profile}、 分支名{label}, 向ConfigServer請求擷取配置資訊。

2. Config Server根據自己維護的Git倉庫資訊和用戶端傳遞過來的配置定位資訊去查找配置資訊。

3 通過git clone指令将找到的配置資訊下載下傳到ConfigServer的檔案系統中。

4. Config Server建立Spring的ApplicationContext執行個體, 并從Git本地倉庫中加載配置檔案, 最後将這些配置内容讀取出來傳回給用戶端應用。

5. 用戶端應用在獲得外部配置檔案後加載到用戶端的ApplicationContext執行個體,該配置内容的優先級高于用戶端Jar包内部的配置内容, 是以在Jar包中重複的内容将不再被加載。

Config Server巧妙地通過git clone将配置資訊存于本地, 起到了緩存的作用, 即使當Git服務端無法通路的時候, 依然可以取ConfigServer中的緩存内容進行使用。

Git配置倉庫

在SpringCloud Config的服務端, 對于配置倉庫的預設實作采用了Git。Git非常适用于存儲配置内容,它可以非常友善地使用各種第三方工具來對其内容進行管理更新和版本化,同時Git倉庫的Hook功能還可以幫助我們實時地監控配置内容的修改。 其中,Git自身的版本控制功能正是其他一些配置中心所欠缺的, 通過Git 進行存儲意味着, 一個應用的不同部署執行個體可以從SpringCloud Config的服務端擷取不同的版本配置, 進而支援一些特殊的應用場景。

通路權限

Config Server在通路Git倉庫的時候, 若采用HTTP的方式進行認證, 那麼我們需要增加username和password屬性來配置賬戶

本地檔案系統

Spring Cloud Config也提供了一種不使用Git倉庫或SYN倉庫的存儲方式, 而是使用本地檔案系統的存儲方式來儲存配置資訊。 實作方式也非常簡單, 我們隻需要設定屬性spring.profiles.ac巨ve= na巨ve, Config Server會預設從應用的 src/main/resource 目錄下搜尋配置檔案。 如果需要指定搜尋配置檔案的路徑, 我們可以通過spring.cloud.config.server.native.searchLoca已ons 屬性來指定具體的配置檔案位置。

健康監測/屬性覆寫/

安全保護

由于配置中心存儲的内容比較敏感,做一定的安全處理是必需的。 為配置中心實作安全保護的方式有很多,比如實體網絡限制、 0Auth2 授權等。 不過, 由于我們的微服務應用和配置中心都建構于 Spring Boot 基礎上,是以與 Spring Security 結合使用會更加友善。我們隻需要在配置中心的 pom.xml 中加入 spring-boot-starter-security 依賴,不需要做任何其他改動就能實作對配置中心通路的安全保護。

預設情況下,我們可以獲得一個名為 user 的使用者,并且在配置中心啟動的時候,在日志中列印出該使用者的随機密碼,

由于我們已經為 config-server 設定了安全保護,如果這時候連接配接到配置中心的用戶端中沒有設定對應的安全資訊,在擷取配置資訊時會傳回401錯誤。 是以,需要通過配置的方式在用戶端中加入安全資訊來通過校驗

加密解密

如果我們直接将敏感資訊以明文的方式存儲于微服務應用的配置檔案中是非常危險的。針對這個問題,Spring Cloud Config 提供了對屬性進行加密解密的功能,以保護配置檔案中的資訊安全。在 Spring Cloud Config 中通過在屬性值前使用 {cipher} 字首來标注該内容是一個加密值, 當微服務用戶端加載配置時,配置中心會自動為帶有 {cipher} 字首的值進行解密。

分布式服務跟蹤: Spring Cloud Sleuth

在複雜的微服務架構系統中, 幾乎每一個前端請求都會形成一條複雜的分布式服務調用鍊路, 在每條鍊路中任何一個依賴服務出現延遲過高或錯誤的時候都有可能引起請求最後的失敗。這時候,對于每個請求, 全鍊路調用的跟蹤就變得越來越重要, 通過實作對請求調用的跟蹤可以幫助我們快速發現錯誤根源以及監控分析每條請求鍊路上的性能瓶頸等。

注意事項:

1、必須要有 Dto的預設構造函數。 不然, Spring Cloud Feign 根據 JSON 字元串轉換 User 對象時會抛出異常。

2、在定義各參數綁定時,@RequestParam、@RequestHeader 等可以指定參數名稱的注解, 它們的 value 千萬不能少。 在SpringMVC 程式中, 這些注解會根據參數名來作為預設值,但是在Feign 中綁定參數必須通過 value 屬性來指明具體的參數名,不然會抛出口legalStateException 異常, value 屬性不能為空。

3、使用 Spring Cloud Feign 繼承特性的優點很明顯, 可以将接口的定義從 Controller 中剝離, 同時配合Maven私有倉庫就可以輕易地實作接口定義的共享, 實作在建構期的接口綁定, 進而有效減少服務用戶端的綁定配置。 這麼做雖然可以很友善地實作接口定義和依賴的共享, 不用再複制粘貼接口進行綁定, 但是這樣的做法使用不當的話會帶來副作用。 由于接口在建構期間就建立起了依賴,那麼接口變動就會對項目建構造成影響, 可能服務提供方修改了一個接口定義,那麼會直接導緻用戶端工程的建構失敗。 是以, 如果開發團隊通過此方法來實作接口共享的話, 建議在開發評審期間嚴格遵守面向對象的開閉原則, 盡可能地做好前後版本的相容,防止牽一發而動全身的後果,增加團隊不必要的維護工作量。

4、Ribbon的逾時 與Hystrix的逾時是兩個概念。 為了讓上述實作有效,我們需要 讓Hystrix的逾時時間大于Ribbon的逾時時間, 否則Hystrix指令逾時後, 該指令直接熔斷, 重試機制就 沒有任何意義了。

5、Feign預設對Hystrix未開啟的,需要設定:feign:

  hystrix:

    enabled: true 

來開啟。

6、請求壓縮Spring Cloud Feign支援對請求與響應進行 GZIP壓縮,以減少通信過程中的性能損耗。我們隻需通過下面兩個參數設定, 就能開啟請求與響應的壓縮功能:feign.compression.request.enabled=true feign.compression.response.enabled=true

spring cloud zuul:

優點:

• 它作為系統的統一入口, 屏蔽了系統内部各個微服務的細節。

• 它可以與服務治理架構結合,實作自動化的服務執行個體維護以及負載均衡的路由轉發。

• 它可以實作接口權限校驗與微服務業務邏輯的解耦。

• 通過服務網關中的過濾器, 在各生命周期中去校驗請求的内容, 将原本在對外服務層做的校驗前移, 保證了微服務的無狀态性, 同時降低了微服務的測試難度, 讓服務本身更集中關注業務邏輯的處理。

API網關是一個更為智能的應用伺服器, 它的定義類似于面向對象設計模式中的Facade模式, 它的存在就像是整個微服務架構系統的門面一樣,所有的外部用戶端通路都需要經過它來進行排程和過濾。它除了要實作請求路由、 負載均衡、 校驗過濾等功能之外, 還需要更多能力, 比如與服務治理架構的結合、 請求轉發時的熔斷機制、 服務的聚合等一系列進階功能。

首先, 對千路由規則與服務執行個體的維護間題。 SpringCloud Zuul通過與SpringCloudEureka進行整合, 将自身注冊為Eureka服務治理下的應用, 同時從Eureka中獲得了所有其他微服務的執行個體資訊。 這樣的設計非常巧妙地将服務治理體系中維護的執行個體資訊利用起來, 使得将維護服務執行個體的工作交給了服務治理架構自動完成, 不再需要人工介入。 而對千路由規則的維護, Zuul預設會将通過以服務名作為ContextPath的方式來建立路由映射,大部分情況下, 這樣的預設設定已經可以實作我們大部分的路由需求, 除了一些特殊情況(比 如相容一些老的URL)還需要做一些特别的配置 。 但是相比于之前架構下的運維工作量, 通過引入SpringCloud Zuul實作API網關後, 已經能夠大大減少了。

其次, 對千類似簽名校驗、 登入校驗在微服務架構中的備援問題。 理論上來說, 這些校驗邏輯在本質上與微服務應用自身的業務并沒有多大的關系, 是以 它們完全可以獨立成一個單獨的服務存在, 隻是它們被剝離和獨立出來之後, 并不是給各個微服務調用, 而是在API網關服務上進行統一調用來對微服務接口做前置過濾, 以實作對微服務接口的攔截和校驗 。 SpringCloud Zuul提供了一套過濾器機制, 它可以 很好地支援這樣的任務。 開發者可以通過使用Zuul來建立各種校驗過濾器,然後指定哪些規則的請求需要執行校驗邏輯,隻有通過校驗的才會被路由到具體的微服務接口,不然就傳回錯誤提示。 通過這樣的改造,各個業務層的微服務應用就不再需要非業務性質的校驗邏輯了, 這使得我們的微服務應用可以更專注千業務邏輯的開發, 同時微服務的自動化測試也變得更容易實作。

請求路由

面向服務的路由很顯然, 傳統 路由的配置方式對于我們來說并不友好, 它同樣需要運維人員花費大量的時間來維護各個路由 path與url的關系 。 為了解決這個問題, SpringCloud Zuul實作了與Spring Cloud Eureka的無縫整合, 我們可以讓路由的path不是映射具體的url, 而是讓它映射到某個具體的服務 , 而具體的url則交給Eureka的服務發現機制去自動維護, 我們稱這類路由為面向服務的路由。

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.service!d=hello-service 

通過面向服務的路由配置 方式, 我們不需要再為各個路由維護微服務應用的具體 執行個體的位置, 而是 通過簡單的path與serviceld的映射組合,使得維護工作變得非常簡單。 這完全歸功于Spring Cloud Eureka的服務發現機制,它使得API網關服務可以自動化完成服務執行個體清單的維護,完美地解決了對路由映射 執行個體的維護問題。

請求過濾

比較好的做法是将這些校驗邏輯剝離出去, 建構出一個獨立的鑒權服務。更好的做法是通過前置的網關服務來完成這些非業務性質的校驗。由于網關服務的加入, 外部用戶端通路我們的系統已經有了統一入口, 既然這些校驗與具體業務無關, 那何不在請求到達的時候就完成校驗和過濾, 而不是轉發後再過濾而導緻更長的請求延遲。 同時, 通過在網關中完成校驗和過濾, 微服務應用端就可以去除各種複雜的過濾器和攔截器了, 這使得微服務應用接口的開發和測試複雜度也得到了相應降低。

Zuul 允許開發者在API網關上通過定義過濾器來實作對請求的攔截與過濾