一、背景
分散式系統環境下,服務間類似依賴非常常見,一個業務呼叫通常依賴多個基礎服務。如下圖,對於同步呼叫,當庫存服務不可用時,商品服務請求執行緒被阻塞,當有大批量請求呼叫庫存服務時,最終可能導致整個商品服務資源耗盡,無法繼續對外提供服務。並且這種不可用可能沿請求呼叫鏈向上傳遞,這種現象被稱為雪崩效應。
二、雪崩效應常見場景
- 硬體故障:如伺服器當機,機房斷電,光纖被挖斷等。
- 流量激增:如異常流量,重試加大流量等。
- 快取穿透:一般發生在應用重啟,所有快取失效時,以及短時間內大量快取失效時。大量的快取不命中,使請求直擊後端服務,造成服務提供者超負荷執行,引起服務不可用。
- 程式BUG:如程式邏輯導致記憶體洩漏,JVM長時間FullGC等。
- 同步等待:服務間採用同步呼叫模式,同步等待造成的資源耗盡。
三、雪崩效應應對策略
針對造成雪崩效應的不同場景,可以使用不同的應對策略,沒有一種通用所有場景的策略,參考如下:
- 硬體故障:多機房容災、異地多活等。
- 流量激增:服務自動擴容、流量控制(限流、關閉重試)等。
- 快取穿透:快取預載入、快取非同步載入等。
- 程式BUG:修改程式bug、及時釋放資源等。
- 同步等待:資源隔離、MQ解耦、不可用服務呼叫快速失敗等。資源隔離通常指不同服務呼叫採用不同的執行緒池;不可用服務呼叫快速失敗一般通過熔斷器模式結合超時機制實現。
綜上所述,如果一個應用不能對來自依賴的故障進行隔離,那該應用本身就處在被拖垮的風險中。 因此,為了構建穩定、可靠的分散式系統,我們的服務應當具有自我保護能力,當依賴服務不可用時,當前服務啟動自我保護功能,從而避免發生雪崩效應。本文將重點介紹使用Hystrix解決同步等待的雪崩問題。
四、什麼是Hystrix
在分散式環境中,許多服務依賴項中的一些必然會失敗。Hystrix是一個庫,通過新增延遲容忍和容錯邏輯,幫助你控制這些分散式服務之間的互動。Hystrix通過隔離服務之間的訪問點、停止級聯失敗和提供回退選項來實現這一點,所有這些都可以提高系統的整體彈性。
Hystrix設計目標:
- 對來自依賴的延遲和故障進行防護和控制——這些依賴通常都是通過網路訪問的
- 阻止故障的連鎖反應
- 快速失敗並迅速恢復
- 回退並優雅降級
- 提供近實時的監控與告警
Hystrix遵循的設計原則:
- 防止任何單獨的依賴耗盡資源(執行緒)
- 過載立即切斷並快速失敗,防止排隊
- 儘可能提供回退以保護使用者免受故障
- 使用隔離技術(例如隔板,泳道和斷路器模式)來限制任何一個依賴的影響
- 通過近實時的指標,監控和告警,確保故障被及時發現
- 通過動態修改配置屬性,確保故障及時恢復
- 防止整個依賴客戶端執行失敗,而不僅僅是網路通訊
Hystrix如何實現這些設計目標?
- 使用命令模式將所有對外部服務(或依賴關係)的呼叫包裝在HystrixCommand或HystrixObservableCommand物件中,並將該物件放在單獨的執行緒中執行;
- 每個依賴都維護著一個執行緒池(或訊號量),執行緒池被耗盡則拒絕請求(而不是讓請求排隊)。
- 記錄請求成功,失敗,超時和執行緒拒絕。
- 服務錯誤百分比超過了閾值,熔斷器開關自動開啟,一段時間內停止對該服務的所有請求。
- 請求失敗,被拒絕,超時或熔斷時執行降級邏輯。
- 近實時地監控指標和配置的修改。
五、應用實戰
專案還是用以前的專案,之前在spring-cloud-user寫了一個UserController,在裡面呼叫了spring-cloud-service服務,下面把除了spring-cloud-service外的其它服務都開啟,然後訪問介面會發現報錯,看控制檯,控制檯報的是連線異常,這就是一個典型的通訊失敗的案例,在生產過程中如果訪問報一個這個頁面總是不那麼友好的,那麼怎麼解決這個通訊失敗呢。
為了解決這個問題就引入了hystrix,在服務呼叫端加入Hystrix包,因為我後面每個應用都會加入spring-cloud-api這個工程,為了減少後面應用重複導包,我將此包放入spring-cloud-api工程,並且要在呼叫端加入@EnableCircuitBreaker註解
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
然後就可以對服務進行降級和熔斷機制了,在UserController類方法中加入@HystrixCommand(fallbackMethod = "fallback")
然後再訪問瀏覽器,發現沒啥用,
這正好引出今天問題,怎麼觸發降級,剛剛配置的方法其實是回退,降級有三種方案:
5.1、熔斷觸發降級
熔斷的配置資訊可以在HystrixCommandProperties類中找到,這裡我只寫3個
然後再次發起訪問,發現發生了熔斷
這裡有個問題,我這裡不演示了,那就是熔斷開啟之後,後續的正常請求也無法傳送過去;說完了這個下面就看下,熔斷是如何觸發的的,又是怎麼恢復的;
1.如何觸發熔斷?"判斷閾值"
10s鍾之內,發起了20次請求,失敗率超過50%。 熔斷的恢復時間(熔斷5s)也就是說熔斷觸發了後,後續請求在5S內都不會發生到服務端 ,這就是熔斷的時間視窗說明.
2.熔斷會有一個自動恢復。
自支恢復的概念是,5S後請求會自動試著傳送到服務端,如果發現傳送通訊正常了,熔斷就會成為關閉狀態
5.2、請求超時觸發降級
在繼續寫時要說明一個情況那就是熔斷後超時是兩個概念,希望不要搞混了,超時是請求傳送中只是時間超了閾值
5.3、資源隔離觸發降級
隔離分為平臺隔離、部署隔離、業務隔離、 服務隔離、資源隔離;比喻說專案中的一個case,有一塊東西,是要用多執行緒做一些事情,小夥伴做專案的時候,沒有太留神,資源隔離,那塊程式碼,在遇到一些故障的情況下,每個執行緒在跑的時候,因為那個bug,直接就死迴圈了,導致那塊東西啟動了大量的執行緒,每個執行緒都死迴圈最終導致系統資源耗盡,崩潰,不工作,不可用,廢掉了;在系統中值得我們隔離的資源無非就幾種:CPU、記憶體、執行緒
訊號量隔離:
/** * 訊號量隔離實現 * 不會使用Hystrix管理的執行緒池處理請求。使用容器(Tomcat)的執行緒處理請求邏輯。 * 不涉及執行緒切換,資源排程,上下文的轉換等,相對效率高。 * 訊號量隔離也會啟動熔斷機制。如果請求併發數超標,則觸發熔斷,返回fallback資料。 * commandProperties - 命令配置,HystrixPropertiesManager中的常量或字串來配置。 * execution.isolation.strategy - 隔離的種類,可選值只有THREAD(執行緒池隔離)和 SEMAPHORE(訊號量隔離)。 * 預設是THREAD執行緒池隔離。 * 設定訊號量隔離後,執行緒池相關配置失效。 * execution.isolation.semaphore.maxConcurrentRequests - 訊號量最大併發數。預設 值是10。常見配置500~1000。 * 如果併發請求超過配置,其他請求進入fallback邏輯。 */ @HystrixCommand(fallbackMethod="semaphoreQuarantineFallback", commandProperties={ @HystrixProperty( name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value="SEMAPHORE"), // 訊號量隔離 @HystrixProperty( name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQU ESTS, value="100") // 訊號量最大併發數
執行緒池隔離
@HystrixCommand(groupKey="spring-cloud-service",
commandKey = "orders",
threadPoolKey="spring-cloud-service",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),//執行緒池大小
@HystrixProperty(name = "maxQueueSize", value = "100"),//最大佇列長度
@HystrixProperty(name = "keepAliveTimeMinutes", value ="2"),//執行緒存活時間
@HystrixProperty(name = "queueSizeRejectionThreshold", value= "15")//拒絕請求
},
fallbackMethod = "fallback")
六、OpenFeign的降級策略
前面講了hystrilx的降級方法,也在之前的篇幅中講了OpenFeign的應用,下面就以hystrix為基礎來講下在OpenFeign中是怎麼做到降級的進行對比一下,記得用OpenFeign要開啟降級和之前篇幅一樣要匯入包還有在啟動類上加上
@EnableFeignClients(basePackages = "com.ghy.*")註解,另外還要在呼叫者的配置檔案中加入下面的開啟配置
#開啟feign的支援,觸發降級的策略 feign: hystrix: enabled: true
這些搞定後,那就寫降級策略了
訪問瀏覽器
如果想在OpenFeign和hystrix一樣配置很多資訊的話是沒辦法用註解配置的,只能在配置檔案中配置,下面配置下超時的時間配置,在呼叫端的配置檔案中配置
#開啟feign的支援,觸發降級的策略 feign: hystrix: enabled: true hystrix: command: default: #全域性配置, feignclient#method(param) execution: timeout: enable: true #超時的開啟 isolation: thread: timeoutInMilliseconds: 3000 #設定超時時間 #設定ribbon的超時時間,而且ribbon的超時時間一定要大於hystrix,這樣才能讓hystrix生效 ribbon: ReadTimeout: 10000 ConnectTimeout: 10000
下面看下執行緒池隔離的配置
然後建立一個spring-cloud-hystrix專案進行監控,在spring-cloud-hystrix匯入以下包,然後spring-cloud-hystrix的啟動類上加上@EnableHystrixDashboard註解
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
配置檔案如下
server: port: 9095 eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/ #指向服務註冊中心的地址
訪問瀏覽器登入到了登入皮膚,被監測的服務一定要有下面的包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
並且被監控的物件要配置如下配置以不斷獲取監控資訊
management:
endpoints:
web:
exposure:
include: refresh,hystrix.stream
因為spring-cloud-user服務的埠號是8090,在瀏覽器上訪問 http://localhost:8090/actuator/hystrix.stream可以發現在不停的Ping我們的服務,其實這皮膚的作用就是將ping的結果收集然後在皮膚上展示
在皮膚上輸入要監控的服務資訊,如果要監控多個這皮膚也能做到,要做一個聚合操作
下面有三行資料,左邊第一行表示成功數,左邊第二行表示熔斷數量,左邊最後一行表示失敗數量
如果有興趣的朋友可以下個Apache JMeter壓測工具壓測玩下,我電腦換系統了工具丟了就懶得搞了,這工具也挺簡單的
git網址:https://github.com/ljx958720/Spring-Cloud-Hystrix.git