SpringCloud中Hystrix容錯(cuò)保護(hù)原理及配置,看它就夠了!
作者:kosamino
cnblogs.com/jing99/p/11625306.html
1 什么是災(zāi)難性雪崩效應(yīng)?
如下圖的過程所示,災(zāi)難性雪崩形成原因就大致如此:

造成災(zāi)難性雪崩效應(yīng)的原因,可以簡(jiǎn)單歸結(jié)為下述三種:
服務(wù)提供者不可用。如:硬件故障、程序BUG、緩存擊穿、并發(fā)請(qǐng)求量過大等。 重試加大流量。如:用戶重試、代碼重試邏輯等。 服務(wù)調(diào)用者不可用。如:同步請(qǐng)求阻塞造成的資源耗盡等。
雪崩效應(yīng)最終的結(jié)果就是:服務(wù)鏈條中的某一個(gè)服務(wù)不可用,導(dǎo)致一系列的服務(wù)不可用,最終造成服務(wù)邏輯崩潰。這種問題造成的后果,往往是無法預(yù)料的。
2 如何解決災(zāi)難性雪崩效應(yīng)?
解決災(zāi)難性雪崩效應(yīng)的方式通常有:降級(jí)、隔離、熔斷、請(qǐng)求緩存、請(qǐng)求合并。
在Spring cloud中處理服務(wù)雪崩效應(yīng),都需要依賴hystrix組件。在pom文件中都需要引入下述依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
通常來說,開發(fā)的時(shí)候,使用ribbon處理服務(wù)災(zāi)難雪崩效應(yīng)(因此章節(jié)2示例均采用Ribbon,章節(jié)3是Feign實(shí)現(xiàn)方式詳解),開發(fā)的成本低。維護(hù)成本高。使用feign技術(shù)處理服務(wù)災(zāi)難雪崩效應(yīng),開發(fā)的成本較高,維護(hù)成本低。
2.1 降級(jí)
降級(jí)是指,當(dāng)請(qǐng)求超時(shí)、資源不足等情況發(fā)生時(shí)進(jìn)行服務(wù)降級(jí)處理,不調(diào)用真實(shí)服務(wù)邏輯,而是使用快速失?。╢allback)方式直接返回一個(gè)托底數(shù)據(jù),保證服務(wù)鏈條的完整,避免服務(wù)雪崩。
解決服務(wù)雪崩效應(yīng),都是避免application client請(qǐng)求application service時(shí),出現(xiàn)服務(wù)調(diào)用錯(cuò)誤或網(wǎng)絡(luò)問題。處理手法都是在application client中實(shí)現(xiàn)。我們需要在application client相關(guān)工程中導(dǎo)入hystrix依賴信息。
并在對(duì)應(yīng)的啟動(dòng)類上增加新的注解@EnableCircuitBreaker,這個(gè)注解是用于開啟hystrix熔斷器的,簡(jiǎn)言之,就是讓代碼中的hystrix相關(guān)注解生效。
引入依賴:
<!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
啟動(dòng)器代碼:
/**
* @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
* 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
*/
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
在調(diào)用application service相關(guān)代碼中,增加新的方法注解@HystrixCommand,代表當(dāng)前方法啟用Hystrix處理服務(wù)雪崩效應(yīng)。
@HystrixCommand注解中的屬性:fallbackMethod - 代表當(dāng)調(diào)用的application service出現(xiàn)問題時(shí),調(diào)用哪個(gè)fallback快速失敗處理方法返回托底數(shù)據(jù)。
實(shí)現(xiàn)類:
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 服務(wù)降級(jí)處理。
* 當(dāng)前方法遠(yuǎn)程調(diào)用application service服務(wù)的時(shí)候,如果service服務(wù)出現(xiàn)了任何錯(cuò)誤(超時(shí),異常等)
* 不會(huì)將異常拋到客戶端,而是使用本地的一個(gè)fallback(錯(cuò)誤返回)方法來返回一個(gè)托底數(shù)據(jù)。
* 避免客戶端看到錯(cuò)誤頁面。
* 使用注解來描述當(dāng)前方法的服務(wù)降級(jí)邏輯。
* @HystrixCommand - 開啟Hystrix命令的注解。代表當(dāng)前方法如果出現(xiàn)服務(wù)調(diào)用問題,使用Hystrix邏輯來處理。
* 重要屬性 - fallbackMethod
* 錯(cuò)誤返回方法名。如果當(dāng)前方法調(diào)用服務(wù),遠(yuǎn)程服務(wù)出現(xiàn)問題的時(shí)候,調(diào)用本地的哪個(gè)方法得到托底數(shù)據(jù)。
* Hystrix會(huì)調(diào)用fallbackMethod指定的方法,獲取結(jié)果,并返回給客戶端。
* @return
*/
@HystrixCommand(fallbackMethod="downgradeFallback")
public List<Map<String, Object>> testDowngrade() {
System.out.println("testDowngrade method : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
/**
* fallback方法。本地定義的。用來處理遠(yuǎn)程服務(wù)調(diào)用錯(cuò)誤時(shí),返回的基礎(chǔ)數(shù)據(jù)。
*/
private List<Map<String, Object>> downgradeFallback(){
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("id", -1);
data.put("name", "downgrade fallback datas");
data.put("age", 0);
result.add(data);
return result;
}
}
2.2 緩存
緩存是指請(qǐng)求緩存。通常意義上說,就是將同樣的GET請(qǐng)求結(jié)果緩存起來,使用緩存機(jī)制(如redis、mongodb)提升請(qǐng)求響應(yīng)效率。
使用請(qǐng)求緩存時(shí),需要注意非冪等性操作對(duì)緩存數(shù)據(jù)的影響。
請(qǐng)求緩存是依托某一緩存服務(wù)來實(shí)現(xiàn)的。在案例中使用redis作為緩存服務(wù)器,那么可以使用spring-data-redis來實(shí)現(xiàn)redis的訪問操作。需要在application client相關(guān)工程中導(dǎo)入下述依賴:
<!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- spring-data-redis spring cloud中集成的spring-data相關(guān)啟動(dòng)器。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在Spring Cloud應(yīng)用中,啟用spring對(duì)cache的支持,需要在啟動(dòng)類中增加注解@EnableCaching,此注解代表當(dāng)前應(yīng)用開啟spring對(duì)cache的支持。簡(jiǎn)言之就是使spring-data-redis相關(guān)的注解生效,如:@CacheConfig、@Cacheable、@CacheEvict等。
啟動(dòng)器:
/**
* @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
* 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
*/
@EnableCircuitBreaker
/**
* @EnableCaching - 開啟spring cloud對(duì)cache的支持。
* 可以自動(dòng)的使用請(qǐng)求緩存,訪問redis等cache服務(wù)。
*/
@EnableCaching
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
spring cloud會(huì)檢查每個(gè)冪等性請(qǐng)求,如果請(qǐng)求完全相同(路徑、參數(shù)等完全一致),則首先訪問緩存redis,查看緩存數(shù)據(jù),如果緩存中有數(shù)據(jù),則不調(diào)用遠(yuǎn)程服務(wù)application service。如果緩存中沒有數(shù)據(jù),則調(diào)用遠(yuǎn)程服務(wù),并將結(jié)果緩存到redis中,供后續(xù)請(qǐng)求使用。
如果請(qǐng)求是一個(gè)非冪等性操作,則會(huì)根據(jù)方法的注解來動(dòng)態(tài)管理redis中的緩存數(shù)據(jù),避免數(shù)據(jù)不一致。
注意:使用請(qǐng)求緩存會(huì)導(dǎo)致很多的隱患,如:緩存管理不當(dāng)導(dǎo)致的數(shù)據(jù)不同步、問題排查困難等。在商業(yè)項(xiàng)目中,解決服務(wù)雪崩效應(yīng)不推薦使用請(qǐng)求緩存。
實(shí)現(xiàn)類:
/**
* 在類上,增加@CacheConfig注解,用來描述當(dāng)前類型可能使用cache緩存。
* 如果使用緩存,則緩存數(shù)據(jù)的key的前綴是cacheNames。
* cacheNames是用來定義一個(gè)緩存集的前綴命名的,相當(dāng)于分組。
*/
@CacheConfig(cacheNames={"test.hystrix.cache"})
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 請(qǐng)求緩存處理方法。
* 使用注解@Cacheable描述方法。配合啟動(dòng)器中的相關(guān)注解,實(shí)現(xiàn)一個(gè)請(qǐng)求緩存邏輯。
* 將當(dāng)期方法的返回值緩存到cache中。
* 屬性 value | cacheNames - 代表緩存到cache的數(shù)據(jù)的key的一部分。
* 可以使用springEL來獲取方法參數(shù)數(shù)據(jù),定制特性化的緩存key。
* 只要方法增加了@Cacheable注解,每次調(diào)用當(dāng)前方法的時(shí)候,spring cloud都會(huì)先訪問cache獲取數(shù)據(jù),
* 如果cache中沒有數(shù)據(jù),則訪問遠(yuǎn)程服務(wù)獲取數(shù)據(jù)。遠(yuǎn)程服務(wù)返回?cái)?shù)據(jù),先保存在cache中,再返回給客戶端。
* 如果cache中有數(shù)據(jù),則直接返回cache中的數(shù)據(jù),不會(huì)訪問遠(yuǎn)程服務(wù)。
*
* 請(qǐng)求緩存會(huì)有緩存數(shù)據(jù)不一致的可能。
* 緩存數(shù)據(jù)過期、失效、臟數(shù)據(jù)等情況。
* 一旦使用了請(qǐng)求緩存來處理冪等性請(qǐng)求操作。則在非冪等性請(qǐng)求操作中必須管理緩存。避免緩存數(shù)據(jù)的錯(cuò)誤。
* @return
*/
@Cacheable("testCache4Get")
public List<Map<String, Object>> testCache4Get() {
System.out.println("testCache4Get method thread name : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
/**
* 非冪等性操作。用于模擬刪除邏輯。
* 一旦非冪等性操作執(zhí)行,則必須管理緩存。就是釋放緩存中的數(shù)據(jù)。刪除緩存數(shù)據(jù)。
* 使用注解@CacheEvict管理緩存。
* 通過數(shù)據(jù)cacheNames | value來刪除對(duì)應(yīng)key的緩存。
* 刪除緩存的邏輯,是在當(dāng)前方法執(zhí)行結(jié)束后。
* @return
*/
@CacheEvict("testCache4Get")
public List<Map<String, Object>> testCache4Del() {
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
}
2.3 請(qǐng)求合并
請(qǐng)求合并是指,在一定時(shí)間內(nèi),收集一定量的同類型請(qǐng)求,合并請(qǐng)求需求后,一次性訪問服務(wù)提供者,得到批量結(jié)果。這種方式可以減少服務(wù)消費(fèi)者和服務(wù)提供者之間的通訊次數(shù),提升應(yīng)用執(zhí)行效率。
未使用請(qǐng)求合并:

使用請(qǐng)求合并:

什么情況下使用請(qǐng)求合并:
在微服務(wù)架構(gòu)中,我們將一個(gè)項(xiàng)目拆分成很多個(gè)獨(dú)立的模塊,這些獨(dú)立的模塊通過遠(yuǎn)程調(diào)用來互相配合工作,但是,在高并發(fā)情況下,通信次數(shù)的增加會(huì)導(dǎo)致總的通信時(shí)間增加,同時(shí),線程池的資源也是有限的,高并發(fā)環(huán)境會(huì)導(dǎo)致有大量的線程處于等待狀態(tài),進(jìn)而導(dǎo)致響應(yīng)延遲,為了解決這些問題,我們需要來了解Hystrix的請(qǐng)求合并。
通常來說,服務(wù)鏈條超出4個(gè),不推薦使用請(qǐng)求合并。因?yàn)檎?qǐng)求合并有等待時(shí)間。
請(qǐng)求合并的缺點(diǎn):
設(shè)置請(qǐng)求合并之后,本來一個(gè)請(qǐng)求可能5ms就搞定了,但是現(xiàn)在必須再等10ms看看還有沒有其他的請(qǐng)求一起的,這樣一個(gè)請(qǐng)求的耗時(shí)就從5ms增加到15ms了,不過,如果我們要發(fā)起的命令本身就是一個(gè)高延遲的命令,那么這個(gè)時(shí)候就可以使用請(qǐng)求合并了,因?yàn)檫@個(gè)時(shí)候時(shí)間窗的時(shí)間消耗就顯得微不足道了,另外高并發(fā)也是請(qǐng)求合并的一個(gè)非常重要的場(chǎng)景。
引入依賴:
<!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
啟動(dòng)器:
/**
* @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
* 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
*/
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
使用注解@HystrixCollapser來描述需要合并請(qǐng)求的方法,并提供合并方法使用注解@HystrixCommand來描述。當(dāng)合并條件(@HystrixCollapser)滿足時(shí),會(huì)觸發(fā)合并方法(@HystrixCommand)來調(diào)用遠(yuǎn)程服務(wù)并得到結(jié)果。
@HystrixCollapser注解介紹:此注解描述的方法,返回值類型必須是java.util.concurrent.Future類型的。代表方法為異步方法。
@HystrixCollapser注解的屬性:
batchMethod - 請(qǐng)求合并方法名。 scope - 請(qǐng)求合并方式。可選值有REQUEST和GLOBAL。REQUEST代表在一個(gè)request請(qǐng)求生命周期內(nèi)的多次遠(yuǎn)程服務(wù)調(diào)用請(qǐng)求需要合并處理,此為默認(rèn)值。GLOBAL代表所有request線程內(nèi)的多次遠(yuǎn)程服務(wù)調(diào)用請(qǐng)求需要合并處理。 timerDelayInMilliseconds - 多少時(shí)間間隔內(nèi)的請(qǐng)求進(jìn)行合并處理,默認(rèn)值為10ms。建議設(shè)置時(shí)間間隔短一些,如果單位時(shí)間并發(fā)量不大,并沒有請(qǐng)求合并的必要。 maxRequestsInBatch - 設(shè)置合并請(qǐng)求的最大極值,也就是timerDelayInMilliseconds時(shí)間內(nèi),最多合并多少個(gè)請(qǐng)求。默認(rèn)值是Integer.MAX_VALUE。
實(shí)現(xiàn)類:
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 需要合并請(qǐng)求的方法。
* 這種方法的返回結(jié)果一定是Future類型的。
* 這種方法的處理邏輯都是異步的。
* 是application client在一定時(shí)間內(nèi)收集客戶端請(qǐng)求,或收集一定量的客戶端請(qǐng)求,一次性發(fā)給application service。
* application service返回的結(jié)果,application client會(huì)進(jìn)行二次處理,封裝為future對(duì)象并返回
* future對(duì)象需要通過get方法獲取最終的結(jié)果。get方法是由控制器調(diào)用的。所以控制器調(diào)用service的過程是一個(gè)異步處理的過程。
* 合并請(qǐng)求的方法需要使用@HystrixCollapser注解描述。
* batchMethod - 合并請(qǐng)求后,使用的方法是什么。如果當(dāng)前方法有參數(shù),合并請(qǐng)求后的方法參數(shù)是當(dāng)前方法參數(shù)的集合,如 int id >> int[] ids。
* scope - 合并請(qǐng)求的請(qǐng)求作用域??蛇x值有g(shù)lobal和request。
* global代表所有的請(qǐng)求線程都可以等待可合并。 常用,所有瀏覽器或者請(qǐng)求源(Postman、curl等)調(diào)用的請(qǐng)求
* request代表一個(gè)請(qǐng)求線程中的多次遠(yuǎn)程服務(wù)調(diào)用可合并
* collapserProperties - 細(xì)致配置。就是配置合并請(qǐng)求的特性。如等待多久,如可合并請(qǐng)求的數(shù)量。
* 屬性的類型是@HystrixProperty類型數(shù)組,可配置的屬性值可以直接通過字符串或常量類定義。
* timerDelayInMilliseconds - 等待時(shí)長(zhǎng)
* maxRequestsInBatch - 可合并的請(qǐng)求最大數(shù)量。
*
* 方法處理邏輯不需要實(shí)現(xiàn),直接返回null即可。
* 合并請(qǐng)求一定是可合并的。也就是同類型請(qǐng)求。同URL的請(qǐng)求。
* @param id
* @return
*/
@HystrixCollapser(batchMethod = "mergeRequest",
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
collapserProperties = {
// 請(qǐng)求時(shí)間間隔在20ms之內(nèi)的請(qǐng)求會(huì)被合并為一個(gè)請(qǐng)求,默認(rèn)為10ms
@HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
// 設(shè)置觸發(fā)批處理執(zhí)行之前,在批處理中允許的最大請(qǐng)求數(shù)。
@HystrixProperty(name = "maxRequestsInBatch", value = "200"),
})
public Future<Map<String, Object>> testMergeRequest(Long id){
return null;
}
/**
* 批量處理方法。就是合并請(qǐng)求后真實(shí)調(diào)用遠(yuǎn)程服務(wù)的方法。
* 必須使用@HystrixCommand注解描述,代表當(dāng)前方法是一個(gè)Hystrix管理的服務(wù)容錯(cuò)方法。
* 是用于處理請(qǐng)求合并的方法。
* @param ids
* @return
*/
@HystrixCommand
public List<Map<String, Object>> mergeRequest(List<Long> ids){
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/testMerge?");
for(int i = 0; i < ids.size(); i++){
Long id = ids.get(i);
if(i != 0){
sb.append("&");
}
sb.append("ids=").append(id);
}
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
}
2.4 熔斷
當(dāng)一定時(shí)間內(nèi),異常請(qǐng)求比例(請(qǐng)求超時(shí)、網(wǎng)絡(luò)故障、服務(wù)異常等)達(dá)到閥值時(shí),啟動(dòng)熔斷器,熔斷器一旦啟動(dòng),則會(huì)停止調(diào)用具體服務(wù)邏輯,通過fallback快速返回托底數(shù)據(jù),保證服務(wù)鏈的完整。
熔斷有自動(dòng)恢復(fù)機(jī)制,如:當(dāng)熔斷器啟動(dòng)后,每隔5秒,嘗試將新的請(qǐng)求發(fā)送給服務(wù)提供者,如果服務(wù)可正常執(zhí)行并返回結(jié)果,則關(guān)閉熔斷器,服務(wù)恢復(fù)。如果仍舊調(diào)用失敗,則繼續(xù)返回托底數(shù)據(jù),熔斷器持續(xù)開啟狀態(tài)。

引入依賴:
<!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
啟動(dòng)器:
/**
* @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
* 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
*/
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
熔斷的實(shí)現(xiàn)是在調(diào)用遠(yuǎn)程服務(wù)的方法上增加@HystrixCommand注解。當(dāng)注解配置滿足則開啟或關(guān)閉熔斷器。
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 熔斷機(jī)制
* 相當(dāng)于一個(gè)強(qiáng)化的服務(wù)降級(jí)。 服務(wù)降級(jí)是只要遠(yuǎn)程服務(wù)出錯(cuò),立刻返回fallback結(jié)果。
* 熔斷是收集一定時(shí)間內(nèi)的錯(cuò)誤比例,如果達(dá)到一定的錯(cuò)誤率。則啟動(dòng)熔斷,返回fallback結(jié)果。
* 間隔一定時(shí)間會(huì)將請(qǐng)求再次發(fā)送給application service進(jìn)行重試。如果重試成功,熔斷關(guān)閉。
* 如果重試失敗,熔斷持續(xù)開啟,并返回fallback數(shù)據(jù)。
* @HystrixCommand 描述方法。
* fallbackMethod - fallback方法名
* commandProperties - 具體的熔斷標(biāo)準(zhǔn)。類型是HystrixProperty數(shù)組。
* 可以通過字符串或常亮類配置。
* CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD - 錯(cuò)誤數(shù)量。在10毫秒內(nèi),出現(xiàn)多少次遠(yuǎn)程服務(wù)調(diào)用錯(cuò)誤,則開啟熔斷。
* 默認(rèn)20個(gè)。10毫秒內(nèi)有20個(gè)錯(cuò)誤請(qǐng)求則開啟熔斷。
* CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE - 錯(cuò)誤比例。在10毫秒內(nèi),遠(yuǎn)程服務(wù)調(diào)用錯(cuò)誤比例達(dá)標(biāo)則開啟熔斷。
* CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS - 熔斷開啟后,間隔多少毫秒重試遠(yuǎn)程服務(wù)調(diào)用。默認(rèn)5000毫秒。
* @return
*/
@HystrixCommand(fallbackMethod = "breakerFallback",
commandProperties = {
// 默認(rèn)20個(gè);10ms內(nèi)請(qǐng)求數(shù)大于20個(gè)時(shí)就啟動(dòng)熔斷器,當(dāng)請(qǐng)求符合熔斷條件時(shí)將觸發(fā)getFallback()。
@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
value="10"),
// 請(qǐng)求錯(cuò)誤率大于50%時(shí)就熔斷,然后for循環(huán)發(fā)起請(qǐng)求,當(dāng)請(qǐng)求符合熔斷條件時(shí)將觸發(fā)getFallback()。
@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
value="50"),
// 默認(rèn)5秒;熔斷多少秒后去嘗試請(qǐng)求
@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
value="5000")}
)
public List<Map<String, Object>> testBreaker() {
System.out.println("testBreaker method thread name : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
private List<Map<String, Object>> breakerFallback(){
System.out.println("breakerFallback method thread name : " + Thread.currentThread().getName());
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("id", -1);
data.put("name", "breaker fallback datas");
data.put("age", 0);
result.add(data);
return result;
}
}
注解屬性描述:
CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
# 是否開啟熔斷策略。默認(rèn)值為true。
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
# 10ms內(nèi),請(qǐng)求并發(fā)數(shù)超出則觸發(fā)熔斷策略。默認(rèn)值為20。
CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
# 當(dāng)熔斷策略開啟后,延遲多久嘗試再次請(qǐng)求遠(yuǎn)程服務(wù)。默認(rèn)為5秒。
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
# 10ms內(nèi),出現(xiàn)錯(cuò)誤的請(qǐng)求百分比達(dá)到限制,則觸發(fā)熔斷策略。默認(rèn)為50%。
CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
# 是否強(qiáng)制開啟熔斷策略。即所有請(qǐng)求都返回fallback托底數(shù)據(jù)。默認(rèn)為false。
CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
# 是否強(qiáng)制關(guān)閉熔斷策略。即所有請(qǐng)求一定調(diào)用遠(yuǎn)程服務(wù)。默認(rèn)為false。
2.5 隔離
所謂隔離,就是當(dāng)服務(wù)發(fā)生問題時(shí),使用技術(shù)手段隔離請(qǐng)求,保證服務(wù)調(diào)用鏈的完整。隔離分為線程池隔離和信號(hào)量隔離兩種實(shí)現(xiàn)方式。
2.5.1 線程池隔離
所謂線程池隔離,就是將并發(fā)請(qǐng)求量大的部分服務(wù)使用獨(dú)立的線程池處理,避免因個(gè)別服務(wù)并發(fā)過高導(dǎo)致整體應(yīng)用宕機(jī)。
線程池隔離優(yōu)點(diǎn):
使用線程池隔離可以完全隔離依賴的服務(wù),請(qǐng)求線程可以快速放回。 當(dāng)線程池出現(xiàn)問題時(shí),線程池是完全隔離狀態(tài)的,是獨(dú)立的,不會(huì)影響到其他服務(wù)的正常執(zhí)行。 當(dāng)崩潰的服務(wù)恢復(fù)時(shí),線程池可以快速清理并恢復(fù),不需要相對(duì)漫長(zhǎng)的恢復(fù)等待。 獨(dú)立的線程池也提供了并發(fā)處理能力。
線程池隔離缺點(diǎn):
線程池隔離機(jī)制,會(huì)導(dǎo)致服務(wù)硬件計(jì)算開銷加大(CPU計(jì)算、調(diào)度等),每個(gè)命令的執(zhí)行都涉及到排隊(duì)、調(diào)度、上下文切換等,這些命令都是在一個(gè)單獨(dú)的線程上運(yùn)行的。




線程池隔離的實(shí)現(xiàn)方式同樣是使用@HystrixCommand注解。相關(guān)注解配置屬性如下:
groupKey - 分組命名,在application client中會(huì)為每個(gè)application service服務(wù)設(shè)置一個(gè)分組,同一個(gè)分組下的服務(wù)調(diào)用使用同一個(gè)線程池。默認(rèn)值為this.getClass().getSimpleName(); commandKey - Hystrix中的命令命名,默認(rèn)為當(dāng)前方法的方法名??墒÷浴S糜跇?biāo)記當(dāng)前要觸發(fā)的遠(yuǎn)程服務(wù)是什么。 threadPoolKey - 線程池命名。要求一個(gè)應(yīng)用中全局唯一。多個(gè)方法使用同一個(gè)線程池命名,代表使用同一個(gè)線程池。默認(rèn)值是groupKey數(shù)據(jù)。 threadPoolProperties - 用于為線程池設(shè)置的參數(shù)。其類型為HystrixProperty數(shù)組。常用線程池設(shè)置參數(shù)有: coreSize - 線程池最大并發(fā)數(shù),建議設(shè)置標(biāo)準(zhǔn)為: requests per second at peak when healthy * 99th percentile latency in second + some breathing room。即每秒最大支持請(qǐng)求數(shù)*(99%平均響應(yīng)時(shí)間 + 一定量的緩沖時(shí)間(99%平均響應(yīng)時(shí)間的10%-20%))。如:每秒可以處理請(qǐng)求數(shù)為1000,99%的響應(yīng)時(shí)間為60ms,自定義提供緩沖時(shí)間為60*0.2=12ms,那么結(jié)果是1000*(0.060+0.012) = 72。maxQueueSize - BlockingQueue的最大長(zhǎng)度,默認(rèn)值為-1,即不限制。如果設(shè)置為正數(shù),等待隊(duì)列將從同步隊(duì)列SynchronousQueue轉(zhuǎn)換為阻塞隊(duì)列LinkedBlockingQueue。 queueSizeRejectionThreshold - 設(shè)置拒絕請(qǐng)求的臨界值。默認(rèn)值為5。此屬性是配合阻塞隊(duì)列使用的,也就是不適用maxQueueSize=-1(為-1的時(shí)候此值無效)的情況。是用于設(shè)置阻塞隊(duì)列限制的,如果超出限制,則拒絕請(qǐng)求。此參數(shù)的意義就是在服務(wù)啟動(dòng)后,可以通過Hystrix的API調(diào)用config API動(dòng)態(tài)修改,而不用用重啟服務(wù),不常用。 keepAliveTimeMinutes - 線程存活時(shí)間,單位是分鐘。默認(rèn)值為1。 execution.isolation.thread.timeoutInMilliseconds - 超時(shí)時(shí)間,默認(rèn)為1000ms。當(dāng)請(qǐng)求超時(shí)自動(dòng)中斷,返回fallback,避免服務(wù)長(zhǎng)期阻塞。 execution.isolation.thread.interruptOnTimeout - 是否開啟超時(shí)中斷。默認(rèn)為TRUE。和上一個(gè)屬性配合使用。
引入依賴:
<!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
啟動(dòng)器:
/**
* @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
* 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
*/
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
實(shí)現(xiàn)類:
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 如果使用了@HystrixCommand注解,則Hystrix自動(dòng)創(chuàng)建獨(dú)立的線程池。
* groupKey和threadPoolKey默認(rèn)值是當(dāng)前服務(wù)方法所在類型的simpleName
*
* 所有的fallback方法,都執(zhí)行在一個(gè)HystrixTimer線程池上。
* 這個(gè)線程池是Hystrix提供的一個(gè),專門處理fallback邏輯的線程池。
*
* 線程池隔離實(shí)現(xiàn)
* 線程池隔離,就是為某一些服務(wù),獨(dú)立劃分線程池。讓這些服務(wù)邏輯在獨(dú)立的線程池中運(yùn)行。
* 不使用tomcat提供的默認(rèn)線程池。
* 線程池隔離也有熔斷能力。如果線程池不能處理更多的請(qǐng)求的時(shí)候,會(huì)觸發(fā)熔斷,返回fallback數(shù)據(jù)。
* groupKey - 分組名稱,就是為服務(wù)劃分分組。如果不配置,默認(rèn)使用threadPoolKey作為組名。
* commandKey - 命令名稱,默認(rèn)值就是當(dāng)前業(yè)務(wù)方法的方法名。
* threadPoolKey - 線程池命名,真實(shí)線程池命名的一部分。Hystrix在創(chuàng)建線程池并命名的時(shí)候,會(huì)提供完整命名。默認(rèn)使用gourpKey命名
* 如果多個(gè)方法使用的threadPoolKey是同名的,則使用同一個(gè)線程池。
* threadPoolProperties - 為Hystrix創(chuàng)建的線程池做配置。可以使用字符串或HystrixPropertiesManager中的常量指定。
* 常用線程池配置:
* coreSize - 核心線程數(shù)。最大并發(fā)數(shù)。1000*(99%平均響應(yīng)時(shí)間 + 適當(dāng)?shù)难舆t時(shí)間)
* maxQueueSize - 阻塞隊(duì)列長(zhǎng)度。如果是-1這是同步隊(duì)列。如果是正數(shù)這是LinkedBlockingQueue。如果線程池最大并發(fā)數(shù)不足,
* 提供多少的阻塞等待。
* keepAliveTimeMinutes - 心跳時(shí)間,超時(shí)時(shí)長(zhǎng)。單位是分鐘。
* queueSizeRejectionThreshold - 拒絕臨界值,當(dāng)最大并發(fā)不足的時(shí)候,超過多少個(gè)阻塞請(qǐng)求,后續(xù)請(qǐng)求拒絕。
*/
@HystrixCommand(groupKey="test-thread-quarantine",
commandKey = "testThreadQuarantine",
threadPoolKey="test-thread-quarantine",
threadPoolProperties = {
@HystrixProperty(name="coreSize", value="30"),
@HystrixProperty(name="maxQueueSize", value="100"),
@HystrixProperty(name="keepAliveTimeMinutes", value="2"),
@HystrixProperty(name="queueSizeRejectionThreshold", value="15")
},
fallbackMethod = "threadQuarantineFallback")
public List<Map<String, Object>> testThreadQuarantine() {
System.out.println("testQuarantine method thread name : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
private List<Map<String, Object>> threadQuarantineFallback(){
System.out.println("threadQuarantineFallback method thread name : " + Thread.currentThread().getName());
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("id", -1);
data.put("name", "thread quarantine fallback datas");
data.put("age", 0);
result.add(data);
return result;
}
}
關(guān)于線程池:
對(duì)于所有請(qǐng)求,都交由tomcat容器的線程池處理,是一個(gè)以http-nio開頭的的線程池; 開啟了線程池隔離后,tomcat容器默認(rèn)的線程池會(huì)將請(qǐng)求轉(zhuǎn)交給threadPoolKey定義名稱的線程池,處理結(jié)束后,由定義的線程池進(jìn)行返回,無需還回tomcat容器默認(rèn)的線程池。線程池默認(rèn)為當(dāng)前方法名; 所有的fallback都單獨(dú)由Hystrix創(chuàng)建的一個(gè)線程池處理。
2.5.2 信號(hào)量隔離
所謂信號(hào)量隔離,就是設(shè)置一個(gè)并發(fā)處理的最大極值。當(dāng)并發(fā)請(qǐng)求數(shù)超過極值時(shí),通過fallback返回托底數(shù)據(jù),保證服務(wù)完整性。

信號(hào)量隔離同樣通過@HystrixCommand注解配置,常用注解屬性有:
commandProperty - 配置信號(hào)量隔離具體數(shù)據(jù)。屬性類型為HystrixProperty數(shù)組,常用配置內(nèi)容如下: execution.isolation.strategy - 設(shè)置隔離方式,默認(rèn)為線程池隔離??蛇x值只有THREAD和SEMAPHORE。 execution.isolation.semaphore.maxConcurrentRequests - 最大信號(hào)量并發(fā)數(shù),默認(rèn)為10。
依賴注入和啟動(dòng)器同線程池隔離,實(shí)現(xiàn)類如下:
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 信號(hào)量隔離實(shí)現(xiàn)
* 不會(huì)使用Hystrix管理的線程池處理請(qǐng)求。使用容器(Tomcat)的線程處理請(qǐng)求邏輯。
* 不涉及線程切換,資源調(diào)度,上下文的轉(zhuǎn)換等,相對(duì)效率高。
* 信號(hào)量隔離也會(huì)啟動(dòng)熔斷機(jī)制。如果請(qǐng)求并發(fā)數(shù)超標(biāo),則觸發(fā)熔斷,返回fallback數(shù)據(jù)。
* commandProperties - 命令配置,HystrixPropertiesManager中的常量或字符串來配置。
* execution.isolation.strategy - 隔離的種類,可選值只有THREAD(線程池隔離)和SEMAPHORE(信號(hào)量隔離)。
* 默認(rèn)是THREAD線程池隔離。
* 設(shè)置信號(hào)量隔離后,線程池相關(guān)配置失效。
* execution.isolation.semaphore.maxConcurrentRequests - 信號(hào)量最大并發(fā)數(shù)。默認(rèn)值是10。常見配置500~1000。
* 如果并發(fā)請(qǐng)求超過配置,其他請(qǐng)求進(jìn)入fallback邏輯。
*/
@HystrixCommand(fallbackMethod="semaphoreQuarantineFallback",
commandProperties={
@HystrixProperty(
name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,
value="SEMAPHORE"), // 信號(hào)量隔離
@HystrixProperty(
name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,
value="100") // 信號(hào)量最大并發(fā)數(shù)
})
public List<Map<String, Object>> testSemaphoreQuarantine() {
System.out.println("testSemaphoreQuarantine method thread name : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
private List<Map<String, Object>> semaphoreQuarantineFallback(){
System.out.println("threadQuarantineFallback method thread name : " + Thread.currentThread().getName());
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("id", -1);
data.put("name", "thread quarantine fallback datas");
data.put("age", 0);
result.add(data);
return result;
}
}
2.5.3線程池隔離和信號(hào)量隔離的對(duì)比

2.5.4線程池隔離和信號(hào)量隔離的選擇
線程池隔離:請(qǐng)求并發(fā)大,耗時(shí)較長(zhǎng)(一般都是計(jì)算大,服務(wù)鏈長(zhǎng)或訪問數(shù)據(jù)庫(kù))時(shí)使用線程池隔離。可以盡可能保證外部容器(如Tomcat)線程池可用,不會(huì)因?yàn)榉?wù)調(diào)用的原因?qū)е抡?qǐng)求阻塞等待。 信號(hào)量隔離:請(qǐng)求并發(fā)大,耗時(shí)短(計(jì)算小,服務(wù)鏈段或訪問緩存)時(shí)使用信號(hào)量隔離。因?yàn)檫@類服務(wù)的響應(yīng)快,不會(huì)占用外部容器(如Tomcat)線程池太長(zhǎng)時(shí)間,減少線程的切換,可以避免不必要的開銷,提高服務(wù)處理效率。
3 Feign的雪崩處理
在聲明式遠(yuǎn)程服務(wù)調(diào)用Feign中,實(shí)現(xiàn)服務(wù)災(zāi)難性雪崩效應(yīng)處理也是通過Hystrix實(shí)現(xiàn)的。而feign啟動(dòng)器spring-cloud-starter-feign中是包含Hystrix相關(guān)依賴的。
如果只使用服務(wù)降級(jí)、熔斷功能不需要做獨(dú)立依賴。如果需要使用Hystrix其他服務(wù)容錯(cuò)能力,需要依賴spring-cloud-starter-hystrix資源。
<!-- hystrix依賴。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
從Dalston版本后,feign默認(rèn)關(guān)閉Hystrix支持。所以必須在全局配置文件中開啟feign技術(shù)中的Hystrix支持。配置如下:
feign.hystrix.enabled=true
如果不使用Hystrix服務(wù)容錯(cuò)功能,在application client端,服務(wù)接口只需要繼承服務(wù)標(biāo)準(zhǔn)api接口即可實(shí)現(xiàn)遠(yuǎn)程服務(wù)調(diào)用。如果使用了Hystrix,則有不同的編寫方式。具體如下。
3.1 代碼實(shí)現(xiàn) - 接口實(shí)現(xiàn)類方式
定義和服務(wù)標(biāo)準(zhǔn)api相同的application client服務(wù)接口。并通過@FeignClient注解來描述fallback方法所在類是什么。這個(gè)fallback方法所在類就是接口的實(shí)現(xiàn)類,實(shí)現(xiàn)的方法就是接口中定義方法的fallback方法。
/**
* 如果在Feign中使用Hystrix,則不能直接繼承服務(wù)標(biāo)準(zhǔn)接口。
* 因?yàn)槔^承接口,一般都不會(huì)給予實(shí)現(xiàn)。會(huì)缺少fallback方法。熔斷機(jī)制鏈條不完整。
* 在當(dāng)前接口中,重復(fù)定義服務(wù)標(biāo)準(zhǔn)接口中定義的方法。
* 遠(yuǎn)程服務(wù)調(diào)用的時(shí)候,是通過@FeignClient實(shí)現(xiàn)的。
* 如果遠(yuǎn)程服務(wù)調(diào)用失敗,則觸發(fā)fallback注解屬性定義的接口實(shí)現(xiàn)類中的對(duì)應(yīng)方法,作為fallback方法。
*
* 在默認(rèn)的Hystrix配置環(huán)境中,使用的是服務(wù)降級(jí)保護(hù)機(jī)制。
*
* 服務(wù)降級(jí),默認(rèn)的情況下,包含了請(qǐng)求超時(shí)。
* feign聲明式遠(yuǎn)程服務(wù)調(diào)用,在啟動(dòng)的時(shí)候,初始化過程比較慢(通過注釋@FeignClient描述接口,接口生成動(dòng)態(tài)代理對(duì)象,實(shí)現(xiàn)服務(wù)調(diào)用)。比ribbon要慢很多。
* 很容易在第一次訪問的時(shí)候,產(chǎn)生超時(shí)。導(dǎo)致返回fallback數(shù)據(jù)。
*/
@FeignClient(name="test-feign-application-service",
fallback=FirstClientFeignServiceImpl.class
)
public interface FirstClientFeignService{
@RequestMapping(value="/testFeign", method=RequestMethod.GET)
public List<String> testFeign();
@RequestMapping(value="/get", method=RequestMethod.GET)
public FeignTestPOJO getById(@RequestParam(value="id") Long id);
@RequestMapping(value="/get", method=RequestMethod.POST)
public FeignTestPOJO getByIdWithPOST(@RequestBody Long id);
@RequestMapping(value="/add", method=RequestMethod.GET)
public FeignTestPOJO add(@RequestParam("id") Long id, @RequestParam("name") String name);
@RequestMapping(value="/addWithGET", method=RequestMethod.GET)
public FeignTestPOJO add(@RequestBody FeignTestPOJO pojo);
@RequestMapping(value="/addWithPOST", method=RequestMethod.POST)
public FeignTestPOJO addWithPOST(@RequestBody FeignTestPOJO pojo);
}
為接口提供實(shí)現(xiàn)類,類中的方法實(shí)現(xiàn)就是fallback邏輯。實(shí)現(xiàn)類需要spring容器管理,使用@Component注解來描述類型。
/**
* 實(shí)現(xiàn)類中的每個(gè)方法,都是對(duì)應(yīng)的接口方法的fallback。
* 一定要提供spring相關(guān)注解(@Component/@Service/@Repository等)。
* 注解是為了讓當(dāng)前類型的對(duì)象被spring容器管理。
* fallback是本地方法。
* 是接口的實(shí)現(xiàn)方法。
*/
@Component
public class FirstClientFeignServiceImpl implements FirstClientFeignService {
@Override
public List<String> testFeign() {
List<String> result = new ArrayList<>();
result.add("this is testFeign method fallback datas");
return result;
}
@Override
public FeignTestPOJO getById(Long id) {
return new FeignTestPOJO(-1L, "this is getById method fallback datas");
}
@Override
public FeignTestPOJO getByIdWithPOST(Long id) {
return new FeignTestPOJO(-1L, "this is getByIdWithPOST method fallback datas");
}
@Override
public FeignTestPOJO add(Long id, String name) {
return new FeignTestPOJO(-1L, "this is add(id, name) method fallback datas");
}
@Override
public FeignTestPOJO add(FeignTestPOJO pojo) {
return new FeignTestPOJO(-1L, "this is add(pojo) method fallback datas");
}
@Override
public FeignTestPOJO addWithPOST(FeignTestPOJO pojo) {
return new FeignTestPOJO(-1L, "this is addWithPOST method fallback datas");
}
}
3.2 相關(guān)配置
在Feign技術(shù)中,一般不使用請(qǐng)求合并,請(qǐng)求緩存等容錯(cuò)機(jī)制。常用的機(jī)制是隔離,降級(jí)和熔斷。
3.2.1 Properties全局配置
# hystrix.command.default和hystrix.threadpool.default中的default為默認(rèn)CommandKey,CommandKey默認(rèn)值為服務(wù)方法名。# 在properties配置中配置格式混亂,如果需要為每個(gè)方法設(shè)置不同的容錯(cuò)規(guī)則,建議使用yml文件配置。
# Command Properties
# Execution相關(guān)的屬性的配置:
# 隔離策略,默認(rèn)是Thread, 可選Thread|Semaphore
hystrix.command.default.execution.isolation.strategy=THREAD
#命令執(zhí)行超時(shí)時(shí)間,默認(rèn)1000ms,只在線程池隔離中有效。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
# 執(zhí)行是否啟用超時(shí),默認(rèn)啟用true,只在線程池隔離中有效。
hystrix.command.default.execution.timeout.enabled=true
# 發(fā)生超時(shí)是是否中斷,默認(rèn)true,只在線程池隔離中有效。
hystrix.command.default.execution.isolation.thread.interruptOnTimeout=true
# 最大并發(fā)請(qǐng)求數(shù),默認(rèn)10,該參數(shù)當(dāng)使用ExecutionIsolationStrategy.SEMAPHORE策略時(shí)才有效。如果達(dá)到最大并發(fā)請(qǐng)求數(shù),請(qǐng)求會(huì)被拒絕。# 理論上選擇semaphore的原則和選擇thread一致,但選用semaphore時(shí)每次執(zhí)行的單元要比較小且執(zhí)行速度快(ms級(jí)別),否則的話應(yīng)該用thread。# semaphore應(yīng)該占整個(gè)容器(tomcat)的線程池的一小部分。
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=10
# 如果并發(fā)數(shù)達(dá)到該設(shè)置值,請(qǐng)求會(huì)被拒絕和拋出異常并且fallback不會(huì)被調(diào)用。默認(rèn)10。# 只在信號(hào)量隔離策略中有效,建議設(shè)置大一些,這樣并發(fā)數(shù)達(dá)到execution最大請(qǐng)求數(shù)時(shí),會(huì)直接調(diào)用fallback,而并發(fā)數(shù)達(dá)到fallback最大請(qǐng)求數(shù)時(shí)會(huì)被拒絕和拋出異常。
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=10
# ThreadPool 相關(guān)參數(shù)
# 并發(fā)執(zhí)行的最大線程數(shù),默認(rèn)10
hystrix.threadpool.default.coreSize=10
# 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.maxQueueSize=-1
# 即使maxQueueSize沒有達(dá)到,達(dá)到queueSizeRejectionThreshold該值后,請(qǐng)求也會(huì)被拒絕。
hystrix.threadpool.default.queueSizeRejectionThreshold=20
# 線程存活時(shí)間,單位是分鐘。默認(rèn)值為1。
hystrix.threadpool.default.keepAliveTimeMinutes=1
# Fallback相關(guān)的屬性 # 當(dāng)執(zhí)行失敗或者請(qǐng)求被拒絕,是否會(huì)嘗試調(diào)用fallback方法 。默認(rèn)true hystrix.command.default.fallback.enabled=true # Circuit Breaker相關(guān)的屬性 # 是否開啟熔斷器。默認(rèn)true hystrix.command.default.circuitBreaker.enabled=true # 一個(gè)rolling window內(nèi)最小的請(qǐng)求數(shù)。如果設(shè)為20,那么當(dāng)一個(gè)rolling window的時(shí)間內(nèi)(比如說1個(gè)rolling window是10毫秒)收到19個(gè)請(qǐng)求# 即使19個(gè)請(qǐng)求都失敗,也不會(huì)觸發(fā)circuit break。默認(rèn)20
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
# 觸發(fā)短路的時(shí)間值,當(dāng)該值設(shè)為5000時(shí),則當(dāng)觸發(fā)circuit break后的5000毫秒內(nèi)都會(huì)拒絕遠(yuǎn)程服務(wù)調(diào)用,也就是5000毫秒后才會(huì)重試遠(yuǎn)程服務(wù)調(diào)用。默認(rèn)5000
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
# 錯(cuò)誤比率閥值,如果錯(cuò)誤率>=該值,circuit會(huì)被打開,并短路所有請(qǐng)求觸發(fā)fallback。默認(rèn)50
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# 強(qiáng)制打開熔斷器
hystrix.command.default.circuitBreaker.forceOpen=false
# 強(qiáng)制關(guān)閉熔斷器
hystrix.command.default.circuitBreaker.forceClosed=false
3.2.2 YML全局配置
YML配置文件,對(duì)SpringEL的支持更加優(yōu)秀。可以通過SpringEL定制化的為每個(gè)服務(wù)調(diào)用配置Hystrix的容錯(cuò)處理方案。對(duì)Hystrix的配置粒度相比較Properties的配置方案更加細(xì)致。
在YML中可配置的Hystrix信息,和Properties中配置的內(nèi)容是一致。
如果需要對(duì)每個(gè)服務(wù)做定制化配置,建議使用yml配置文件。在語法和格式上更容易管理和維護(hù)。
spring:
application:
name: test-feign-application-client
server:
port: 9008
feign:
hystrix:
enabled: true
hystrix:
command:
# default代表全部服務(wù)配置,如果為某個(gè)具體服務(wù)定制配置,使用:'服務(wù)接口名#方法名(參數(shù)類型列表)'的方式來定義。
# 如:'FirstClientFeignService#test(int)'。如果接口名稱在應(yīng)用中唯一,可以只寫simpleName。
# 如果接口名稱在應(yīng)用中不唯一,需要寫fullName(包名.類名)
"FirstClientFeignService#testFeign()":
fallback:
enabled: true
3.3 代碼實(shí)現(xiàn) - Factory實(shí)現(xiàn)方式
在服務(wù)接口的@FeignClient注解中,不再使用fallback屬性,而是定義fallbackFactory屬性。這個(gè)屬性的類型是Class類型的,用于配置fallback代碼所處的Factory。
再定義一個(gè)Java類,實(shí)現(xiàn)接口FallbackFactory,實(shí)現(xiàn)其中的create方法。使用匿名內(nèi)部類的方式,為服務(wù)接口定義一個(gè)實(shí)現(xiàn)類,定義fallback方法實(shí)現(xiàn)。
本地接口定義:
@FeignClient(name="test-feign-application-service",
fallbackFactory=FirstClientFeignServiceFallbackFactory.class
)
public interface FirstClientFeignService{
@RequestMapping(value="/testFeign", method=RequestMethod.GET)
public List<String> testFeign();
@RequestMapping(value="/get", method=RequestMethod.GET)
public FeignTestPOJO getById(@RequestParam(value="id") Long id);
@RequestMapping(value="/get", method=RequestMethod.POST)
public FeignTestPOJO getByIdWithPOST(@RequestBody Long id);
@RequestMapping(value="/add", method=RequestMethod.GET)
public FeignTestPOJO add(@RequestParam("id") Long id, @RequestParam("name") String name);
@RequestMapping(value="/addWithGET", method=RequestMethod.GET)
public FeignTestPOJO add(@RequestBody FeignTestPOJO pojo);
@RequestMapping(value="/addWithPOST", method=RequestMethod.POST)
public FeignTestPOJO addWithPOST(@RequestBody FeignTestPOJO pojo);
}
FallbackFactory實(shí)現(xiàn)類:
/**
* 使用Factory方式實(shí)現(xiàn)Feign的Hystrix容錯(cuò)處理。
* 編寫的自定義Factory必須實(shí)現(xiàn)接口FallbackFactory。
* FallbackFactory中的方法是
* 服務(wù)接口的類型 create(Throwable 遠(yuǎn)程服務(wù)調(diào)用的錯(cuò)誤)
*
* 工廠實(shí)現(xiàn)方案和服務(wù)接口實(shí)現(xiàn)類實(shí)現(xiàn)方案的區(qū)別:
* 工廠可以提供自定義的異常信息處理邏輯。因?yàn)閏reate方法負(fù)責(zé)傳遞遠(yuǎn)程服務(wù)調(diào)用的異常對(duì)象。
* 實(shí)現(xiàn)類可以快速的開發(fā),但是會(huì)丟失遠(yuǎn)程服務(wù)調(diào)用的異常信息。
*/
@Component
public class FirstClientFeignServiceFallbackFactory implements FallbackFactory<FirstClientFeignService> {
Logger logger = LoggerFactory.getLogger(FirstClientFeignServiceFallbackFactory.class);
/**
* create方法 - 就是工廠的生產(chǎn)產(chǎn)品的方法。
* 當(dāng)前工廠生產(chǎn)的產(chǎn)品就是服務(wù)接口的Fallback處理對(duì)象。 就是服務(wù)接口的實(shí)現(xiàn)類的對(duì)象。
*/
@Override
public FirstClientFeignService create(final Throwable cause) {
return new FirstClientFeignService() {
@Override
public List<String> testFeign() {
logger.warn("testFeign() - ", cause);
List<String> result = new ArrayList<>();
result.add("this is testFeign method fallback datas");
return result;
}
@Override
public FeignTestPOJO getById(Long id) {
return new FeignTestPOJO(-1L, "this is getById method fallback datas");
}
@Override
public FeignTestPOJO getByIdWithPOST(Long id) {
return new FeignTestPOJO(-1L, "this is getByIdWithPOST method fallback datas");
}
@Override
public FeignTestPOJO add(Long id, String name) {
return new FeignTestPOJO(-1L, "this is add(id, name) method fallback datas");
}
@Override
public FeignTestPOJO add(FeignTestPOJO pojo) {
return new FeignTestPOJO(-1L, "this is add(pojo) method fallback datas");
}
@Override
public FeignTestPOJO addWithPOST(FeignTestPOJO pojo) {
return new FeignTestPOJO(-1L, "this is addWithPOST method fallback datas");
}
};
}
}
這種實(shí)現(xiàn)邏輯的優(yōu)勢(shì)是,可以獲取遠(yuǎn)程調(diào)用服務(wù)的異常信息。為后期異常處理提供參考。
工廠實(shí)現(xiàn)方案和實(shí)現(xiàn)類的實(shí)現(xiàn)方案,沒有效率和邏輯上的優(yōu)缺點(diǎn)對(duì)比。只是在遠(yuǎn)程服務(wù)調(diào)用異常的處理上有區(qū)別。
4 Hystrix Dashboard - 數(shù)據(jù)監(jiān)控
Hystrix dashboard是一款針對(duì)Hystrix進(jìn)行實(shí)時(shí)監(jiān)控的工具,通過Hystrix Dashboard我們可以在直觀地看到各Hystrix Command的請(qǐng)求響應(yīng)時(shí)間, 請(qǐng)求成功率等數(shù)據(jù)。
4.1 實(shí)現(xiàn)單服務(wù)單節(jié)點(diǎn)數(shù)據(jù)監(jiān)控
在使用了Hystrix技術(shù)的application client工程中增加下述依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
在啟動(dòng)器上增加注解@EnableHystrixDashboard、@EnableHystrix。
@EnableCircuitBreaker
@EnableCaching
@EnableEurekaClient
@SpringBootApplication
@EnableHystrixDashboard
@EnableHystrix
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
啟動(dòng)工程后,如果觸發(fā)了Hystrix,則可以通過http://ip:port/hystrix.stream得到監(jiān)控?cái)?shù)據(jù)。這種監(jiān)控?cái)?shù)據(jù)的獲取都是JSON數(shù)據(jù)。
且數(shù)據(jù)量級(jí)較大。不易于查看??梢允褂肏ystrix Dashboard提供的視圖界面來觀察監(jiān)控結(jié)果。視圖界面訪問路徑為http://ip:port/hystrix。視圖界面中各數(shù)據(jù)的含義如下:

建議:監(jiān)控中心建議使用獨(dú)立工程來實(shí)現(xiàn)。這樣更便于維護(hù)。
4.2 使用Turbine實(shí)現(xiàn)多服務(wù)或集群的數(shù)據(jù)監(jiān)控
Turbine是聚合服務(wù)器發(fā)送事件流數(shù)據(jù)的一個(gè)工具,hystrix的監(jiān)控中,只能監(jiān)控單個(gè)服務(wù)或單個(gè)節(jié)點(diǎn),實(shí)際生產(chǎn)中都為多服務(wù)集群,因此可以通過turbine來監(jiān)控多集群服務(wù)。
Turbine在Hystrix Dashboard中的作用如下:

4.2.1多服務(wù)監(jiān)控
當(dāng)使用Turbine來監(jiān)控多服務(wù)狀態(tài)時(shí),需提供一個(gè)獨(dú)立工程來搭建Turbine服務(wù)邏輯。并在工程中增加下述依賴:
<!-- Dashboard需要的依賴信息。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<!-- Turbine需要的依賴信息。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-turbine</artifactId>
</dependency>
并在全局配置文件中增加下述配置:
#配置Eureka中的serviceId列表,標(biāo)記監(jiān)控哪些服務(wù),多個(gè)服務(wù)名用逗號(hào)分隔,可以配置監(jiān)控的服務(wù),必須開啟了Hystrix Dashboard。
turbine.appConfig=hystrix-application-client,test-feign-application-client
#指定聚合哪些集群,多個(gè)使用","分割,default代表默認(rèn)集群。集群就是服務(wù)名稱。需要配置clusterNameExpression使用。
turbine.aggregator.clusterConfig=default
# 1. clusterNameExpression指定集群名稱,默認(rèn)表達(dá)式appName;此時(shí):turbine.aggregator.clusterConfig需要配置想要監(jiān)控的應(yīng)用名稱;
# 2. 當(dāng)clusterNameExpression: default時(shí),turbine.aggregator.clusterConfig可以不寫,因?yàn)槟J(rèn)就是default;代表所有集群都需要監(jiān)控
turbine.clusterNameExpression="default"
在應(yīng)用啟動(dòng)類中,增加注解@EnableTurbine,代表開啟Turbine服務(wù),提供多服務(wù)集群監(jiān)控?cái)?shù)據(jù)收集。
/**
* @EnableTurbine - 開啟Turbine功能。
* 可以實(shí)現(xiàn)收集多個(gè)App client的Dashboard監(jiān)控?cái)?shù)據(jù)。
*/
@SpringBootApplication
@EnableTurbine
public class HystrixTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixTurbineApplication.class, args);
}
}
最后再Hystrix Dashboard視圖監(jiān)控服務(wù)中,使用http://ip:port/turbine.stream作為監(jiān)控?cái)?shù)據(jù)來源,提供可視化監(jiān)控界面。
注意:使用Turbine做多服務(wù)監(jiān)控的時(shí)候,要求全局配置文件中配置的服務(wù)列表命名在Eureka注冊(cè)中心中可見。就是先啟動(dòng)Application client再啟動(dòng)Turbine。
4.2.2服務(wù)集群監(jiān)控
在spring cloud中,服務(wù)名相同的多服務(wù)結(jié)點(diǎn)會(huì)自動(dòng)形成集群,并提供服務(wù)。在Turbine中,監(jiān)控服務(wù)集群不需要提供任何的特殊配置,因?yàn)閠urbine.appConfig已經(jīng)配置了要監(jiān)控的服務(wù)名稱。集群監(jiān)控?cái)?shù)據(jù)會(huì)自動(dòng)收集。
在Hystrix Dashboard的可視化監(jiān)控界面中,hosts信息會(huì)顯示出服務(wù)集群中的節(jié)點(diǎn)數(shù)量。如圖所示:

注意:使用Turbine做服務(wù)集群監(jiān)控的時(shí)候,必須先啟動(dòng)application client集群,再啟動(dòng)Turbine。保證Turbine啟動(dòng)的時(shí)候,可以在eureka注冊(cè)中心中發(fā)現(xiàn)要監(jiān)控的服務(wù)集群。
獲取更多優(yōu)質(zhì)文章,點(diǎn)擊關(guān)注
??????

