服務降級
1、Hystrix斷路器
分布式系統面臨的問題
復雜分布式體系結構中的應用程序有數十個依賴關系,每個依賴關系在某些時候將不可避免地失數。
服務雪崩
多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其它的微服務,這就是所謂的“扇出”。如果扇出的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會占用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”.
對於高流量的應用來說,單一的后端依賴可能會導致所有服務器上的所有資源都在幾秒鍾內飽和。比失敗更糟糕的是,這些應用程序還可能導致服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導致整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關系的失敗,不能取消整個應用程序或系統。所以,通常當你發現一個模塊下的某個實例失敗后,這時候這個模塊依然還會接收流量,然后這個有問題的模塊還調用了其他的模塊,這樣就
會發生級聯故障,或者叫雪崩。
Hystrix是什么
Hystrix是一個用於處理分布式系統的延遲和容錯的開源庫,在分布式系統里,許多依賴不可避免的會調用失敗,比如超時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分布式系統的彈性。
“斷路器”本身是一種開關裝置,當某個服務單元發生故障之后,通過斷路器的故障監控(類似熔斷保險絲),向調用方返回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或者拋出調用方無法處理的異常,這樣就保證了服務調用方的線程不會被長時間、不必要地占用,從而避免了故障在分布式系統中的蔓延,乃至雪崩。
能干嘛
服務降級、服務熔斷、接近實時的監控(圖形化)
官網資料
https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix官宣,停更進維
https://github.com/Netflix/Hystrix
服務降級fallback
對方系統不用了,你需要給我一個兜底的解決方法。
服務器忙,請稍后再試,不讓客戶端等待並立刻返回一個友好提示,fallback
哪些情況會出發降級?
Hystrix支付微服務構建
新建cloud-provider-hystrix-payment8001模塊,提供服務。
//service
@Service
public class PaymentService {
/**
* 正常訪問
*
* @param id id
* @return String
*/
public String hystrixGetOk(Integer id) {
return "當前線程:" + Thread.currentThread().getName() + " hystrixGetOk()正常調用成功 , id :" + id + " O(∩_∩)O哈哈~ ";
}
/**
* 模擬延時訪問
*
* @param id id
* @return String
*/
public String hystrixGetTimeOut(Integer id) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
int time = 3;
return "當前線程:" + Thread.currentThread().getName() + " hystrixGetTimeOut()延時調用成功 , id :" + id + " 耗時(秒):" + time;
}
}
// controller
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
String port;
@Resource
PaymentService service;
@GetMapping(value = "/payment/hystrix/ok/{id}")
public CommonResult<Object> hystrixGetOk(@PathVariable("id") Integer id) {
String ok = service.hystrixGetOk(id);
log.info(ok);
return new CommonResult<>(200, "success,port:" + port, ok);
}
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public CommonResult<Object> hystrixGetTimeOut(@PathVariable("id") Integer id) {
String out = service.hystrixGetTimeOut(id);
log.info(out);
return new CommonResult<>(200, "success,port:" + port, out);
}
}
高並發壓力測試 JMeter
開啟JMeter···20000訪問payment接口。。。
如何使用JMeter:
https://www.cnblogs.com/monjeo/p/9330464.html
手動調用http接口
上面還是服務提供者8001自己測試,假如此時外部的消費者80也來訪問,那消費者只能干等,最終導致消費端80不滿意,服務端8001直接被拖死
Hystrix 消費者模塊
@Component
@FeignClient(value = "cloud-payment-hystrix-service")
public interface OpenFeignService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
public CommonResult<Object> hystrixGetOk(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public CommonResult<Object> hystrixGetTimeOut(@PathVariable("id") Integer id);
}
@RestController
@Slf4j
public class OrderController implements OpenFeignService {
@Resource
OpenFeignService service;
@Override
@GetMapping(value = "/consumer/payment/hystrix/ok/{id}")
public CommonResult<Object> hystrixGetOk(@PathVariable("id") Integer id) {
return service.hystrixGetOk(id);
}
@Override
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
public CommonResult<Object> hystrixGetTimeOut(@PathVariable("id") Integer id) {
return service.hystrixGetTimeOut(id);
}
}
cloud-consumer-feign-hystrix-order80
8001同一層次的其它接口服務被困死,因為tomcat線程池里面的工作線程已經被擠占完畢
80此時調用8001,客戶端訪問響應緩慢,轉圈圈
如何解決和要求?
、、自己解決。
服務降級
1、8001提供者模塊:
引入pom依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
降級配置使用 @HystrixCommand
/**
* 模擬延時訪問
* HystrixProperty 設置 超時時間為 3 秒鍾
*
* @param id id
* @return String
*/
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutExceptionHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String hystrixGetTimeOut(Integer id) {
//模擬 異常
System.out.println((10 / 0));
// 模擬超時
int time = 5;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "當前線程:" + Thread.currentThread().getName() + " hystrixGetTimeOut()延時調用成功 , id :" + id + " 耗時(秒):" + time;
}
public String paymentInfoTimeOutExceptionHandler(Integer id) {
return "當前線程:" + Thread.currentThread().getName() +
" paymentInfoTimeOutExceptionHandler()延時或者異常進入此方法,請稍后重試 o(╥﹏╥)o, id :" + id;
}
啟動類開啟
@EnableCircuitBreaker
故意制造兩個異常:
1 int age = 10/0;計算異常
2我們能接受3秒鍾,它運行5秒鍾,超時異常。
當前服務不可用了,做服務降級,兜底的方案都是paymentInfoTimeOutExceptionHandler
2、消費者模塊
改造
@Override
@HystrixCommand(fallbackMethod = "orderInfoTimeOutExceptionHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
public CommonResult<Object> hystrixGetTimeOut(@PathVariable("id") Integer id) {
//模擬出錯
// System.out.println(10/0);
return service.hystrixGetTimeOut(id);
}
public CommonResult<Object> orderInfoTimeOutExceptionHandler(Integer id) {
return new CommonResult<>(555, "當前線程:" + Thread.currentThread().getName() +
" 我是消費者80,對方支付系統繁忙請10秒后再試或者自己運行出錯請校驗 o(╥﹏╥)o, id :" + id);
}
}
啟動類增加@EnableHystrix
3、問題和解決
問題:
每個業務方法對應一個兜底的方法,代碼膨脹
統—和自定義的分開
每個方法配置一個???膨脹解決
DefaultProperties(defaultFallback ="")
1:1每個方法配置—個服務降級方法,技術上可以,實際上俊X
1:N除了個別重要核心業務有專屬,其它普通的可以通過@DefaultProperties(defaultFallback=")統一跳轉到統━處理結果頁面
通用的和獨享的各自分開,避免了代碼膨脹,合理減少了代碼量,o(n_∩)o哈哈~
1、yml增加配置
## 開啟feign對 hystrix的支持
feign:
hystrix:
enabled: true
2、改動接口類
增加注解
@DefaultProperties(defaultFallback = "orderGlobalTimeOutExceptionHandler")
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "orderGlobalTimeOutExceptionHandler")
public class OrderController implements OpenFeignService {
@HystrixCommand
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
public CommonResult<Object> hystrixGetTimeOut(@PathVariable("id") Integer id) {
//模擬出錯
System.out.println(10/0);
return service.hystrixGetTimeOut(id);
}
/**
* 全局處理
*
* @param id
* @return
*/
public CommonResult<Object> orderGlobalTimeOutExceptionHandler() {
return new CommonResult<>(555, "Global異常處理信息,請稍后再試,o(╥﹏╥)o");
}
}
和業務邏輯混在一起???混亂(服務器宕機)
新增OpenFeignService接口實現類PaymentFallBackServiceImpl
@Component
public class PaymentFallBackServiceImpl implements OpenFeignService {
@Override
public CommonResult<Object> hystrixGetOk(Integer id) {
return new CommonResult<>(555, "----PaymentFallBackServiceImpl fall- hystrixGetOk , o(╥﹏╥)o");
}
@Override
public CommonResult<Object> hystrixGetTimeOut(Integer id) {
return new CommonResult<>(555, "----PaymentFallBackServiceImpl fall- hystrixGetTimeOut , o(╥﹏╥)o");
}
}
OpenFeignService接口注解修改
@FeignClient(value = "cloud-payment-hystrix-service", fallback = PaymentFallBackServiceImpl.class)
結果顯示:
服務熔斷break
類比保險絲達到最大服務訪問后,直接拒絕訪問,拉閘限電,然后調用服務降級的方法並返回友好提示。
服務的降級->進而熔斷->恢復調用鏈路
一句話就是類似於家里的保險絲
熔斷機制概述
熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。當扇出鏈路的某個微服務出錯不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速返回錯誤的響應信息。
當檢測到該節點微服務調用響應正常后,恢復調用鏈路。
在Spring Cloud框架里,熔斷機制通過Hystrix實現。Hystrix會監控微服務間調用的狀況,當失敗的調用到一定閾值,缺省是5秒內20次調用失敗,就會啟動熔斷機制。熔斷機制的注解是@HystrixCommand.
大神論文:
https://martinfowler.com/bliki/CircuitBreaker.html
實操:
PaymentService:
// - ------------- 服務熔斷 -----
// HystrixProperty默認值 看 HystrixCommandProperties
@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallBack", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否開啟斷路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//請求次數
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//時間窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),//失敗率達到多少后跳閘(在時間窗口期中計算失敗率)
})
public CommonResult<Object> paymentCircuitBreaker(Integer id) {
if (id < 0) {
throw new RuntimeException("****** id \t 不能為負數");
}
String uuid = IdUtil.simpleUUID();
log.info("uuid\t" + uuid);
return new CommonResult<>(200, Thread.currentThread().getName() + "\t調用成功,流水號為" + uuid, "id:" + id);
}
public CommonResult<Object> paymentCircuitBreakerFallBack(Integer id) {
return new CommonResult<>(555, Thread.currentThread().getName() + "\t調用失敗,o(╥﹏╥)o", "id:" + id);
}
PaymentController:
// ==== 服務 熔斷
@GetMapping(value = "/payment/hystrix/circuit/{id}")
public CommonResult<Object> hystrixGetCircuit(@PathVariable("id") Integer id) {
CommonResult<Object> result = service.paymentCircuitBreaker(id);
log.info(result.toString());
return result;
}
測試結果:
快速訪問異常接口,id值為負數,然后達到熔斷條件后,調用正數返回:
熔斷類型:
什么時候起作用?
斷路器開啟或關閉的條件:
斷路器打開之后:
1:再有請求調用的時候,將不會調用主邏輯,而是直接調用降級fallback、通過斷路器,實現了自動地發現錯誤並將降級邏輯切換為主邏輯,減少響
應延遲的效果。
2:原來的主邏輯要如何恢復呢?
對於這一問題,hystrix也為我們實現了自動恢復功能。
當斷路器打開,對主邏輯進行熔斷之后,hystrix會啟動一個休眠時間窗,在這個時間窗內,降級邏輯是臨時的成為主邏輯,
當休眠時間窗到期,斷路器將進入半開狀態,釋放一次請求到原來的主邏輯上,如果此次請求正常返回,那么斷路器將繼續閉合,
主邏輯恢復,如果這次請求依然有問題,斷路器繼續進入打開狀態,休眠時間窗重新計時。
服務限流flowlimit
秒殺高並發等操作,嚴禁一窩蜂的過來擁擠,大家排隊,一秒鍾N個,有序進行
后面使用sentinel詳解。
Hystrix工作流程
https://github.com/Netflix/Hystrix/wiki/How-it-Works
整個流程可以大致歸納為如下幾個步驟:
- 創建HystrixCommand或則HystrixObservableCommand對象
- 調用HystrixCommand或則HystrixObservableCommand方法執行Command
- 根據依賴調用的結果緩存情況進行相應的處理
- 根據該類依賴請求熔斷器的打開狀態進行相應的處理
- 根據該類依賴請求的總量進行相應的處理
- 執行對外部依賴的請求邏輯
- 計算統計熔斷器數據值
- 調用降級方法或則返回依賴請求的真正結果
高清大圖:https://www.processon.com/view/link/5e1c128ce4b0169fb51ce77e
Hystrix圖形化Dashboard搭建
除了隔離依賴服務的調用以外,Hystrix還提供了准實時的調用監控(Hystrix Dashboard),Hystrix會持續地記錄所有通過Hystrix發起的請求的執行信息,並以統計報表和圖形的形式展示給用戶,包括每秒執行多少請求多少成功,多少失敗等。Netflix通過hystrix-metrics-event-stream項目實現了對以上指標的監控。Spring Cloud也提供了HystrixDashboard的整合,對監控內容轉化成可視化界面。
新建模塊cloud-consumer-hystrix-dashboard9001
yml
server:
port: 9001
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
HystrixDashboard9001啟動類增加@EnableHystrixDashboard注解
訪問:http://localhost:9001/hystrix
點擊Monitor Stream 按鈕
調用8001接口后:
實心圓:共有兩種含義。它通過顏色的變化代表了實例的健康程度,它的健康度從和<黃色<橙色<紅色遞減。
該實心圓除了顏色的變化之外,它的大小也會根據實例的請求流量發生變化,流量越大該實心圓就越大。所以通過該實心圓的展示,就
可以在大量的實例中快速的發現故障實例和高壓力實例.
問題1:
解決:
在被監控的模塊中增加
/**
* 此配置是為了服務監控面配置,與服務容錯本身無關,SpringCloud 升級后的坑。
* ServletRegistrationBean因為SpringBoot的默認路徑不是“/hystrix.stream”
* 只要在自己的項目里配置上下面的servlet就可以了
*
* @return ServletRegistrationBean
*/
@Bean
public ServletRegistrationBean<Servlet> getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<Servlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
訪問http://localhost:8001/hystrix.stream,訪問之前記得調用一次成功的接口,例如http://localhost:8001/payment/hystrix/circuit/12
問題2:
Unable to connect to Command Metric Stream.
2020-08-27 15:10:52.890 WARN 43420 --- [nio-9001-exec-4] ashboardConfiguration$ProxyStreamServlet : Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names. If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList.
2020-08-27 15:10:52.902 WARN 43420 --- [nio-9001-exec-5] ashboardConfiguration$ProxyStreamServlet : Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names. If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList.
解決:
參考 https://www.jianshu.com/p/0a682e4781b0
yml增加
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
公眾號:發哥講
這是一個稍偏基礎和偏技術的公眾號,甚至其中包括一些可能閱讀量很低的包含代碼的技術文,不知道你是不是喜歡,期待你的關注。
代碼分享
微信公眾號 點擊關於我,加入QQ群,即可獲取到代碼以及高級進階視頻和電子書!!
如果你覺得文章還不錯,就請點擊右上角選擇發送給朋友或者轉發到朋友圈~
● 掃碼關注我們
據說看到好文章不推薦的人,服務器容易宕機!