springcloud-Hystrix斷路器
分散式系統面臨的問題 : 複雜分散式體系結構中的應用程式有數十個依賴關係,每個依賴關係在某些時候將不可避免失敗
1 服務雪崩
多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,微服務B和微服務C又呼叫其他的微服務,這就是所謂的“扇出”,如果扇出的鏈路上某個微服務的呼叫響應時間過長,或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應。
對於高流量的應用來說,單一的後端依賴可能會導致所有伺服器上的所有資源都在幾十秒內飽和。比失敗更糟糕的是,這些應用程式還可能導致服務之間的延遲增加,備份佇列,執行緒和其他系統資源緊張,導致整個系統發生更多的級聯故障,這些都表示需要對故障和延遲進行隔離和管理,以達到單個依賴關係的失敗而不影響整個應用程式或系統執行。
我們需要,棄車保帥!
2 Hystrix概念描述
2.1 Hystrix是什麼
- Hystrix是一個應用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時,異常等,Hystrix 能夠保證在一個依賴出問題的情況下,不會導致整個體系服務失敗,避免級聯故障,以提高分散式系統的彈性。
- “斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控 (類似熔斷保險絲) ,向呼叫方返回一個服務預期的,可處理的備選響應 (FallBack) ,而不是長時間的等待或者丟擲呼叫方法無法處理的異常,這樣就可以保證了服務呼叫方的執行緒不會被長時間,不必要的佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。
2.2 Hystrix能幹什麼
- 服務降級
- 服務熔斷
- 服務限流
- 接近實時的監控
- …
當一切正常時,請求流可以如下所示:
當許多後端系統中有一個潛在阻塞服務時,它可以阻止整個使用者請求:
隨著大容量通訊量的增加,單個後端依賴項的潛在性會導致所有伺服器上的所有資源在幾秒鐘內飽和。
應用程式中通過網路或客戶端庫可能導致網路請求的每個點都是潛在故障的來源。比失敗更糟糕的是,這些應用程式還可能導致服務之間的延遲增加,從而備份佇列、執行緒和其他系統資源,從而導致更多跨系統的級聯故障。
當使用Hystrix包裝每個基礎依賴項時,上面的圖表中所示的體系結構會發生類似於以下關係圖的變化。每個依賴項是相互隔離的,限制在延遲發生時它可以填充的資源中,幷包含在回退邏輯中,該邏輯決定在依賴項中發生任何型別的故障時要做出什麼樣的響應:
官網資料:https://github.com/Netflix/Hystrix/wiki
3 什麼是服務熔斷?
熔斷機制是對應雪崩效應的一種微服務鏈路保護機制。
當扇出鏈路的某個微服務不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的呼叫,快速返回錯誤的響應資訊。檢測到該節點微服務呼叫響應正常後恢復呼叫鏈路。在SpringCloud框架裡熔斷機制通過Hystrix實現。Hystrix會監控微服務間呼叫的狀況,當失敗的呼叫到一定閥值預設是5秒內20次呼叫失敗,就會啟動熔斷機制。熔斷機制的註解是:@HystrixCommand。
服務熔斷解決如下問題:
- 當所依賴的物件不穩定時,能夠起到快速失敗的目的;
- 快速失敗後,能夠根據一定的演算法動態試探所依賴物件是否恢復。
4 服務熔斷實現
基於:springcloud-Feign負載均衡(基於服務端)
4.1 建立專案
在父工程下新建一個普通的maven專案springcloud-provider-dept-hystrix-8001模組,該模組的內容完全複製springcloud-provider-dept-8001
4.2 引入依賴
在springcloud-provider-dept-hystrix-8001模組下引入hystrix的依賴
springcloud-provider-dept-hystrix-8001 : pom.xml
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
4.3 修改配置檔案
調整springcloud-provider-dept-hystrix-8001的yml配置檔案
springcloud-provider-dept-hystrix-8001 : src/main/resources/application.yaml
server:
port: 8001
#mybatis配置
mybatis:
type-aliases-package: com.lv.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
#spring的配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource #資料來源
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
#Eureka的配置,服務註冊到哪裡
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: springcloud-provider-hystrix-dept8001 #修改eureka上的預設描述資訊!
prefer-ip-address: true #改為true後預設顯示的是ip地址而不再是localhost
#info配置
info:
app.name: lv1024-springcloud
company.name: blog.lv1024.com
4.4 修改控制層
修改springcloud-provider-dept-hystrix-8001模組的控制層
springcloud-provider-dept-hystrix-8001 : src/main/java/com/lv/springcloud/controller/DeptController.java
package com.lv.springcloud.controller;
import com.lv.springcloud.pojo.Dept;
import com.lv.springcloud.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
//提供Restful服務!
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id") Long id){
Dept dept = deptService.queryById(id);
if (dept==null){
throw new RuntimeException("id=>"+id+",不存在該使用者,或者該資訊無法找到~");
}
return dept;
}
//備選方法
public Dept hystrixGet(@PathVariable("id") Long id){
return new Dept()
.setDeptno(id)
.setDname("id=>"+id+"沒有對應的資訊,null--@Hystrix")
.setDb_source("no this database in MySql");
}
}
4.5 修改主啟動類
最後修改主啟動類,新增對熔斷的支援註解@EnableCircuitBreaker,和主啟動類的名字
springcloud-provider-dept-hystrix-8001 : src/main/java/com/lv/springcloud/DeptProviderHystrix_8001.java
package com.lv.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
//啟動類
@SpringBootApplication
@EnableEurekaClient// 在服務啟動後自動註冊到Eureka中
@EnableDiscoveryClient //服務發現
@EnableCircuitBreaker//新增對熔斷的支援
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
}
4.6 測試
依次啟動: 7001,7002,springcloud-provider-dept-hystrix-8001,springcloud-consumer-dept-80
執行成功後,訪問一個不存在的id,就會啟動我們編寫的備用方法
上圖是使用了熔斷的效果,如果我們不適用熔斷,來訪問不存在的id時,這一步的測試只需要關掉springcloud-provider-dept-hystrix-8001模組然後開啟springcloud-provider-dept-8001模組
因此,為了避免因某個微服務後臺出現異常或錯誤而導致整個應用或網頁報錯,使用熔斷是必要的
4.7 顯示具體的IP
最後補充一個點:在yaml配置檔案中我們添加了 prefer-ip-address: true ,作用是為了顯示具體的ip而不是localhost,下面的兩張圖是修改的效果對比
使用prefer-ip-address: true
prefer-ip-address: false,預設就是false,不配置就是false
5 服務降級概念描述
5.1 什麼是服務降級
- 服務降級是指 當伺服器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理,或換種簡單的方式處理,從而釋放伺服器資源以保證核心業務正常運作或高效運作。說白了,就是儘可能的把系統資源讓給優先順序高的服務。
- 資源有限,而請求是無限的。如果在併發高峰期,不做服務降級處理,一方面肯定會影響整體服務的效能,嚴重的話可能會導致宕機某些重要的服務不可用。所以,一般在高峰期,為了保證核心功能服務的可用性,都要對某些服務降級處理。比如當雙11活動時,把交易無關的服務統統降級,如檢視螞蟻深林,檢視歷史訂單等等。
- 服務降級主要用於什麼場景呢?當整個微服務架構整體的負載超出了預設的上限閾值或即將到來的流量預計將會超過預設的閾值時,為了保證重要或基本的服務能正常執行,可以將一些 不重要 或 不緊急 的服務或任務進行服務的 延遲使用 或 暫停使用。
- 降級的方式可以根據業務來,可以延遲服務,比如延遲給使用者增加積分,只是放到一個快取中,等服務平穩之後再執行 ;或者在粒度範圍內關閉服務,比如關閉相關文章的推薦。
由上圖可得,當某一時間內服務A的訪問量暴增,而B和C的訪問量較少,為了緩解A服務的壓力,這時候需要B和C暫時關閉一些服務功能,去承擔A的部分服務,從而為A分擔壓力,叫做服務降級。
5.2 服務降級需要考慮的問題
- 那些服務是核心服務,哪些服務是非核心服務?
- 那些服務可以支援降級,那些服務不能支援降級,降級策略是什麼?
- 除服務降級之外是否存在更復雜的業務放通場景,策略是什麼?
5.3 自動降級分類
- 超時降級:主要配置好超時時間和超時重試次數和機制,並使用非同步機制探測回覆情況
- 失敗次數降級:主要是一些不穩定的api,當失敗呼叫次數達到一定閥值自動降級,同樣要使用非同步機制探測回覆情況
- 故障降級:比如要呼叫的遠端服務掛掉了(網路故障、DNS故障、http服務返回錯誤的狀態碼、rpc服務丟擲異常),則可以直接降級。降級後的處理方案有:預設值(比如庫存服務掛了,返回預設現貨)、兜底資料(比如廣告掛了,返回提前準備好的一些靜態頁面)、快取(之前暫存的一些快取資料)
- 限流降級:秒殺或者搶購一些限購商品時,此時可能會因為訪問量太大而導致系統崩潰,此時會使用限流來進行限制訪問量,當達到限流閥值,後續請求會被降級;降級後的處理方案可以是:排隊頁面(將使用者導流到排隊頁面等一會重試)、無貨(直接告知使用者沒貨了)、錯誤頁(如活動太火爆了,稍後重試)。
6 服務降級實現
6.1 新建降級配置類
在springcloud-api模組下的service包中新建降級配置類DeptClientServiceFallBackFactory.java
springcloud-api : src/main/java/com/lv/springcloud/service/DeptClientServiceFallbackFactory.java
package com.lv.springcloud.service;
import com.lv.springcloud.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
//降級
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
public Object create(Throwable throwable) {
return new DeptClientService() {
@Override
public Dept queryById(Long id) {
return new Dept()
.setDeptno(id)
.setDname("id=>"+id+"沒有對應的資訊,客戶端提供了降級的資訊,這個服務現在已經被關閉")
.setDb_source("沒有資料~");
}
@Override
public List<Dept> queryAll() {
return null;
}
@Override
public Boolean addDept(Dept dept) {
return null;
}
};
}
}
6.2 指定降級配置類
在DeptClientService中指定降級配置類DeptClientServiceFallBackFactory
springcloud-api : src/main/java/com/lv/springcloud/service/DeptClientService.java
@Component//註冊到spring容器中
//@FeignClient:微服務客戶端註解,value:指定微服務的名字,這樣就可以使Feign客戶端直接找到對應的微服務
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)//fallbackFactory指定降級配置類
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
@PostMapping("/dept/add")
public Boolean addDept(Dept dept);
}
6.3 開啟服務降級
在springcloud-consumer-dept-feign模組的yaml配置檔案中中開啟降級
springcloud-consumer-dept-feign : src/main/resources/application.yaml
server:
port: 80
#開啟降級feign.hystrix
feign:
hystrix:
enabled: true
#Eureka配置
eureka:
client:
register-with-eureka: false #不向Eureka中註冊自己
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
6.4 測試
依次啟動7001,springcloud-provider-dept-8001,和springcloud-consumer-dept-80
訪問http://localhost/consumer/dept/get/3
資料顯示正常,接下來關閉springcloud-provider-dept-8001模組,再次訪問http://localhost/consumer/dept/get/3
顯示了我們設定的資訊
7 服務熔斷與服務降級的區別
- 服務熔斷—>服務端:某個服務超時或異常,引起熔斷~,類似於保險絲(自我熔斷)
- 服務降級—>客戶端:從整體網站請求負載考慮,當某個服務熔斷或者關閉之後,服務將不再被呼叫,此時在客戶端,我們可以準備一個 FallBackFactory ,返回一個預設的值(預設值)。會導致整體的服務下降,但是好歹能用,比直接掛掉強。
- 觸發原因不太一樣,服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是從整體負荷考慮;管理目標的層次不太一樣,熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而降級一般需要對業務有層級之分(比如降級一般是從最外圍服務開始)
- 實現方式不太一樣,服務降級具有程式碼侵入性(由控制器完成/或自動降級),熔斷一般稱為自我熔斷。
熔斷,降級,限流:
- 限流:限制併發的請求訪問量,超過閾值則拒絕;
- 降級:服務分優先順序,犧牲非核心服務(不可用),保證核心服務穩定;從整體負荷考慮;
- 熔斷:依賴的下游服務故障觸發熔斷,避免引發本系統崩潰;系統自動執行和恢復
8 Dashboard流監控
8.1 建立專案
在父工程下新建一個普通的maven專案springcloud-consumer-hystrix-dashboard模組
8.2 匯入依賴
在springcloud-consumer-hystrix-dashboard模組中匯入依賴
springcloud-consumer-hystrix-dashboard : pom.xml
<?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>springcloud</artifactId>
<groupId>com.lv</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-hystrix-dashboard</artifactId>
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--hystrix-dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--Ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--EUREKA-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--實體類+web-->
<dependency>
<groupId>com.lv</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--熱部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
8.3 編寫配置檔案
在resources目錄下新建一個配置檔案application.yaml,這裡我的電腦9001埠號被佔用了,改用9002
springcloud-consumer-hystrix-dashboard : src/main/resources/application.yaml
server:
port: 9002
8.4 編寫主啟動類
建立包結構com.lv.springcloud,並在該包下新建一個主啟動類DeptConsumerDashboard_9001.java
springcloud-consumer-hystrix-dashboard : src/main/java/com/lv/springcloud/DeptConsumerDashboard_9001.java
package com.lv.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableHystrixDashboard//開啟Dashboard監控
public class DeptConsumerDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerDashboard_9001.class,args);
}
//增加一個Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
8.5 確認要監控的服務中有監控資訊的依賴
這裡我們打算監控springcloud-provider-dept-hystrix-8001模組,所以要檢查該模組的pom檔案中有如下依賴,我們之前應該是新增過了,此時再確認一下
springcloud-provider-dept-hystrix-8001 : pom.xml
<!--actuator完善監控資訊-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
8.6 給要監控的服務新增監控
給springcloud-provider-dept-hystrix-8001模組下的主啟動類編寫如下程式碼,新增監控的servlet
springcloud-provider-dept-hystrix-8001 : src/main/java/com/lv/springcloud/DeptProviderHystrix_8001.java
package com.lv.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
//啟動類
@SpringBootApplication
@EnableEurekaClient// 在服務啟動後自動註冊到Eureka中
@EnableDiscoveryClient //服務發現
@EnableCircuitBreaker//新增對熔斷的支援
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
//增加一個 Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
//訪問該頁面就是監控頁面
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
8.7 測試
依次開啟7001,springcloud-consumer-hystrix-dashboard,springcloud-consumer-dept-80,springcloud-provider-dept-hystrix-8001
訪問http://localhost:9002/hystrix,並填入如下信心後進入監控後臺
進入成功後效果圖如下,該頁面可以實時顯示服務端接受請求的狀態
圖中內容具體的含義如下