一、Hystrix 是什麼
在微服務架構中,我們將系統拆分成了若干弱小的單元,單元與單元之間通過HTTP或者TCP等方式相互訪問,各單元的應用間通過服務註冊與訂閱的方式相互依賴。由於每個單元都在不同的程式中執行,依賴遠端呼叫
的方式執行,這樣就可能引起因為網速變慢或者網路故障導致請求變慢或超時,若此時呼叫方的請求在不斷增加,最後就會因等待出現故障的依賴方響應形成任務積壓,最終導致自身服務的癱瘓。
Hystrix
是Netflix 中的一個元件庫,它隔離了服務之間的訪問點,阻止了故障節點之間可能會引起的雪崩效應,並提供了後備選項。
在微服務架構中,存在著許多的服務單元,若單一節點的故障,就很容易因為依賴關係而引發故障的蔓延,最終導致整個生態系統的癱瘓。為了解決這樣的問題,產生了斷路器
等一系列的保護機制措施。
在分散式架構中
,斷路器模式的作用也是類似的,當某個服務單元發生故障(類似用電器發生短路)之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得執行緒因呼叫故障服務被長時間佔用不釋放,避免了故障在分散式系統中的蔓延。
雪崩效應
雪崩效應就像是水滴石穿,蝴蝶效應一樣,是指微小的事物隨著時間的推移,會變得越來越巨大,從而對整個環境造成影響的現象。例如:在生態系統中,某一類物種的滅絕可能對整個生態系統造成不了太大的損失,但是這類物種的滅絕可能會引發其他物種的死亡,其他物種的滅絕又會影響另外一種物種的滅亡,就像雪球越滾越大,最終會導致整個生態系統的崩潰。
如上圖所示:A作為服務提供者,B為A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,並將不可用像滾雪球一樣放大到C和D時,雪崩效應就形成了。
雪崩效應產生場景
流量激增
: 比如異常流量,使用者重試導致系統負載升高;快取重新整理
: 假設A為client端,B為Server端,假設A系統請求都流向B系統,請求超出了B系統的承載能力,就會造成B系統崩潰連線未釋放
: 程式碼迴圈呼叫的邏輯問題,資源未釋放引起的記憶體洩漏等問題;硬體故障
: 比如當機,機房斷電等執行緒同步等待
: 系統間經常採用同步服務呼叫模式,核心服務和非核心服務共用一個執行緒池和訊息佇列。如果一個核心業務執行緒呼叫非核心業務執行緒,這個非核心執行緒交由第三方系統完成,當第三方系統本身出現問題,導致核心執行緒阻塞,一直處於等待狀態,而程式間的呼叫是有超時限制的,最終這條執行緒將斷掉,也可能引發雪崩;
常見解決方案
針對上述的雪崩問題,每一條都有一個自己的解決方案,但是任何一個解決方案能夠應對所有場景
針對流量激增,採用自動擴容以應對流量激增,或者在負載均衡器上安裝限流模組
針對快取重新整理,參考Cache應用的服務過載案例研究
針對硬體故障,採用多機房災備,跨機房路由
針對同步等待,採用執行緒隔離,熔斷器等機制
通過實踐發現,執行緒同步等待是最常見引發的雪崩效應的場景。
二、Hystrix斷路器搭建
在開始使用Spring Cloud Hystrix斷路器之前,我們先用之前實現的一些內容作為基礎,構建一個如下圖所示的服務呼叫關係:
如圖所示,上面需要的角色有三個,服務有四個
ribbon-connsumer
: ribbon消費者,消費server-provider提供的服務server-provider
: 服務提供者,提供服務供消費者消費(有點像父母默默的付出一樣),啟動兩個例項,還記得怎麼啟動嗎?—server.port 啟動eureka-server
: eureka註冊中心,提供最基本的訂閱釋出功能。消費者和服務提供者都需要往註冊中心註冊自己
依次啟動上面的四個服務,發現註冊中心已經成功註冊了四個服務(包括自己)
- 呼叫http://localhost:9000/ribbon-consumer 發現能夠通過Ribbon進行遠端呼叫
- 在未加入斷路器之前,關閉ribbon-consumer 的連線,再次呼叫http://localhost:9000/ribbon-consumer,發現服務無法提供(使用Postman 測試)
下面開始引入Hystrix
在ribbon-consumer 工程的pom.xml的dependency節點下引入spring-cloud-starter-hystrix依賴
在ribbon-consumer 工程的
主載入類
中新增@EnableCircuitBreaker
開啟斷路器的功能注意:這裡也可以使用@SpringCloudApplication註解來修飾應用主類,具體定義如下
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker public @interface SpringCloudApplication {}
SpringCloudApplication 註解上有@EnableCircuitBreaker 註解,用來開啟斷路器的功能,其他主要註解是@SpringBootApplication ,這個註解是SpringBoot的啟動類註解, @EnableDiscoveryClient該註解可以發現Eureka註冊中心
改造消費方式,新增
HystrixService
類,並且注入RestTemplate
例項,然後,將在RibbonController中對RestTemplate 的使用遷移到hystrixService方法中,最後,在hystrixService上新增@HystrixCommand註解來指定回掉方法。
// HystrixService
@Service
public class HystrixService {
@Resource
RestTemplate restTemplate;
// 指定回掉方法是下面的hystrixCallback
@HystrixCommand(fallbackMethod = "hystrixCallBack")
public String hystrixService(){
return restTemplate.getForEntity("http://server-provider/hystrix",String.class).getBody();
}
public String hystrixCallBack(){
return "error";
}
}
服務提供者
的業務非常簡單,具體程式碼如下
@RequestMapping(value = "/hystrix", method = RequestMethod.GET)
public String hystrix(){
return "hystrix";
}
- 下面來驗證一下通過斷路器的回掉實現,重啟之前關閉的8081埠,恢復成為四個服務的狀態,並確保http://localhost:9000/ribbon-consumer/ 能夠提供服務,並且以輪詢的方式迴圈訪問8081 和 8082 埠的服務。此時斷開8081埠,發現頁面上展示的不再是 hystrix ,而是"error",而另一個服務是正常能夠列印。
三、斷路器優化
經過以上服務的搭建,相信你已經能夠搭建出來最基本的Hystrix熔斷器,並且實現了服務熔斷機制,下面就來對斷路器做一下簡單的優化,來模擬服務阻塞(長時間未響應)
的情況。
優化server-provider
程式碼如下:
@RequestMapping(value = "/hystrix", method = RequestMethod.GET)
public String hystrix() throws InterruptedException {
ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
// 讓執行緒等待幾秒鐘
int sleepTime = new Random().nextInt(3000);
Thread.sleep(sleepTime);
System.out.println("weak up!!!");
log.info("sleepTime = " + sleepTime);
return "hystrix";
}
依次啟動所有的服務,在主頁上訪問 http://localhost:9000/ribbon-consumer ,多次重新整理主頁,發現error 和 hystrix 是交替出現的,這是為何?
因為hystrix斷路器的預設超時時間
是2000毫秒,所以這裡採用了0 - 3000 的隨機數,也就是訪問請求在 0 -2000 毫秒內是不超時的,不會觸發斷路器,而> 2000 毫秒是超市的,預設會觸發斷路器。
參考資料:
http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html
https://www.cnblogs.com/vveiliang/p/6643907.html
《Spring Cloud 微服務實戰》