SpringCloud 入門實戰 - Hystrix 服務降級案例
我們知道 Hystrix 關鍵特性:服務降級、服務熔斷、服務限流。本篇文章主要來講一講 Hystrix 服務降級是怎麼做的。
一、Hystrix 服務降級
服務降級
:也就是假設對方系統不可用了,向調用方返回一個符合預期的,可備選的響應。比如我們常見的 “服務器忙,請稍後重試!”、“系統開小差,請稍後再試!”、“您的內容飛到了外太空…” 等,超時不再等待,出錯有兜底方案。不用客戶等待並立刻返回一個友好的提示,這就是服務降級。
出現服務降級的情況
:
①程序運行異常;
②超時;
③服務熔斷觸發服務降級;
④線程池 / 信號量打滿也會導致服務降級。
解決方案
:
對方服務超時了或宕機,調用者不能一直卡死等待,必須有服務降級;
對方服務可能 OK,調用者自己出故障或有自我要求(自己的等待時間小於服務提供者),自己服務降級。
二、服務端降級
在 springcloud 項目中,技術實現上來說 Hystrix 服務降級放在服務端、客戶端均可以,具體根據自己的業務場景判定,但是一般 Hystrix 服務降級 fallback 是放在客戶端,這裏我們均演示一下。
項目結構:
父工程的版本環境:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
1、引入 Hystrix 依賴
首先服務端(也就是我們的 payment 工程)引入 hystrix 依賴 (具體版本根據自己環境決定):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.18</version>
</dependency>
2、案例演示
Controller 類代碼:
@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
}
服務端業務類簡單編寫兩個方法:一個正常查詢方法,一個模擬複查業務耗時的方法:
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, Payment> implements PaymentService {
@Override
public String paymentInfoOk(Integer id) {
return "線程池:" + Thread.currentThread().getName() + "成功訪問paymentInfoOK,id=" + id;
}
@Override
public String paymentInfoTimeOut(Integer id) {
int time = 5;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "線程池:" + Thread.currentThread().getName() + "耗時" + time + "秒成功訪問paymentInfoTimeOut,id=" + id;
}
}
簡單測試
:兩個接口都正常訪問和返回:
http://localhost:8001/payment/hystrix/ok/1 直接返回
http://localhost:8001/payment/hystrix/timeout/1 等待 5s 返回(轉圈圈一下)
併發請求
:接下來通過 jmeter 壓測接口:http://localhost:8001/payment/hystrix/timeout/1,模擬 2w 個請求訪問我們的 paymentInfoTimeOut 耗時業務接口。
現象
:發現兩個接口都會轉圈甚至卡死,我們發現正常直接返回結果的接口也會被同一個微服務裏耗時接口拖垮。所以想要系統高可用必須降級處理。假設 payment 服務端業務邏輯實際處理需要 5s,微服務對自身有要求,超過 3s 作超時處理,也是需要降級的。
原因
:SpringBoot 默認集成的是 tomcat,tomcat 的默認工作線程(10 個)被打滿了,沒有多餘的線程來分解壓力和處理。
3、服務端如何降級
1)啓動類添加激活註解:
@EnableCircuitBreaker
有的環境會提示 @EnableCircuitBreaker is deprecated 已棄用,用 @EnableHystrix 註解代替即可。
本 demo 環境使用的是 @EnableHystrix 激活註解
2)業務類方法上添加啓用:@HystrixCommand(fallbackMethod = “fallback”)
在 fallbackMethod 中添加一個方法,value 設置爲超時時間,當超過超時時間時,就會調用備用的方法
注:降級(FallBack)方法必須與其對應的業務方法在同一個類中,否則無法生效。
@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
//設置超時時間3s
@HystrixCommand(fallbackMethod = "fallback",commandProperties = {
@HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
public String fallback(Integer id){
return "系統繁忙,請稍後再試!線程池:" + Thread.currentThread().getName() +"訪問paymentInfoTimeOut,id=" + id;
}
}
測試降級效果:
不會再等待 5s,到達設置的超時時間 3s 就會調用降級方法。
Tip:如果不設置超時時間,
Hystrix默認超時時間是1秒
,我們可以通過 hystrix 源碼看到:找到 hystrix-core.jar 包下的 HystrixCommandProperties 類中的 default_executionTimeoutInMilliseconds 屬性局勢默認的超時時間:1000
三、客戶端降級
我們將服務端 payment 工程剛做的操作恢復到沒有任何降級處理的狀態,開始演示客戶端降級。
1、引入 Hystrix 依賴
客戶端工程 order 同樣的操作引入 Hystrix 依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.18</version>
</dependency>
2、啓動類添加激活註解 @EnableHystrix
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableHystrix
public class CloudOrder {
public static void main(String[] args) {
SpringApplication.run(CloudOrder.class, args);
}
}
3、改 yml
我們的客戶端是通過 feign 調用服務端,所以在 order 工程中修改 yml,添加內容:
feign:
hystrix:
enabled: true
4、業務類方法上添加啓用:@HystrixCommand(fallbackMethod = “fallback”)
添加:@HystrixCommand(fallbackMethod = “fallback”,commandProperties = {@HystrixProperty(name =“execution.isolation.thread.timeoutInMilliseconds”,value = “1500”) }) 及 fallback 方法:
@RestController
@RequestMapping("/order")
@Slf4j
@DefaultProperties(defaultFallback="globalFallback")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
@HystrixCommand(fallbackMethod = "fallback",commandProperties = {
@HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
public String fallback(Integer id){
return "太久了我不想等待了,我已經等待了1.5s了!線程池:" + Thread.currentThread().getName() +"訪問paymentInfoTimeOut,id=" + id;
}
}
Feign 接口:
@Component
@FeignClient(value = "CLOUD-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);
}
客戶端測試調用服務端超時降級:
我們發現方法一對一降級,那樣代碼將越來越多,越來越重複,代碼膨脹,所以要設置全局通用服務降級方法。
四、全局通用服務降級
避免代碼膨脹,我們設置全局通用服務降級方法,如果需要特殊配置的在另外單獨配。
小結:如果註解 @HystrixCommand 定義 fallback 方法,走自己定義的 fallback 方法,反之走全局默認的 @DefaultProperties 中指定的方法。
總之:全局降級方法的優先級較低
,只有業務方法沒有指定其降級方法時,服務降級時纔會觸發全局回退方法。若業務方法指定它自己的回退方法,那麼在服務降級時,就只會直接觸發它自己的回退方法,而非全局回退方法。
1、類上加註解:DefaultProperties(defaultFallback=" ") 指定全局兜底方法
2、方法加註解:@HystrixCommand
3、編寫全局降級方法
@RestController
@RequestMapping("/order")
@Slf4j
@DefaultProperties(defaultFallback="globalFallback")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping(value = "/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoOk(id);
log.info("========result:{}========", result);
return result;
}
@HystrixCommand
@GetMapping(value = "/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoTimeOut(id);
log.info("========result:{}========", result);
return result;
}
//全局方法降級
public String globalFallback(){
return "全局降級!線程池:" + Thread.currentThread().getName() +"訪問paymentInfoTimeOut";
}
}
測試客戶端全局降級:
繼續思考,所有的降級都在 Controller 層處理是不是
耦合度很高
,服務降級每個方法都添加 hyxtrix 兜底的方法,造成方法的膨脹,既然是服務間的調用,我們能不能將和服務端工程相關的 feign 接口整體降級,也就是和 payment 工程相關的都統一降級。
五、解耦服務降級
只需要爲Feign客戶端定義的接口添加一個服務降級處理的實現類即可實現解耦,也就是@FeignClient註解修飾的這個調用類。
1、新建一個解耦降級處理類
新建一個解耦降級處理類 PaymentFallbackService.java,該類實現 @FeignClient 修飾的 PaymentHystrixService 接口:
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfoOk(Integer id) {
return "全局解耦降級處理PaymentFallback->paymentInfoOk!";
}
@Override
public String paymentInfoTimeOut(Integer id) {
return "全局解耦降級處理PaymentFallback->paymentInfoTimeOut!";
}
}
2、Feign 客戶端定義的接口添加 fallback
Feign 客戶端定義的接口 PaymentHystrixService.java 中添加 fallback = PaymentFallbackService.class
@Component
@FeignClient(value = "CLOUD-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
String paymentInfoOk(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);
}
3、測試降級
Tip:如果不生效,檢查該實現類是否以組件的形式添加 Spring 容器中,最常用的方式就是在類上標註 @Service 註解或者 @Component 註解。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Pyf0WDeU_klsV-0nAq5Whw