SpringCloud微服務架構實戰:Feign+Hystrix實現RPC調用保護

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"公衆號:Java架構師聯盟,每日更新技術好文","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Feign+Hystrix實現RPC調用保護","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Spring Cloud微服務架構下,RPC保護可以通過Hystrix開源組件來實現,並且Spring Cloud對Hystrix組件進行了集成,使用起來非常方便。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Hystrix翻譯過來是豪豬,由於豪豬身上長滿了刺,因此能保護自己不受天敵的傷害,代表了一種防禦機制。Hystrix開源框架是Netflix開源的一個延遲和容錯的組件,主要用於在遠程Provider服務異常時對消費端的RPC進行保護。有關Hystrix的詳細資料,可參考其官方網站(https://github.com/Netflix/Hystrix),本文只對它的基本原理和使用進行介紹。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Hystrix之前需要在Maven的pom文件中增加以下Spring CloudHystrix集成模塊的依賴:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" \n \n org.springframework.cloud\n spring-cloud-starter-netflix-hystrix\n \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Spring Cloud架構中,Hystrix是和Feign組合起來使用的,所以需要在應用的屬性配置文件中開啓Feign對Hystrix的支持:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"feign:\n hystrix:\n enabled: true #開啓Hystrix對Feign的支持\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在啓動類上添加@EnableHystrix或者@EnableCircuitBreaker。注意,@EnableHystrix中包含了@EnableCircuitBreaker。作爲示例,下面是Demo-provider啓動類的部分代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"package com.crazymaker.springcloud.demo.start;\n...\n/**\n *在啓動類上啓用Hystrix\n */\n@EnableHystrix\npublic class DemoCloudApplication\n{\n public static void main(String[] args)\n {\n SpringApplication.run(DemoCloudApplication.class, args);\n ...\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud Hystrix的RPC保護功能包括失敗回退、熔斷、重試、艙壁隔離等,接下來學習一下Hystrix的失敗回退和熔斷兩大功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Spring Cloud Hystrix失敗回退","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什麼是失敗回退呢?當目標Provider實例發生故障時,RPC的失敗回退會產生作用,返回一個後備的結果。一個失敗回退的演示如圖2-16所示,有A、B、C、D四個Provider實例,A-Provider和B-Provider對D-Provider發起RPC遠程調用,但是D-Provider發生了故障,在A、B收到失敗回退保護的情況下,最終會拿到失敗回退提供的後備結果(或者Fallback回退結果)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2b/2bf36f092ad8ca8ca2cf4c8c581a2117.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2-16 RPC遠程調用失敗回退示意圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何設置RPC調用的回退邏輯呢?有兩種方式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)定義和使用一個Fallback回退處理類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)定義和使用一個FallbackFactory回退處理工廠類。首先來看第一種方式:定義和使用一個Fallback回退處理類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一種方式具體的實現可以分爲兩步:第一步是實現Feign客戶端遠程調用接口,編寫一個Fallback回退處理類,並將RPC失敗後的回退邏輯編寫在回退處理類對應的實現方法中;第二步是在Feign客戶端接口的關鍵性註解@FeignClient上配置失敗處理類,具體來說,將該註解的Fallback屬性的值配置爲上一步定義的Fallback回退處理類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面介紹具體的實例,演示如何定義和使用一個Fallback回退處理類。在crazy-springcloud腳手架的uaa-client模塊中,有一個用於對uaa-provider進行RPC調用的Feign客戶端遠程調用接口UserClient,其目的是獲取用戶信息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步爲UserClient接口定義一個簡單的Fallback回退處理實現類,代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"package com.crazymaker.springcloud.user.info.remote.fallback;\n//省略import\n/**\n *Feign客戶端接口的Fallback回退處理類\n */\n@Component\npublic class UserClientFallback implements UserClient\n{\n /**\n *獲取用戶信息RPC失敗後的回退方法\n */\n @Override\n public RestOut detail(Long id)\n {\n return RestOut.error(\"failBack:user detail rest服務調用失敗\" );\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步是在UserClient客戶端接口的@FeignClient註解中,將Fallback屬性的值配置爲上一步定義的Fallback回退處理類UserClientFallback,代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"package com.crazymaker.springcloud.user.info.remote.client;\n//省略import\n/**\n *Feign客戶端接口\n *@description:獲取用戶信息的RPC接口類\n*/\n@FeignClient(value = \"uaa-provider\",\n configuration = FeignConfiguration.class,\n fallback = UserClientFallback.class, #配置回退處理類 path = \"/uaa-provider/api/user\")\npublic interface UserClient\n{\n @RequestMapping(value = \"/detail/v1\", method = RequestMethod.GET)\n RestOut detail(@RequestParam(value = \"userId\") Long userId);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回退處理類的實現已經完成,如何進行驗證呢?仍然使用前面定義的demo\u0002provider的REST接口/api/call/uaa/user/detail/v2,該接口通過UserClient對uaa-provider進行遠程調用。具體的演示方式爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"停掉所有uaa-provider服務,然後在demo-provider的swagger-ui界面訪問其REST接口/api/call/uaa/user/detail/v2,該接口的內部代碼會通過UserClient遠程調用Feign接口對目標uaa-provider的REST接口/api/user/detail/v1發起FeignRPC遠程調用,而uaa-provider全部服務處於宕機狀態,因此Feign將會觸發Hystrix回退,執行Fallback回退處理類UserClientFallback的回退實現方法,返回Fallback回退處理的內容,輸出的內容如圖2-17所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6d/6dd907316261fd413ae9bc98bc744cc2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2-17 UserClientFallback回退處理類生效後的示意圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來看第二種方式,定義和使用一個Fallback回退處理工廠類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二種方式具體的實現也可以分爲兩步:第一步創建一個Fallback回退處理工廠類,該工廠類需要實現Hystrix的FallbackFactory回退工廠接口,實現其抽象的create創建方法,在該方法的實現代碼中,需要返回一個Feign客戶端接口的實現類,方法中的具體實現即爲回退處理實例,可以通過匿名類的方式創建一個新的回退處理類,並在該匿名類的每個方法的實現代碼中編寫好RPC回退邏輯;第二步在Feign客戶端接口的關鍵性註解@FeignClient上配置失敗處理工廠類,將fallbackFactory屬性的值配置爲上一步定義的FallbackFactory回退處理工廠類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面介紹具體的實例,演示如何定義和使用一個FallbackFactory回退處理工廠類。這裏任意以uaa-client模塊中的RPC調用接口UserClient爲例進行演示。第一步爲其定義一個簡單的FallbackFactory回退處理工廠類,代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"package com.crazymaker.springcloud.user.info.remote.fallback;\n//省略import\n/**\n *Feign客戶端接口的回退處理工廠類\n */\n@Slf4j\n@Component\npublic class UserClientFallbackFactory implements FallbackFactory\n{\n /**\n *創建UserClient客戶端的回退處理實例\n */\n @Override\n public UserClient create(final Throwable cause) {\nlog.error(\"RPC異常了,回退!\",cause);\n /**\n *創建一個UserClient客戶端接口的匿名回退實例\n */\n return new UserClient() {\n /**\n *方法: 獲取用戶信息RPC失敗後的回退方法\n */\n @Override\n public RestOut detail(Long userId)\n {\n return RestOut.error(\"FallbackFactory fallback:user detail rest服務調用失敗\" );\n }\n };\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步是在Feign客戶端接口UserClient的@FeignClient註解上,將fallbackFactory屬性的值配置爲上一步定義的UserClientFallbackFactory回退處理工廠類,代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"package com.crazymaker.springcloud.user.info.remote.client;\n//省略import\n/**\n *Feign客戶端接口\n *@description:獲取用戶信息的RPC接口類\n*/\n@FeignClient(value = \"uaa-provider\",\n configuration = FeignConfiguration.class,\n配置回退處理\n廠類 fallbackFactory = UserClientFallbackFactory.class, #配置回退處理工廠類\n path = \"/uaa-provider/api/user\")\npublic interface UserClient\n{\n @RequestMapping(value = \"/detail/v1\", method = RequestMethod.GET)\n RestOut detail(@RequestParam(value = \"userId\") Long userId);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二種方式回退工廠類的具體驗證過程與第一種方式回退類的驗證相同:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"停掉所有的uaa-provider服務,然後在demo-provider的swagger-ui界面訪問其REST接口/api/call/uaa/user/detail/v2,此REST接口的內部代碼會通過UserClient遠程調用Feign接口對目標uaa-provider的REST接口/api/user/detail/v1發起Feign RPC遠程調用,而uaa-provider全部服務處於宕機狀態,因此Feign將會觸發Hystrix回退,執行fallback回退處理工廠類UserClientFallbackFactory的create方法創建一個回退處理類實例,並執行回退處理類實例中的回退處理邏輯,返回回退處理的結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在進行失敗回退時,使用第一種方式的回退類和使用第二種方式的回退工廠類有什麼區別呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是:在使用第一種方式的回退類時,遠程調用RPC過程中所引發的異常已經被回退邏輯徹底地屏蔽掉了。應用程序不太方便干預,也看不到RPC過程中的具體異常,儘管這些異常對於問題的排除非常有幫助。在使用第二種方式的回退工廠類時,應用程序可以通過Java代碼對RPC異常進行攔截和處理,包括進行日誌輸出。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"分佈式系統面臨的雪崩難題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在分佈式系統中,一個服務可能會依賴很多其他的服務,並且這些服務不可避免有失效的可能。假如一個應用運行30個Provider實例,每個實例99.99%的時間處於正常服務狀態,即使只有0.01%的失敗率,每個月仍然有幾個小時不可用。另外,還有一個大問題:流量洪峯過來時,服務有可能被其他服務所依賴。如果這個Provider實例出現延遲響應,就會導致其他Provider發生更多級聯故障,從而導致這個分佈式系統不可用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉一個簡單的例子,在一個秒殺系統中,商品(good\u0002provider)、訂單(order-provider)、秒殺(seckill-provider)3個Provider都會通過RPC遠程調用到用戶賬號與認證(uaa-provider)的相關接口,查詢用戶的相關信息,如圖2-18所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e5/e5fad1b6d8095ddcd3b23fd3f7f5d980.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2-18 秒殺系統中,商品、訂單、秒殺、用戶4個Provider之間的依 賴示意圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若在流量洪峯過來之時uaa-provider出現響應遲鈍(甚至宕機),則商品、訂單、秒殺3個Provider都會出現等待超時而導致響應緩慢,由於排隊的請求越來越多、單個請求時間變得很長(因爲內部都有超時等待),因此各服務節點的系統資源(CPU、內存等)很快會耗盡,最後進入系統性雪崩狀態,如圖2-19所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a8/a8cb7f1534e24cf47531a60157a0f667.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2-19 流量洪峯過來時因uaa-provider響應緩慢導致整體雪崩","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總體來說,在微服務架構中,根據業務拆分成一個個Provider微服務,由於網絡原因或者自身的原因,服務並不能保證100%可用,爲了保證服務提供者高可用,單個Provider服務通常會多體部署。由於Provider與Provider之間的依賴性,故障或者不可用會沿請求調用鏈向上傳遞,對整個系統造成癱瘓的災難性後果,這就是故障的雪崩效應。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"引發雪崩效應的原因比較多,下面是常見的幾種:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)硬件故障:如服務器宕機、機房斷電、光纖被挖斷等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)流量激增:如流量異常、巨量請求瞬時湧入(如秒殺)等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(3)緩存穿透:一般發生在系統重啓所有緩存失效時,或者發生在短時間內大量緩存失效時,前端過來的大量請求沒有命中緩存,直擊後端服務和數據庫,造成服務提供者和數據庫超負荷運行,引起整體癱瘓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(4)程序BUG:如程序邏輯BUG導致內存泄漏等原因引發的整體癱瘓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(5)JVM卡頓:JVM的FullGC時間較長,極端的情況長達數十秒,這段時間內JVM不能提供任何服務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決雪崩效應,業界提出了熔斷器模型。通過熔斷器,當一些非核心服務出現響應遲緩或者宕機等異常時,對服務進行降級並提供有損服務,以保證服務的柔性可用,避免引起雪崩效應。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Spring Cloud Hystrix熔斷器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在物理學上,熔斷器本身是一個開關裝置,用在電路上保護線路過載,當線路中有電器發生短路時,熔斷器能夠及時切斷故障,防止發生過載、發熱甚至起火等嚴重後果。分佈式架構中的熔斷器主要用於RPC接口上,爲接口安裝上“保險絲”,以防止RPC接口出現擁塞時導致系統壓力過大而引起的系統癱瘓,當RPC接口流量過大或者目標Provider出現異常時,熔斷器及時切斷故障可以起到自我保護的作用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼說熔斷器非常重要呢?如果沒有過載保護,在分佈式系統中,當被調用的遠程服務無法使用時,就會導致請求的資源阻塞在遠程服務器上而耗盡。很多時候剛開始可能只是出現了局部小規模的故障,然而由於種種原因,故障影響範圍越來越大,最終導致全局性的後果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"熔斷器通常也叫作熔斷器,其具體的工作機制爲:統計最近RPC調用發生錯誤的次數,然後根據統計值中的失敗比例等信息來決定是否允許後面的RPC調用繼續或者快速地失敗回退。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"熔斷器的3種狀態如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)關閉(closed):熔斷器關閉狀態,這也是熔斷器的初始狀態,此狀態下RPC調用正常放行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)開啓(open):失敗比例到一定的閾值之後,熔斷器進入開啓狀態,此狀態下RPC將會快速失敗,然後執行失敗回退邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(3)半開啓(half-open):在打開一定時間之後(睡眠窗口結束),熔斷器進入半開啓狀態,小流量嘗試進行RPC調用放行。如果嘗試成功,熔斷器就變爲關閉狀態,RPC調用正常;如果嘗試失敗,熔斷器就變爲開啓狀態,RPC調用快速失敗。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"熔斷器狀態之間的相互轉換關係如圖2-20所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b2/b23a00ae171819c47e50cea4dbd8ce79.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2-20 熔斷器狀態之間的相互轉換關係","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面重點介紹熔斷器的半開啓狀態。在半開啓狀態下,允許進行一次RPC調用的嘗試,如果實際調用成功,熔斷器就會復位到關閉狀態,迴歸正常的模式;但是如果這次RPC調用的嘗試失敗,熔斷器就會返回到開啓狀態,一直等待到下次半開啓狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud Hystrix中的熔斷器默認是開啓的,但是可以通過配置熔斷器的參數進行定製。下面是demo-provider微服務中熔斷器示例的相關配置:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"hystrix:\n ...\n command:\n default:\n ...\n circuitBreaker: #熔斷器相關配置\n enabled: true #是否使用熔斷器,默認爲true\n requestVolumeThreshold: 20 #窗口時間內的最小請求數\n sleepWindowInMilliseconds: 5000 #打開後允許一次嘗試的睡眠時間,默認配置爲5秒\n errorThresholdPercentage: 50 #窗口時間內熔斷器開啓的錯誤比例,默認配置爲50\n metrics:\n rollingStats:\n timeInMilliseconds: 10000 #滑動窗口時間\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"numBuckets: 10 #滑動窗口的時間桶數以上用到的Hystrix熔斷器相關參數分爲兩類:熔斷器相關參數和滑動窗口相關參數。對示例中用到的熔斷器的相關參數大致介紹如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)hystrix.command.default.circuitBreaker.enabled:該配置用來確定熔斷器是否用於跟蹤RPC請求的運行狀態,或者說用於配置是否啓用熔斷器,默認值爲true。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)hystrix.command.default.circuitBreaker.requestVolumeThreshold:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該配置用於設置熔斷器觸發熔斷的最少請求次數。如果設置爲20,那麼當一個滑動窗口時間內(比如10秒)收到19個請求時,即使19個請求都失敗,熔斷器也不會打開變成open狀態,默認值爲20。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(3)hystrix.command.default.circuitBreaker.errorThresholdPercentage:該配置用於設置錯誤率閾值,在滑動窗口時間內,當錯誤率超過此值時,熔斷器進入open狀態,所有請求都會觸發失敗回退(fallback),錯誤率閾值百分比的默認值爲50。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(4)hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds:該配置用於設置熔斷器的睡眠窗口,具體指的是確定熔斷器打開之後多長時間才允許一次請求嘗試執行,默認值爲5 000毫秒,表示當熔斷器打開後,5 000毫秒內會拒絕所有請求,5 000毫秒後熔斷器纔會進行入half-open狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(5)hystrix.command.default.circuitBreaker.forceOpen:如果配置爲true,熔斷器就會被強制打開,所有請求將觸發失敗回退(Fallback),默認值爲false。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"熔斷器的狀態轉換與Hystrix的滑動窗口的健康統計值(比如失敗比例)相關。接下來對示例中使用到的Hystrix健康統計相關配置大致介紹如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)hystrix.command.default.metrics.rollingStats.timeInMilliseconds:設置統計滑動窗口的持續時間(以毫秒爲單位),默認值爲10 000毫秒。熔斷器的打開會根據一個滑動窗口的統計值來計算,若滑動窗口時間內的錯誤率超過閾值,則熔斷器進入開啓狀態。滑動窗口將被進一步細分爲時間桶(Bucket),滑動窗口的統計值等於窗口內所有時間桶的統計信息的累加,每個時間桶的統計信息包含請求成功(Success)、失敗(Failure)、超時(Timeout)、被拒(Rejection)的次數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)hystrix.command.default.metrics.rollingStats.numBuckets:設置一個滑動窗口被劃分的時間桶數量,默認值爲10。若滑動窗口的持續時間爲10 000毫秒,並且一個滑動窗口被劃爲10個時間桶,則一個時間桶的時間爲1秒。所設置的numBuckets(時間桶數量)和timeInMilliseconds(滑動窗口時長)的值有一定關係,必須符合timeInMilliseconds%numberBuckets==0的規則,否則會拋出異常,例如70 000(滑動窗口70 000毫秒)%700(桶數)==0是可以的,但是70000(滑動窗口70 000毫秒)%600(桶數)==400將拋出異常。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上有關Hystrix熔斷器的配置選項使用的是hystrix.command.default前綴,這些默認配置項將對項目中所有FeignRPC接口生效,除非某個Feign RPC接口進行單獨配置。如果需要對某個Feign RPC調用進行特殊的配置,配置項前綴的格式如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"hystrix.command.類名#方法名(參數類型列表)\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面來看一個對單個接口進行特殊配置的例子,以對UserClient類中","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"的Feign RPC接口/detail/v1進行特殊配置爲例。該接口的功能是從user-provider服務獲取用戶信息,在配置之前先看一下UserClient接口的代碼,具體如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"package com.crazymaker.springcloud.user.info.remote.client;\n...\n@FeignClient(value = \"uaa-provider\",\n configuration = FeignConfiguration.class,\n fallback = UserClientFallback.class,\n path = \"/uaa-provider/api/user\")\npublic interface UserClient\n{\n /**\n *遠程調用RPC方法:獲取用戶詳細信息\n *@param userId用戶Id\n *@return用戶詳細信息\n */\n @RequestMapping(value = \"/detail/v1\", method = RequestMethod.GET)\n RestOut detail(@RequestParam(value = \"userId\") Long userId);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在demo-provider中,如果要對UserClient.detail接口的RPC調用的熔斷器參數進行特殊的配置,就不使用hystrix.command.default默認前綴,而是使用hystrix.command.FeignClient#Method格式的前綴,具體的配置項如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"hystrix:\n ...\n command:\n UserClient#detail(Long): #格式爲:類名#方法名(參數類型列表)\n ...\n circuitBreaker: #熔斷器相關配置\n enabled: true #是否使用熔斷器,默認爲true\n requestVolumeThreshold: 20 #至少有20個請求,熔斷器纔會達到熔斷觸發的次數閾值\n sleepWindowInMilliseconds: 5000 #打開後允許一次嘗試的睡眠時間,默認配置爲5秒\n errorThresholdPercentage: 50 #窗口時間內熔斷器開啓的錯誤比例,默認配置爲50\n metrics:\n rollingPercentile:\n timeInMilliseconds: 60000 #滑動窗口時間\n numBuckets: 600 #滑動窗口的時間桶數\n bucketSize: 200 #時間桶內的統計次數\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了熔斷器circuitBreaker相關參數和metrics滑動窗口相關參數之外,其他很多Hystrix command參數也可以對特定的Feign RPC接口進行特殊配置,配置時仍然使用“類名#方法名(形參類型列表)”的格式。對於初學者來說,有關滑動窗口的概念和配置理解起來還是比較費勁的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"本文給大家講解的內容是springcloud入門實戰:Feign+Hystrix實現RPC調用保護","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"下篇文章給大家講解的是SpringCloudRPC遠程調用核心原理;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"覺得文章不錯的朋友可以轉發此文關注小編;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"感謝大家的支持!","attrs":{}},{"type":"text","text":"SpringCloud微服務架構實戰:Feign+Hystrix實現RPC調用保護 ","attrs":{}}]}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章