一、高可用系統(tǒng)架構(gòu):
- 資源隔離:讓你的系統(tǒng)里,某一塊東西,在故障的情況下,不會(huì)耗盡系統(tǒng)所有的資源,比如線程資源。
- 限流:高并發(fā)的流量涌入進(jìn)來(lái),比如說(shuō)突然間一秒鐘100萬(wàn)QPS,廢掉了,10萬(wàn)QPS進(jìn)入系統(tǒng),其他90萬(wàn)QPS被拒絕了。
- 熔斷:系統(tǒng)后端的一些依賴,出了一些故障,比如說(shuō)mysql掛掉了,每次請(qǐng)求都是報(bào)錯(cuò)的,熔斷了,后續(xù)的請(qǐng)求過(guò)來(lái)直接不接收了,拒絕訪問(wèn),10分鐘之后再嘗試去看看mysql恢復(fù)沒(méi)有
- 降級(jí):mysql掛了,系統(tǒng)發(fā)現(xiàn)了,自動(dòng)降級(jí),從內(nèi)存里存的少量數(shù)據(jù)中,去提取一些數(shù)據(jù)出來(lái)
- 運(yùn)維監(jiān)控:監(jiān)控+報(bào)警+優(yōu)化,各種異常的情況,有問(wèn)題就及時(shí)報(bào)警,優(yōu)化一些系統(tǒng)的配置和參數(shù),或者代碼。
二、雪崩效應(yīng)
分布式系統(tǒng)環(huán)境下,服務(wù)間類似依賴非常常見(jiàn),一個(gè)業(yè)務(wù)調(diào)用通常依賴多個(gè)基礎(chǔ)服務(wù)。
比如我們現(xiàn)在有3個(gè)業(yè)務(wù)調(diào)用分別是查詢訂單、查詢商品、查詢用戶,且這三個(gè)業(yè)務(wù)請(qǐng)求都是依賴第三方服務(wù)-訂單服務(wù)、商品服務(wù)、用戶服務(wù)。三個(gè)服務(wù)均是通過(guò)RPC調(diào)用。當(dāng)查詢訂單服務(wù),假如線程阻塞了,這個(gè)時(shí)候后續(xù)有大量的查詢訂單請(qǐng)求過(guò)來(lái),那么容器中的線程數(shù)量則會(huì)持續(xù)增加直致CPU資源耗盡到100%,整個(gè)服務(wù)對(duì)外不可用,并且這種可不用可能沿請(qǐng)求調(diào)用鏈向上傳遞,這種現(xiàn)象在集群環(huán)境下就是雪崩。如下圖


復(fù)雜分布式體系結(jié)構(gòu)中的應(yīng)用程序有幾十個(gè)依賴項(xiàng),每個(gè)依賴項(xiàng)在某個(gè)時(shí)候都不可避免地會(huì)失敗。如果主機(jī)應(yīng)用程序沒(méi)有從這些外部故障中隔離出來(lái),那么它就有可能與這些外部故障一起宕機(jī)。
-
當(dāng)一切正常時(shí),請(qǐng)求流可以是這樣的:
image.png - 當(dāng)許多后端系統(tǒng)之一成為潛在,它可以阻止整個(gè)用戶請(qǐng)求:

-
在高并發(fā)下,一個(gè)后端依賴項(xiàng)出現(xiàn)問(wèn)題,可能會(huì)導(dǎo)致所有服務(wù)器上的所有資源在幾秒鐘內(nèi)飽和。應(yīng)用程序中通過(guò)網(wǎng)絡(luò)或客戶機(jī)庫(kù)到達(dá)可能導(dǎo)致網(wǎng)絡(luò)請(qǐng)求的每個(gè)點(diǎn)都是潛在故障的來(lái)源。比故障更糟的是,這些應(yīng)用程序還可能導(dǎo)致服務(wù)之間的延遲增加,從而備份隊(duì)列、線程和其他系統(tǒng)資源,從而導(dǎo)致系統(tǒng)中出現(xiàn)更多級(jí)聯(lián)故障。
image.png
一個(gè)應(yīng)用中,任意一個(gè)點(diǎn)的不可用或者響應(yīng)延時(shí)都有可能造成服務(wù)不可用
更可怕的是,被hang住的請(qǐng)求會(huì)很快耗盡系統(tǒng)的資源,當(dāng)該類請(qǐng)求越來(lái)越多,占用的計(jì)算機(jī)資源越來(lái)越多的時(shí)候,會(huì)導(dǎo)致系統(tǒng)瓶頸出現(xiàn),造成其他的請(qǐng)求同樣不可用,最終導(dǎo)致業(yè)務(wù)系統(tǒng)崩潰
雪崩效應(yīng)常見(jiàn)場(chǎng)景
- 硬件故障:如服務(wù)器宕機(jī),機(jī)房斷電,光纖被挖斷等。
- 流量激增:如異常流量,重試加大流量等。
- 緩存穿透:一般發(fā)生在應(yīng)用重啟,所有緩存失效時(shí),以及短時(shí)間內(nèi)大量緩存失效時(shí)。大量的緩存不命中,使請(qǐng)求直擊后端服務(wù),造成服務(wù)提供者超負(fù)荷運(yùn)行,引起服務(wù)不可用。
- 程序BUG:如程序邏輯導(dǎo)致內(nèi)存泄漏,JVM長(zhǎng)時(shí)間FullGC等。
- 同步等待:服務(wù)間采用同步調(diào)用模式,同步等待造成的資源耗盡。
最終的結(jié)果就是一個(gè)服務(wù)不可用導(dǎo)致一系列服務(wù)的不可用,而往往這種后果往往無(wú)法預(yù)料的。
雪崩效應(yīng)應(yīng)對(duì)策略
- 硬件故障:多機(jī)房容災(zāi)、異地多活等。
- 流量激增:服務(wù)自動(dòng)擴(kuò)容、流量控制(限流、關(guān)閉重試)等。
- 緩存穿透:緩存預(yù)加載、緩存異步加載等。
- 程序BUG:修改程序bug、及時(shí)釋放資源等。
- 同步等待:資源隔離、MQ解耦、不可用服務(wù)調(diào)用快速失敗等。資源隔離通常指不同服務(wù)調(diào)用采用不同的線程池;不可用服務(wù)調(diào)用快速失敗一般通過(guò)超時(shí)機(jī)制,熔斷器以及熔斷后降級(jí)方法等方案實(shí)現(xiàn)。
三、Hystrix的產(chǎn)生
1. Hystrix的設(shè)計(jì)目標(biāo)
- 對(duì)來(lái)自依賴的延遲和故障進(jìn)行防護(hù)和控制——這些依賴通常都是通過(guò)網(wǎng)絡(luò)訪問(wèn)的
- 阻止故障的連鎖反應(yīng)
- 快速失敗并迅速恢復(fù)
- 回退并優(yōu)雅降級(jí)
- 提供近實(shí)時(shí)的監(jiān)控與告警
2. Hystrix遵循的設(shè)計(jì)原則
- 防止任何單獨(dú)的依賴耗盡資源(線程)
- 過(guò)載立即切斷并快速失敗,防止排隊(duì)
- 盡可能提供回退以保護(hù)用戶免受故障
- 使用隔離技術(shù)(例如隔板,泳道和斷路器模式)來(lái)限制任何一個(gè)依賴的影響
- 通過(guò)近實(shí)時(shí)的指標(biāo),監(jiān)控和告警,確保故障被及時(shí)發(fā)現(xiàn)
- 通過(guò)動(dòng)態(tài)修改配置屬性,確保故障及時(shí)恢復(fù)
- 防止整個(gè)依賴客戶端執(zhí)行失敗,而不僅僅是網(wǎng)絡(luò)通信
3. Hystrix如何實(shí)現(xiàn)這些設(shè)計(jì)目標(biāo)?
- 使用命令模式將所有對(duì)外部服務(wù)(或依賴關(guān)系)的調(diào)用包裝在HystrixCommand或HystrixObservableCommand對(duì)象中,并將該對(duì)象放在單獨(dú)的線程中執(zhí)行;
- 每個(gè)依賴都維護(hù)著一個(gè)線程池(或信號(hào)量),線程池被耗盡則拒絕請(qǐng)求(而不是讓請(qǐng)求排隊(duì))。
-
記錄請(qǐng)求成功,失敗,超時(shí)和線程拒絕。
服務(wù)錯(cuò)誤百分比超過(guò)了閾值,熔斷器開(kāi)關(guān)自動(dòng)打開(kāi),一段時(shí)間內(nèi)停止對(duì)該服務(wù)的所有請(qǐng)求。
請(qǐng)求失敗,被拒絕,超時(shí)或熔斷時(shí)執(zhí)行降級(jí)邏輯。
近實(shí)時(shí)地監(jiān)控指標(biāo)和配置的修改。
image.png

三、hystrix實(shí)現(xiàn)原理
Hystrix實(shí)現(xiàn)了熔斷機(jī)制、請(qǐng)求超時(shí)、限流降級(jí)、結(jié)果緩存、請(qǐng)求合并、統(tǒng)計(jì)、線程池隔離等功能共同保障應(yīng)用的穩(wěn)定性。
流程說(shuō)明:
1:每次調(diào)用創(chuàng)建一個(gè)新的HystrixCommand,把依賴調(diào)用封裝在run()方法中.
2:執(zhí)行execute()/queue做同步或異步調(diào)用.
3:判斷熔斷器(circuit-breaker)是否打開(kāi),如果打開(kāi)跳到步驟8,進(jìn)行降級(jí)策略,如果關(guān)閉進(jìn)入步驟.
4:判斷線程池/隊(duì)列/信號(hào)量是否跑滿,如果跑滿進(jìn)入降級(jí)步驟8,否則繼續(xù)后續(xù)步驟.
5:調(diào)用HystrixCommand的run方法.運(yùn)行依賴邏輯
5a:依賴邏輯調(diào)用超時(shí),進(jìn)入步驟8.
6:判斷邏輯是否調(diào)用成功
6a:返回成功調(diào)用結(jié)果
6b:調(diào)用出錯(cuò),進(jìn)入步驟8.
7:計(jì)算熔斷器狀態(tài),所有的運(yùn)行狀態(tài)(成功, 失敗, 拒絕,超時(shí))上報(bào)給熔斷器,用于統(tǒng)計(jì)從而判斷熔斷器狀態(tài).
8:getFallback()降級(jí)邏輯.
以下四種情況將觸發(fā)getFallback調(diào)用:
(1):run()方法拋出非HystrixBadRequestException異常。
(2):run()方法調(diào)用超時(shí)
(3):熔斷器開(kāi)啟攔截調(diào)用
(4):線程池/隊(duì)列/信號(hào)量是否跑滿
8a:沒(méi)有實(shí)現(xiàn)getFallback的Command將直接拋出異常
8b:fallback降級(jí)邏輯調(diào)用成功直接返回
8c:降級(jí)邏輯調(diào)用失敗拋出異常
9:返回執(zhí)行成功結(jié)果
image.png

1. 隔離

(1)線程池隔離模式:使用一個(gè)線程池來(lái)存儲(chǔ)當(dāng)前的請(qǐng)求,線程池對(duì)請(qǐng)求作處理,設(shè)置任務(wù)返回處理超時(shí)時(shí)間,堆積的請(qǐng)求堆積入線程池隊(duì)列。這種方式需要為每個(gè)依賴的服務(wù)申請(qǐng)線程池,有一定的資源消耗,好處是可以應(yīng)對(duì)突發(fā)流量(流量洪峰來(lái)臨時(shí),處理不完可將數(shù)據(jù)存儲(chǔ)到線程池隊(duì)里慢慢處理)
(2)信號(hào)量隔離模式:使用一個(gè)原子計(jì)數(shù)器(或信號(hào)量)來(lái)記錄當(dāng)前有多少個(gè)線程在運(yùn)行,請(qǐng)求來(lái)先判斷計(jì)數(shù)器的數(shù)值,若超過(guò)設(shè)置的最大線程個(gè)數(shù)則丟棄改類型的新請(qǐng)求,若不超過(guò)則執(zhí)行計(jì)數(shù)操作請(qǐng)求來(lái)計(jì)數(shù)器+1,請(qǐng)求返回計(jì)數(shù)器-1。這種方式是嚴(yán)格的控制線程且立即返回模式,無(wú)法應(yīng)對(duì)突發(fā)流量(流量洪峰來(lái)臨時(shí),處理的線程超過(guò)數(shù)量,其他的請(qǐng)求會(huì)直接返回,不繼續(xù)去請(qǐng)求依賴的服務(wù))
區(qū)別(兩種隔離方式只能選其一):
| 對(duì)比角度 | 線程池隔離 | 信號(hào)量隔離 |
|---|---|---|
| 線程 | 與調(diào)用線程非相同線程 | 與調(diào)用線程相同(jetty線程) |
| 開(kāi)銷 | 排隊(duì)、調(diào)度、上下文開(kāi)銷等 | 無(wú)線程切換,開(kāi)銷低 |
| 異步 | 支持 | 不支持 |
| 并發(fā)支持 | 支持(最大線程池大?。?/td> | 支持(最大信號(hào)量上限) |
線程池和信號(hào)量都支持熔斷和限流。相比線程池,信號(hào)量不需要線程切換,因此避免了不必要的開(kāi)銷。但是信號(hào)量不支持異步,也不支持超時(shí),也就是說(shuō)當(dāng)所請(qǐng)求的服務(wù)不可用時(shí),信號(hào)量會(huì)控制超過(guò)限制的請(qǐng)求立即返回,但是已經(jīng)持有信號(hào)量的線程只能等待服務(wù)響應(yīng)或從超時(shí)中返回,即可能出現(xiàn)長(zhǎng)時(shí)間等待。線程池模式下,當(dāng)超過(guò)指定時(shí)間未響應(yīng)的服務(wù),Hystrix會(huì)通過(guò)響應(yīng)中斷的方式通知線程立即結(jié)束并返回。
2. 融斷
正常狀態(tài)下,電路處于關(guān)閉狀態(tài)(Closed),如果調(diào)用持續(xù)出錯(cuò)或者超時(shí),電路被打開(kāi)進(jìn)入熔斷狀態(tài)(Open),后續(xù)一段時(shí)間內(nèi)的所有調(diào)用都會(huì)被拒絕(Fail Fast),一段時(shí)間以后,保護(hù)器會(huì)嘗試進(jìn)入半熔斷狀態(tài)(Half-Open),允許少量請(qǐng)求進(jìn)來(lái)嘗試,如果調(diào)用仍然失敗,則回到熔斷狀態(tài),如果調(diào)用成功,則回到電路閉合狀態(tài);

3. 降級(jí)
可能大家會(huì)混淆“融斷”和“降級(jí)”兩個(gè)概念。
在股票市場(chǎng),熔斷這個(gè)詞大家都不陌生,是指當(dāng)股指波幅達(dá)到某個(gè)點(diǎn)后,交易所為控制風(fēng)險(xiǎn)采取的暫停交易措施。相應(yīng)的,服務(wù)熔斷一般是指軟件系統(tǒng)中,由于某些原因使得服務(wù)出現(xiàn)了過(guò)載現(xiàn)象,為防止造成整個(gè)系統(tǒng)故障,從而采用的一種保護(hù)措施,所以很多地方把熔斷亦稱為過(guò)載保護(hù)。
大家都見(jiàn)過(guò)女生旅行吧,大號(hào)的旅行箱是必備物,平常走走近處綽綽有余,但一旦出個(gè)遠(yuǎn)門,再大的箱子都白搭了,怎么辦呢?常見(jiàn)的情景就是把物品拿出來(lái)分分堆,比了又比,最后一些非必需品的就忍痛放下了,等到下次箱子夠用了,再帶上用一用。而服務(wù)降級(jí),就是這么回事,整體資源快不夠了,忍痛將某些服務(wù)先關(guān)掉,待渡過(guò)難關(guān),再開(kāi)啟回來(lái)。
二者的目標(biāo)是一致的,目的都是保證上游服務(wù)的穩(wěn)定性。但其關(guān)注的重點(diǎn)并不一樣,融斷對(duì)下層依賴的服務(wù)并不級(jí)(或者說(shuō)孰輕孰重),一旦產(chǎn)生故障就斷掉;而降級(jí)需要對(duì)下層依賴的業(yè)務(wù)分級(jí),把產(chǎn)生故障的丟了,換一個(gè)輕量級(jí)的方案,是一種退而求其次的方法。
根據(jù)業(yè)務(wù)場(chǎng)景的不同,一般采用以下兩種模式:
第一種(最常用)如果服務(wù)失敗,則我們通過(guò)fallback進(jìn)行降級(jí),返回靜態(tài)值。

第二種采用服務(wù)級(jí)聯(lián)的模式,如果第一個(gè)服務(wù)失敗,則調(diào)用備用服務(wù),例如失敗重試或者訪問(wèn)緩存失敗再去取數(shù)據(jù)庫(kù)。服務(wù)級(jí)聯(lián)的目的則是盡最大努力保證返回?cái)?shù)據(jù)的成功性,但如果考慮不充分,則有可能導(dǎo)致級(jí)聯(lián)的服務(wù)崩潰(比如,緩存失敗了,把全部流量打到數(shù)據(jù)庫(kù),瞬間導(dǎo)致數(shù)據(jù)庫(kù)掛掉)。因此級(jí)聯(lián)模式,也要慎用,增加了管理的難度。

4. 緩存
不建議使用,對(duì)問(wèn)題排查會(huì)造成很大的困擾
四、使用Hystrix
1. 添加maven依賴:
<!--添加Hystrix依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
2. 修改啟動(dòng)類
在主函數(shù)上添加注解@EnableCircuitBreaker,啟動(dòng)Hystrix
Circuit n. 環(huán)行; 環(huán)行路線; 電路; 線路; 巡回賽;
@SpringBootApplication
@EnableEurekaClient//聲明為eureka 客戶端
@EnableFeignClients //啟動(dòng)Feign
@EnableCircuitBreaker //啟動(dòng)Hystrix
public class FuturecloudHystrixApplication
{
public static void main( String[] args )
{
SpringApplication.run(FuturecloudHystrixApplication.class,args);
}
}
3. Feign 接口
(可以不使用)
@FeignClient(name = "futurecloud-user") //通過(guò)注解指定依賴服務(wù)
public interface FeignClientInterfaces {
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
User getUserById(@PathVariable("id") Long id);
@GetMapping("/getUser/{id}")
User getUser(@PathVariable("id") Long id);
@RequestMapping(value = "/find/user/{id}",method = RequestMethod.POST)
User findUserById(@PathVariable("id") Long id);
}
4、使用Hystix熔斷器
@RestController
public class FuturecloudHystrixController {
@Autowired
private FeignClientInterfaces feignClientInterfaces;
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public User getUserById(@PathVariable("id") Long id){
User user = feignClientInterfaces.getUserById(id);
return user;
}
@GetMapping("/getUser/{id}")
public User getUser(@PathVariable("id") Long id){
User user = feignClientInterfaces.getUser(id);
return user;
}
/**
* 使用Hystrix熔斷器,當(dāng)調(diào)用findUserById失敗后,調(diào)用forbackFindUserById方法
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "forbackFindUserById")
@RequestMapping(value = "/find/user/{id}",method = RequestMethod.GET)
public User findUserById(@PathVariable("id") Long id){
User user = feignClientInterfaces.findUserById(id);
int a = 4/0;
return user;
}
public User forbackFindUserById(Long id){
User user = new User();
user.setId(-400L);
user.setUsername("hystrix-fallback");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838384381");
user.setCreateDate(new Date());
return user;
}
}
使用這個(gè)注解@HystrixCommand來(lái)定義Hystrix的熔斷器,查看HystrixCommand源碼,這個(gè)注解有10個(gè)屬性,
我們使用fallbackMethod屬性,來(lái)指定接口調(diào)用失敗后執(zhí)行的方法。
| *********************************************************************************************************************************************************************** | 說(shuō)明 |
|---|---|
| commandKey | 配置全局唯一標(biāo)識(shí)服務(wù)的名稱,比如,庫(kù)存系統(tǒng)有一個(gè)獲取庫(kù)存服務(wù),那么就可以為這個(gè)服務(wù)起一個(gè)名字來(lái)唯一識(shí)別該服務(wù),如果不配置,則默認(rèn)是@HystrixCommand注解修飾的函數(shù)的函數(shù)名。 |
| groupKey | 一個(gè)比較重要的注解,配置全局唯一標(biāo)識(shí)服務(wù)分組的名稱,比如,庫(kù)存系統(tǒng)就是一個(gè)服務(wù)分組。通過(guò)設(shè)置分組,Hystrix會(huì)根據(jù)組來(lái)組織和統(tǒng)計(jì)命令的告、儀表盤等信息。Hystrix命令默認(rèn)的線程劃分也是根據(jù)命令組來(lái)實(shí)現(xiàn)。默認(rèn)情況下,Hystrix會(huì)讓相同組名的命令使用同一個(gè)線程池,所以我們需要在創(chuàng)建Hystrix命令時(shí)為其指定命令組來(lái)實(shí)現(xiàn)默認(rèn)的線程池劃分。此外,Hystrix還提供了通過(guò)設(shè)置threadPoolKey來(lái)對(duì)線程池進(jìn)行設(shè)置。建議最好設(shè)置該參數(shù),使用threadPoolKey來(lái)控制線程池組。 |
| threadPoolKey | 對(duì)線程池進(jìn)行設(shè)定,細(xì)粒度的配置,相當(dāng)于對(duì)單個(gè)服務(wù)的線程池信息進(jìn)行設(shè)置,也可多個(gè)服務(wù)設(shè)置同一個(gè)threadPoolKey構(gòu)成線程組。 |
| fallbackMethod | @HystrixCommand注解修飾的函數(shù)的回調(diào)函數(shù),@HystrixCommand修飾的函數(shù)必須和這個(gè)回調(diào)函數(shù)定義在同一個(gè)類中,因?yàn)槎x在了同一個(gè)類中,所以fackback method可以是public/private均可。 |
| commandProperties | 配置該命令的一些參數(shù),如executionIsolationStrategy配置執(zhí)行隔離策略,默認(rèn)是使用線程隔離,此處我們配置為THREAD,即線程池隔離。參見(jiàn):com.netflix.hystrix.HystrixCommandProperties中各個(gè)參數(shù)的定義。 |
| threadPoolProperties | 線程池相關(guān)參數(shù)設(shè)置,具體可以設(shè)置哪些參數(shù)請(qǐng)見(jiàn):com.netflix.hystrix.HystrixThreadPoolProperties |
| ignoreExceptions | 調(diào)用服務(wù)時(shí),除了HystrixBadRequestException之外,其他@HystrixCommand修飾的函數(shù)拋出的異常均會(huì)被Hystrix認(rèn)為命令執(zhí)行失敗而觸發(fā)服務(wù)降級(jí)的處理邏輯(調(diào)用fallbackMethod指定的回調(diào)函數(shù)),所以當(dāng)需要在命令執(zhí)行中拋出不觸發(fā)降級(jí)的異常時(shí)來(lái)使用它,通過(guò)這個(gè)參數(shù)指定,哪些異常拋出時(shí)不觸發(fā)降級(jí)(不去調(diào)用fallbackMethod),而是將異常向上拋出。 |
| observableExecutionMode | 定義hystrix observable command的模式; |
| raiseHystrixExceptions | 任何不可忽略的異常都包含在HystrixRuntimeException中; |
| defaultFallback | 默認(rèn)的回調(diào)函數(shù),該函數(shù)的函數(shù)體不能有入?yún)ⅲ祷刂殿愋团c@HystrixCommand修飾的函數(shù)體的返回值一致。如果指定了fallbackMethod,則fallbackMethod優(yōu)先級(jí)更高。 |
5. application.yml
server:
port: 8906
spring:
application:
name: futurecloud-hystrix
#將此服務(wù)注冊(cè)到eureka 服務(wù)上
eureka:
client:
serviceUrl:
defaultZone: http://user:123@localhost:10000/eureka
instance:
prefer-ip-address: true #將注冊(cè)到eureka服務(wù)的實(shí)例使用ip地址
#Feign日志的配置
logging:
level:
com.futurecloud.feign.interfaces.CustomFeignClient: DEBUG
啟動(dòng)eureka 服務(wù)futurecloud-service,
啟動(dòng)服務(wù)提供者服務(wù)futurecloud-user,
啟動(dòng)此服務(wù)futurecloud-hystrix
訪問(wèn)http://localhost:8906/order/20 ,進(jìn)入到了@HystrixCommand指定的方法forbackFindUserById
五、Feign 使用Hystrix
在Feign 接口中定義了依賴服務(wù)的接口,當(dāng)Feign調(diào)用依賴服務(wù)的接口失敗時(shí)怎么辦?
我們也可以在Feign中利用Hystrix的功能,當(dāng)Feign 調(diào)用依賴服務(wù)的接口失敗時(shí),通過(guò)Hystrix調(diào)用指定的方法,Spring Cloud默認(rèn)為Feign整合了Hystrix,但在Spring Cloud 基于Spring boot 2.x的版本中,默認(rèn)是Feign是關(guān)閉Hystrix的。
那么Feign與Hystrix怎么配合使用呢?
1、創(chuàng)建項(xiàng)目futurecloud-feign-hystrix
注意:不需要添加依賴spring-cloud-starter-netflix-hystrix,Spring Cloud默認(rèn)已為Feign整合了Hystrix。spring boot 主函數(shù)也不需要添加注解@EnableCircuitBreaker 來(lái)開(kāi)啟Hystrix。主函數(shù)類如下:
@SpringBootApplication
@EnableEurekaClient //聲明為eureka 客戶端
@EnableFeignClients //啟動(dòng)Feign
public class FuturecloudFeignHystrixApplication
{
public static void main( String[] args )
{
SpringApplication.run(FuturecloudFeignHystrixApplication.class,args);
}
}
2. 開(kāi)啟Hystrix在application.yml中配置
#開(kāi)啟hystrix配置
feign:
hystrix:
enabled: true
2、Feign 使用fallback 方式進(jìn)行Hystrix的回退
在定義Feign接口時(shí),我們可以指定Feign接口中的方法調(diào)用失敗后的一個(gè)回調(diào)類,這個(gè)類實(shí)現(xiàn)此Feign接口,
Feign接口代碼如下:
@FeignClient(name = "futurecloud-user",fallback = FeignHystrixFallback.class) //指定依賴服務(wù)Hystrixd的回調(diào)類
public interface FeignInterface {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
@GetMapping("/getObject")
User getObject(User user);
@GetMapping("/find//user/{id}")
User findUser(@PathVariable("id") Long id);
}
FeignHystrixFallback 代碼如下:
@Component
public class FeignHystrixFallback implements FeignInterface{
@Override
public User getUser(Long id) {
User user = new User();
user.setId(-401L);
user.setUsername("01-feignHystrixFallback");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838389438");
user.setCreateDate(new Date());
return user;
}
@Override
public User getObject(User user) {
user = new User();
user.setId(-402L);
user.setUsername("02-feignHystrixFallback");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838389438");
user.setCreateDate(new Date());
return user;
}
@Override
public User findUser(Long id) {
User user = new User();
user.setId(-403L);
user.setUsername("03-feignHystrixFallback");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838389438");
user.setCreateDate(new Date());
return user;
}
}
注意:要添加注解@Component,否則Spring boot 找不到這個(gè)bean
啟動(dòng)eureka 服務(wù)futurecloud-service,
啟動(dòng)服務(wù)提供者服務(wù)futurecloud-user,
啟動(dòng)此服務(wù)futurecloud-feign-hystrix
訪問(wèn)http://localhost:8907/order/20 ,成功訪問(wèn)
將服務(wù)futurecloud-user關(guān)掉,再一次訪問(wèn)http://localhost:8907/order/20,調(diào)用了fallback指定的FeignHystrixFallback中的方法。
3. Feign 使用fallbackFactory方式進(jìn)行Hystrix的回退
在Feign接口中指定使用fallbackFactory工廠類,其實(shí)就是實(shí)現(xiàn)Feign接口的匿名類
Feign接口定義如下:
@FeignClient(name = "futurecloud-user",fallbackFactory = FeignHystrixFallbackFactory.class)
public interface FeignInterface {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
@GetMapping("/getObject")
User getObject(User user);
@GetMapping("/find//user/{id}")
User findUser(@PathVariable("id") Long id);
}
FeignHystrixFallbackFactory代碼如下:
@Component
public class FeignHystrixFallbackFactory implements FallbackFactory<FeignInterface>{
@Override
public FeignInterface create(Throwable throwable) {
return new FeignInterface() {
@Override
public User getUser(Long id) {
User user = new User();
user.setId(-404L);
user.setUsername("01-feignHystrixFallbackFactory");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838389438");
user.setCreateDate(new Date());
return user;
}
@Override
public User getObject(User user) {
user = new User();
user.setId(-405L);
user.setUsername("02-feignHystrixFallbackFactory");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838389438");
user.setCreateDate(new Date());
return user;
}
@Override
public User findUser(Long id) {
User user = new User();
user.setId(-406L);
user.setUsername("03-feignHystrixFallbackFactory");
user.setMail("hystrix-fallback@sina.com");
user.setPhone("13838389438");
user.setCreateDate(new Date());
return user;
}
};
}
}
其余配置與fallback一致。
啟動(dòng)eureka 服務(wù)futurecloud-service,
啟動(dòng)服務(wù)提供者服務(wù)futurecloud-user,
啟動(dòng)此服務(wù)futurecloud-feign-hystrix
訪問(wèn)http://localhost:8907/order/20 ,成功訪問(wèn)
將服務(wù)futurecloud-user關(guān)掉,再一次訪問(wèn)http://localhost:8907/order/20,調(diào)用了fallbackFactory指定的FeignHystrixFallbackFactory中的方法。
六、Hystrix的健康監(jiān)測(cè)
1. actuator
①添加依賴
<!--添加認(rèn)證監(jiān)控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
②瀏覽器訪問(wèn) http://192.168.215.2:8908/actuator/health
就能顯示Hystrix開(kāi)啟狀態(tài)信息。
有人說(shuō),Spring boot 2.x 使用了endpoint,導(dǎo)致/actuator/health,/actuator/hystrix.stream都無(wú)法訪問(wèn),報(bào)404錯(cuò)誤。
我用的Spring Cloud 版本是Finchley.RELEASE,但是我沒(méi)遇上,如果有人遇上,可以試試以下三種方法。而我將以下三種方法注釋掉,一樣能顯示Hystrix的/actuator/health,/actuator/hystrix.stream信息
設(shè)置讓endpoint允許顯示/actuator/health,/actuator/hystrix.stream信息的方法如下:
方式一:在application.yml中添加如下配置:
management:
endpoint:
health:
show-details: always #允許顯示/actuator/health信息
management:
endpoints:
web:
exposure:
include: ["hystrix-stream"] #允許顯示/actuator/hystrix.stream信息
management:
endpoints:
web:
exposure:
include: '*' #允許顯示所有信息,包括/actuator/health,/actuator/hystrix.stream
解決方式二:在Hystrix監(jiān)控項(xiàng)目的啟動(dòng)類中添加方法,設(shè)置允許顯示的信息
/**
* 配置Hystrix ,使其顯示健康監(jiān)測(cè)信息
* @return
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/health","/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
解決方式三:在application.yml中打開(kāi)Feign 的Hystrix
*************************************************************
#開(kāi)啟hystrix配置
feign:
hystrix:
enabled: true
2. Hystrix-DashBoard的使用
即前面說(shuō)的/hystrix.stream 詳細(xì)信息的圖形化。
①引入依賴
<!--添加hystrix dashboard依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
②在主函數(shù)上添加配置@EnableHystrixDashboard,開(kāi)啟Hystrix-DashBoard
@SpringBootApplication
@EnableHystrixDashboard //開(kāi)啟Hystrix-DashBoard
public class FuturecloudHystrixDashBoardApplication
{
public static void main( String[] args )
{
SpringApplication.run(FuturecloudHystrixDashBoardApplication.class,args);
}
}
③配置文件增加endpoint
management:
endpoints:
web:
exposure:
include: "*"
④訪問(wèn)入口
http://localhost:8781/hystrix
Hystrix Dashboard輸入: http://localhost:8781/actuator/hystrix.stream
七. 補(bǔ)充
一、hystrix參數(shù)使用方法
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/movie/{id}")
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
@HystrixProperty(name = "execution.timeout.enabled", value = "false")},fallbackMethod = "findByIdFallback")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class);
}
/**
* fallback方法
* @param id
* @return
*/
public User findByIdFallback(Long id) {
User user = new User();
user.setId(5L);
return user;
}
}
或者 application.yml
feign:
hystrix:
enabled: true //feign開(kāi)啟hystrix支持【這里開(kāi)啟了,啟動(dòng)類就不用了】
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 500 //線程超時(shí),調(diào)用Fallback方法
circuitBreaker:
requestVolumeThreshold: 3 //10秒內(nèi)出現(xiàn)3個(gè)以上請(qǐng)求(已臨近閥值),并且出錯(cuò)率在50%以上,開(kāi)啟斷路器.斷開(kāi)服務(wù),調(diào)用Fallback方法
二、hystrix參數(shù)如下
| **********參數(shù)類型********** | 參數(shù)名 | 默認(rèn)值 | 說(shuō)明 |
|---|---|---|---|
| command配置 | executionIsolationStrategy | ExecutionIsolationStrategy.THREAD | 信號(hào)隔離或線程隔離,默認(rèn):采用線程隔離, |
| executionIsolationThreadTimeoutInMillisecond | 1s | 隔離時(shí)間大,即多長(zhǎng)時(shí)間后進(jìn)行重試 | |
| executionIsolationSemaphoreMaxConcurrentRequests | 10 | 使用信號(hào)量隔離時(shí),命令調(diào)用最大的并發(fā)數(shù),默認(rèn):10 | |
| fallbackIsolationSemaphoreMaxConcurrentRequests | 10 | 使用信號(hào)量隔離時(shí),命令fallback(降級(jí))調(diào)用最大的并發(fā)數(shù),默認(rèn):10 | |
| fallbackEnabled | true | 是否開(kāi)啟fallback降級(jí)策略 | |
| executionIsolationThreadInterruptOnTimeout | true | 使用線程隔離時(shí),是否對(duì)命令執(zhí)行超時(shí)的線程調(diào)用中斷(Thread.interrupt())操作 | |
| metricsRollingStatisticalWindowInMilliseconds | 10000ms | 統(tǒng)計(jì)滾動(dòng)的時(shí)間窗口,默認(rèn):10s | |
| metricsRollingStatisticalWindowBuckets | 10 | 統(tǒng)計(jì)窗口的Buckets的數(shù)量,默認(rèn):10個(gè) | |
| metricsRollingPercentileEnabled | true | 是否開(kāi)啟監(jiān)控統(tǒng)計(jì)功能,默認(rèn):true | |
| requestLogEnabled | true | 是否開(kāi)啟請(qǐng)求日志 | |
| requestCacheEnabled | true | 是否開(kāi)啟請(qǐng)求緩存 | |
| 熔斷器配置 | circuitBreakerRequestVolumeThreshold | 20 | 主要用在小流量 |
| circuitBreakerSleepWindowInMilliseconds | 5000ms | 熔斷器默認(rèn)工作時(shí)間,默認(rèn):5秒.熔斷器中斷請(qǐng)求5秒后會(huì)進(jìn)入半打開(kāi)狀態(tài),放部分流量過(guò)去重試 | |
| circuitBreakerEnabled | true | 是否啟用熔斷器,默認(rèn)true. 啟動(dòng) | |
| circuitBreakerErrorThresholdPercentage | 50 | 默認(rèn):50%。當(dāng)出錯(cuò)率超過(guò)50%后熔斷器啟動(dòng) | |
| circuitBreakerForceOpen | false | 是否強(qiáng)制開(kāi)啟熔斷器阻斷所有請(qǐng)求,默認(rèn):false,不開(kāi)啟 | |
| circuitBreakerForceClosed | false | 是否允許熔斷器忽略錯(cuò)誤,默認(rèn)false, 不開(kāi)啟 | |
| 線程池配置 | HystrixThreadPoolProperties.Setter().withCoreSize(int value) | 10 | 配置線程池大小,默認(rèn)值10個(gè) |
| HystrixThreadPoolProperties.Setter().withMaxQueueSize(int value) | -1 | 配置線程值等待隊(duì)列長(zhǎng)度 |
詳細(xì)參數(shù)
hystrix.command.default和hystrix.threadpool.default中的default為默認(rèn)CommandKey
Command Properties
Execution相關(guān)的屬性的配置:
hystrix.command.default.execution.isolation.strategy 隔離策略,默認(rèn)是Thread, 可選Thread|Semaphore
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令執(zhí)行超時(shí)時(shí)間,默認(rèn)1000ms
hystrix.command.default.execution.timeout.enabled 執(zhí)行是否啟用超時(shí),默認(rèn)啟用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 發(fā)生超時(shí)是是否中斷,默認(rèn)true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并發(fā)請(qǐng)求數(shù),默認(rèn)10,該參數(shù)當(dāng)使用ExecutionIsolationStrategy.SEMAPHORE策略時(shí)才有效。如果達(dá)到最大并發(fā)請(qǐng)求數(shù),請(qǐng)求會(huì)被拒絕。理論上選擇semaphore size的原則和選擇thread size一致,但選用semaphore時(shí)每次執(zhí)行的單元要比較小且執(zhí)行速度快(ms級(jí)別),否則的話應(yīng)該用thread。semaphore應(yīng)該占整個(gè)容器(tomcat)的線程池的一小部分。
Fallback相關(guān)的屬性
這些參數(shù)可以應(yīng)用于Hystrix的THREAD和SEMAPHORE策略
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并發(fā)數(shù)達(dá)到該設(shè)置值,請(qǐng)求會(huì)被拒絕和拋出異常并且fallback不會(huì)被調(diào)用。默認(rèn)10
hystrix.command.default.fallback.enabled 當(dāng)執(zhí)行失敗或者請(qǐng)求被拒絕,是否會(huì)嘗試調(diào)用hystrixCommand.getFallback() 。默認(rèn)true
Circuit Breaker相關(guān)的屬性
hystrix.command.default.circuitBreaker.enabled 用來(lái)跟蹤circuit的健康性,如果未達(dá)標(biāo)則讓request短路。默認(rèn)true
hystrix.command.default.circuitBreaker.requestVolumeThreshold 一個(gè)rolling window內(nèi)最小的請(qǐng)求數(shù)。如果設(shè)為20,那么當(dāng)一個(gè)rolling window的時(shí)間內(nèi)(比如說(shuō)1個(gè)rolling window是10秒)收到19個(gè)請(qǐng)求,即使19個(gè)請(qǐng)求都失敗,也不會(huì)觸發(fā)circuit break。默認(rèn)20
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 觸發(fā)短路的時(shí)間值,當(dāng)該值設(shè)為5000時(shí),則當(dāng)觸發(fā)circuit break后的5000毫秒內(nèi)都會(huì)拒絕request,也就是5000毫秒后才會(huì)關(guān)閉circuit。默認(rèn)5000
hystrix.command.default.circuitBreaker.errorThresholdPercentage錯(cuò)誤比率閥值,如果錯(cuò)誤率>=該值,circuit會(huì)被打開(kāi),并短路所有請(qǐng)求觸發(fā)fallback。默認(rèn)50
hystrix.command.default.circuitBreaker.forceOpen 強(qiáng)制打開(kāi)熔斷器,如果打開(kāi)這個(gè)開(kāi)關(guān),那么拒絕所有request,默認(rèn)false
hystrix.command.default.circuitBreaker.forceClosed 強(qiáng)制關(guān)閉熔斷器 如果這個(gè)開(kāi)關(guān)打開(kāi),circuit將一直關(guān)閉且忽略circuitBreaker.errorThresholdPercentageMetrics相關(guān)參數(shù)
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 設(shè)置統(tǒng)計(jì)的時(shí)間窗口值的,毫秒值,circuit break 的打開(kāi)會(huì)根據(jù)1個(gè)rolling window的統(tǒng)計(jì)來(lái)計(jì)算。若rolling window被設(shè)為10000毫秒,則rolling window會(huì)被分成n個(gè)buckets,每個(gè)bucket包含success,failure,timeout,rejection的次數(shù)的統(tǒng)計(jì)信息。默認(rèn)10000
hystrix.command.default.metrics.rollingStats.numBuckets 設(shè)置一個(gè)rolling window被劃分的數(shù)量,若numBuckets=10,rolling window=10000,那么一個(gè)bucket的時(shí)間即1秒。必須符合rolling window % numberBuckets == 0。默認(rèn)10
hystrix.command.default.metrics.rollingPercentile.enabled 執(zhí)行時(shí)是否enable指標(biāo)的計(jì)算和跟蹤,默認(rèn)true
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 設(shè)置rolling percentile window的時(shí)間,默認(rèn)60000
hystrix.command.default.metrics.rollingPercentile.numBuckets 設(shè)置rolling percentile window的numberBuckets。邏輯同上。默認(rèn)6
hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若這10s里有500次執(zhí)行,只有最后100次執(zhí)行會(huì)被統(tǒng)計(jì)到bucket里去。增加該值會(huì)增加內(nèi)存開(kāi)銷以及排序的開(kāi)銷。默認(rèn)100
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 記錄health 快照(用來(lái)統(tǒng)計(jì)成功和錯(cuò)誤綠)的間隔,默認(rèn)500ms
Request Context 相關(guān)參數(shù)
hystrix.command.default.requestCache.enabled 默認(rèn)true,需要重載getCacheKey(),返回null時(shí)不緩存
hystrix.command.default.requestLog.enabled 記錄日志到HystrixRequestLog,默認(rèn)true
Collapser Properties 相關(guān)參數(shù)
hystrix.collapser.default.maxRequestsInBatch 單次批處理的最大請(qǐng)求數(shù),達(dá)到該數(shù)量觸發(fā)批處理,默認(rèn)Integer.MAX_VALUE
hystrix.collapser.default.timerDelayInMilliseconds 觸發(fā)批處理的延遲,也可以為創(chuàng)建批處理的時(shí)間+該值,默認(rèn)10
hystrix.collapser.default.requestCache.enabled 是否對(duì)HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默認(rèn)true
ThreadPool 相關(guān)參數(shù)
線程數(shù)默認(rèn)值10適用于大部分情況(有時(shí)可以設(shè)置得更?。绻枰O(shè)置得更大,那有個(gè)基本得公式可以follow:
requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room
每秒最大支撐的請(qǐng)求數(shù) (99%平均響應(yīng)時(shí)間 + 緩存值) 比如:每秒能處理1000個(gè)請(qǐng)求,99%的請(qǐng)求響應(yīng)時(shí)間是60ms,那么公式是:(0.060+0.012)基本得原則時(shí)保持線程池盡可能小,他主要是為了釋放壓力,防止資源被阻塞。當(dāng)一切都是正常的時(shí)候,線程池一般僅會(huì)有1到2個(gè)線程激活來(lái)提供服務(wù)
hystrix.threadpool.default.coreSize 并發(fā)執(zhí)行的最大線程數(shù),默認(rèn)10
hystrix.threadpool.default.maxQueueSize BlockingQueue的最大隊(duì)列數(shù),當(dāng)設(shè)為-1,會(huì)使用SynchronousQueue,值為正時(shí)使用LinkedBlcokingQueue。該設(shè)置只會(huì)在初始化時(shí)有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默認(rèn)-1。
hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize沒(méi)有達(dá)到,達(dá)到queueSizeRejectionThreshold該值后,請(qǐng)求也會(huì)被拒絕。因?yàn)閙axQueueSize不能被動(dòng)態(tài)修改,這個(gè)參數(shù)將允許我們動(dòng)態(tài)設(shè)置該值。if maxQueueSize == -1,該字段將不起作用
hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize設(shè)成一樣(默認(rèn)實(shí)現(xiàn))該設(shè)置無(wú)效。如果通過(guò)plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定義實(shí)現(xiàn),該設(shè)置才有用,默認(rèn)1.
hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 線程池統(tǒng)計(jì)指標(biāo)的時(shí)間,默認(rèn)10000
hystrix.threadpool.default.metrics.rollingStats.numBuckets 將rolling window劃分為n個(gè)buckets,默認(rèn)10



