記一次斷電偶然導致Linux磁碟I/O故障無法讀寫
學習地址:https://www.bilibili.com/video/BV18E411x7eT?p=47
分散式系統面臨的問題
複雜分散式體系結構中的應用程式有數十個依賴關係,每個依賴關係在某些時候將不可避免地失敗。
服務雪崩
-
多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,微服務B和微服務C又呼叫其它的微服務,這就是所渭的“扇出“
如果扇出的鏈路上某個微服務的呼叫響應時間過長或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的"雪崩效應" -
對於高流量的應用來說,單一的後端依賴可能會導致所有伺服器上的所有資源都在幾秒鐘內飽和。比失敗更糟糕的是,這些應用程式還可能導致服務之間的延遲增加,備份佇列,執行緒和其他系統資源緊張,導致整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程式或系統。
-
所以,通常當你發現一個模組下的某個例項失敗後,這時候這個模組依然還會接收流量然後這個有問題的模組還呼叫了其他的模組,這樣就會發生級聯故障或者叫雪崩。
Hystrix介紹
官網地址:https://github.com/Netflix/Hystrix/wiki/How-To-Use
-
Hystrix是一個用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時、異常等,Hystri×能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,免級聯故障,以提高分散式系統的彈性。
-
"斷路器"本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個符合預期的、可處理的備選響應(FallBaclk),而不是長時間的等待或者丟擲呼叫方無法處理的異常,這樣就保證了服務呼叫方的執行緒不會被長時間、不必要地佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。
主要功能
- 服務降級
- 服務熔斷
- 接近實時的監控
Hystrix官宣,停更進維
被動修復bugs
不再接受合併請求
不再發布新版本
搭建環境
伺服器忙,請稍候再試,不讓客戶端等待並立刻返回一個友好提示,fallback
哪些情況會觸發降級
-
程式執行異常
-
超時
-
服務熔斷觸發服務降級
-
執行緒池/訊號量打滿也會導致服務降級
cloud-provider-hystrix-payment8001
-
建module
-
寫POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud2020</artifactId> <groupId>com.nuc.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-hystrix-payment8001</artifactId> <dependencies> <!--新增hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>com.nuc.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> xml <optional>true</optional> </dependency> <dependency> <groupId>org.springframewomlrk.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
- 寫YML
server:
port: 8001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
spring:
application:
name: cloud-provider-hystrix-payment
- 主啟動
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
- 業務類
service
@Service
public class PaymentService {
//成功
public String paymentInfo_OK(Integer id) {
return "執行緒池: " + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id;
}
//失敗
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "執行緒池: " + Thread.currentThread().getName() + " paymentInfo_TimeOut,id: " + id + "耗時: " + timeNumber;
}
}
controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
}
-
測試
啟動eureka7001
啟動cloud-provider-hystrix-payment8001
http://localhost:8001/payment/hystrix/ok/31
http://localhost:8001/payment/hystrix/timeout/31
生產者高併發測試
Jmeter壓測測試
Jmeter:https://jmeter.apache.org/
下載--解壓--啟動--bin/jmeter.sh
開啟Jmeter,來20000個併發壓死8001,20000個請求都去訪問paymentInfo_TimeOut服務
- 建立執行緒組
- 設定執行緒數
- 新增Http請求
- 寫請求
-
再次測試
兩個請求方法都在轉圈圈
tomcat的預設的工作執行緒數被打滿了,沒有多餘的執行緒來分解壓力和處理。
-
結論
上面還是服務提供者8001自己測試,假如此時外部的消費者80也來訪問,那消費者只能乾等,最終導致消費端80不滿意,服務端8001直接被拖死
cloud-consumer-feign-hystrix-order80
如果發生在消費端呢
-
建module
-
寫POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud2020</artifactId>
<groupId>com.nuc.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
<dependencies>
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.nuc.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 寫YML
server:
port: 80
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
spring:
application:
name: cloud-provider-hystrix-order
- 主啟動
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
- 業務類
service
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
controller
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_OK(id);
log.info("*******result:" + result);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
}
- 測試
http://localhost/consumer/payment/hystrix/ok/31
消費者高併發測試
2W個執行緒壓8001
消費端80微服務再去訪問正常的OK微服務8001地址
故障現象和導致原因
8001同一層次的其他介面服務被困死,因為tomcat執行緒裡面的工作執行緒已經被擠佔完畢
80此時呼叫8001,客戶端訪問響應緩慢,轉圈圈
結論
正因為有上述故障或不佳表現,才有我們的降級/容錯/限流等技術誕生
如何解決?解決的要求
超時導致伺服器變慢(轉圈) --> 超時不再等待
出錯(宕機或程式執行出錯) --> 出錯要有兜底
-
對方服務(8001)超時了,呼叫者(80)不能一直卡死等待,必須有服務降級
-
對方服務(8001)down機了,呼叫者(80)不能一直卡死等待,必須有服務降級
-
對方服務(8001)OK,呼叫者(80)自己出故障或有自我要求(自己的等待時間小於服務提供者),自己處理降級
服務降級
設定自身呼叫超時時間的峰值,峰值內可以正常執行,超過了需要有兜底的方法處理,作服務降級fallback
哪些情況會觸發降級
執行異常
超時
宕機
執行緒池/訊號量打滿也會導致服務降級
8001
-
業務類
一旦呼叫服務方法失敗並丟擲了錯誤資訊後,會自動呼叫@HystrixCommand標註好的fallbackMethod呼叫類中的指定方法
//失敗
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") //3秒鐘以內就是正常的業務邏輯
})
public String paymentInfo_TimeOut(Integer id) {
//int age = 10 / 0;
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "執行緒池: " + Thread.currentThread().getName() + " paymentInfo_TimeOut,id: " + id;
}
//兜底方法
public String paymentInfo_TimeOutHandler(Integer id) {
return "執行緒池:" + Thread.currentThread().getName() + " 系統繁忙, 請稍候再試 ,id: " + id + "\t" + "哭了哇嗚";
}
故意製造兩個異常
int age = 10 / 0;
//計算異常
超時異常
當服務不可用時,做服務降級,兜底方法paymentInfo_TimeOutHandler
-
主啟動類
新增@EnableCircuitBreaker註解
-
測試
80
- YML
feign:
hystrix:
enabled: true #如果處理自身的容錯就開啟。開啟方式與生產端不一樣。
-
主啟動類
新增@EnableHystrix註解
-
業務類
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") //3秒鐘以內就是正常的業務邏輯
})
//@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
int age = 10 / 0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消費者80,對付支付系統繁忙請10秒鐘後再試或者自己執行出錯請檢查自己,(┬_┬)";
}
- 測試
目前問題&解決
每個業務方法對應一個兜底的方法,程式碼膨脹
-
解決膨脹
feign介面系列
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //全域性的
public class OrderHystrixController {
//下面是全域性fallback方法
public String payment_Global_FallbackMethod() {
return "Global異常處理資訊,請稍後再試,(┬_┬)";
}
}
@DefaultProperties(defaultFallback = " ")配置全域性服務降級方法
通用的和獨自各自分開,避免了程式碼膨脹,合理減少程式碼量
- 測試(使用全域性服務降級
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
int age = 10 / 0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
本次案例服務降級處理是在客戶端80實現完成的,與服務端8001沒有關係,只需要為Feign客戶端定義的介面新增一個服務降級處理的實現類即可實現解耦
服務降級,客戶端去呼叫服務端,碰上服務端宕機或關閉
並且服務降級和業務邏輯混一起,混亂
-
修改cloud-consumer-feign-hystrix-order80
根據cloud-consumer-feign-hystrix-order80已經有的PaymentHystrixService介面,重新新建一個類(PaymentFallbackService)實現該介面,統一為接口裡面的方法進行異常處理
-
PaymentFallbackService類實現PaymentFeignClientService介面
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
}
}
- 修改PaymentFeignClientService介面註解
//發現我們新增的PaymentFallbackService實現類
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
}
- 測試
單個eureka先啟動7001
PaymentHystrixMain8001啟動
http://localhost/consumer/payment/hystrix/ok/31
故意關閉微服務8001
客戶端自己呼叫提升
此時服務端provider已經down了,但是我們做了服務降級處理,讓客戶端在服務端不可用時也會獲得提示資訊而不會掛起耗死伺服器
服務熔斷
論文:https://martinfowler.com/bliki/CircuitBreaker.html
類比保險絲達到最大服務訪問後,直接拒絕訪問,拉閘限電,然後呼叫服務降級的方法並返回友好提示
服務的降級->進而熔斷->恢復呼叫鏈路
-
熔斷機制是應對雪崩效應的一種微服務鏈別戶機制。當扇出鏈路的某個服務出錯不可用者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的呼叫,快速返回錯誤的響應資訊。
-
當檢測到該節點微服務呼叫響應正鐗後,恢復呼叫鏈路。
-
在SpringCloud框架裡,熔斷機制通過Hystrix實現。Hystrix會監控微服務間呼叫的狀況,當失敗的呼叫到一定閾值,預設是5秒內20次呼叫失敗,就會後動熔斷機制。熔斷機制的註解是
@HystrixCommand
8001
- Service
//服務熔斷
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", 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 String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id < 0) {
throw new RuntimeException("*****id 不能負數");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "呼叫成功,流水號:" + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能負數,請稍候再試,(┬_┬)/~~ id: " + id;
}
- Controller
//===服務熔斷
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*******result:"+result);
return result;
}
- 測試
正確:http://localhost:8001/payment/circuit/31
錯誤:http://localhost:8001/payment/circuit/-31
多次錯誤,然後慢慢正確,發現剛開始不滿足條件,就算是正確的訪問地址也不能進行訪問,需要慢慢的恢復鏈路
斷路器
-
熔斷三個狀態
- 熔斷開啟
- 請求不再進行呼叫當前服務,內部設定時鐘一般為MTTR(平均故障處理時間),當開啟時長達到所設時鐘則進入熔斷狀態
- 熔斷關閉
- 熔斷關閉不會對服務進行熔斷
- 熔斷半開
- 部分請求根據規則呼叫當前服務,如果請求成功且符合規則則認為當前服務恢復正常,關閉熔斷
- 熔斷開啟
-
三個重要引數
-
斷路器開啟或者關閉的條件
-
當滿足一定閥值的時候(預設10秒內超過20個請求次數)
-
當失敗率達到一定的時候(預設10秒內超過50%請求失敗)
-
達以上閥值,斷路器將會開啟
-
當開啟的時候,所有請求都不會進行轉發
-
一段時間之後(預設是5秒),這個時候斷路器是半開狀態,會讓其中一個請求進行轉發。如果成功,斷路器會關閉,若失敗,繼續開啟。重複4和5
-
-
斷路器開啟之後
-
再有請求呼叫的時候,將不會呼叫主邏輯,而是直接呼叫降級fallback。通過斷路器,實現了自動地發現錯誤並將降級邏輯切換為主邏輯,減少響應延遲的效果。
-
原來的主邏輯要如何恢復呢?
-
對於這一問題,hystrix也為我們實現了自動恢復功能。
-
當斷路器開啟,對主邏輯進行熔斷之後,hystrix會啟動一個休眠時間窗,在這個時間窗內,降級邏輯是臨時的成為主邏輯,
-
當休眠時間窗到期,斷路器將進入半開狀態,釋放一次請求到原來的主邏輯上,如果此次請求正常返回,那麼斷路器將繼續閉合,
-
主邏輯恢復,如果這次請求依然有問題,斷路器繼續進入開啟狀態,休眠時間窗重新計時。
-
-
服務限流
秒殺高併發等操作,嚴禁一窩蜂的過來擁擠,大家排隊,一秒鐘N個,有序進行
alibaba的Sentinel說明
服務監控
除了隔離依賴服務的呼叫以外,Hystrix還提供了準實時的調監控(HystrixDashboard),Hystrix會持續地記錄所有通過Hystrix發起的請求的執行資訊,並以統計報表和圖形的形式展示給使用者,包括每秒執行多少請求多少成功,多少失敗等。Netflix通過hystrix-metrics-event-stream專案實現了對以上指標的監控。SpringCloud也提供了HystrixDashboard的整合對監控內容轉化成視覺化介面。
cloud-consumer-hystrix-dashboard9001
-
建module
-
寫POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud2020</artifactId>
<groupId>com.nuc.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>
<dependencies>
<!--新增hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 寫YML
server:
port: 9001
- 主啟動類
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
- 修改cloud-provider-hystrix-payment8001主啟動類
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
- 監控測試
http://localhost:9001/hystrix
填寫監控地址
http://localhost:8001/hystrix.stream
訪問測試
http://localhost:8001/payment/circuit/31
http://localhost:8001/payment/circuit/-31