SpringCloud Alibaba微服務實戰五 – 限流熔斷

  • 2019 年 12 月 17 日
  • 筆記

導讀:本篇作為SpringCloud Alibaba微服務實戰系列的第五篇,主要內容是使用Sentinel給微服務加上限流熔斷功能,防止異常情況拖垮應用服務。系列文章,歡迎持續關注。

簡介

Sentinel是面向分散式服務框架的輕量級流量控制框架,主要以流量為切入點,從流量控制,熔斷降級,系統負載保護等多個維度來維護系統的穩定性。在SpringCloud體系中,sentinel主要是為了替換原Hystrix的功能,與Hystrix相比,sentinel的隔離級別更加精細,提供的Dashboard可以在線更改限流熔斷規則,而且使用也越加方便。要了解更多詳細資訊請移步至Sentinel官網。

基礎準備

要使用Sentinel提供的限流熔斷能力,需要先做如下準備:

  • 安裝Sentinel 這部分內容我已經在第一期SpringCloud Alibaba微服務實戰一 – 基礎環境準備中提過,大家可以翻閱查看。
  • 引入Sentinel 在需要配置限流熔斷服務的POM文件中引入Sentinel組件
<!--Sentinel-->  <dependency>  	<groupId>org.springframework.cloud</groupId>  	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>  </dependency>  
  • 自定義資源@SentinelResource我們只需要在相關方法上加上@SentinelResource註解,讓其可以成為sentinel識別的資源即可。如:
@GetMapping("/account/getByCode/{accountCode}")  @SentinelResource(value = "getByCode")  public ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode){  	log.info("get account detail,accountCode is :{}",accountCode);  	AccountDTO accountDTO = accountService.selectByCode(accountCode);  	return ResultData.success(accountDTO);  }  
  • 在配置文件中添加sentinel的服務端地址
server:    port: 8010  spring:    application:      name: account-service    cloud:      nacos:        discovery:          server-addr: 192.168.0.107:8848/      sentinel:        transport:        # sentinel服務端地址          dashboard: 192.168.0.107:8858        # 取消延遲載入        eager: true  

經過以上幾步我們準備好了使用Sentinel的基礎環境,接下來我們看看限流熔斷的具體配置。

限流

概念說明

生產者accout-service是一個核心服務,我們通過壓測得出服務的最大負載能力為60。如果某個時間account-service的請求數飆升達到了600,那服務肯定就直接gg了。所以為了保護我們的accout-service,我們會給它配置一個限流規則,如果每秒鐘有超過60的請求那不好意思我直接丟掉不處理了,然後丟給消費者一個異常,想拖垮我,哼,沒門!。

總而言之,限流是通過限制調用方對自己的調用,起到保護自己系統的效果。

限流配置

理想是豐滿的,現實是骨感的。由於本人對Jmeter之類的壓測工具不是很精通所以為了方便測試,我們就將accout-service的QPS單機閾值設置成5,如果每秒QPS超過5,直接丟棄。

這裡的資源名就是我們使用@SentinelResource註解自定義的資源。

打開瀏覽器,快速刷新瀏覽器,當每秒請求數超過5時會看到如下錯誤:

在後端服務日誌中你會看到如下的錯誤日誌:

2019-12-10 14:22:31,948 ERROR [dispatcherServlet]:175 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause  com.alibaba.csp.sentinel.slots.block.flow.FlowException: null  

不要慌,這說明我們的目的達到了,限流成功!

自定義異常

我們可以通過@SentinelResource中添加blockHandler參數,給其添加自定義異常方法。如:

@GetMapping("/account/getByCode/{accountCode}")  @SentinelResource(value = "getByCode",blockHandler = "handleException")  public ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode){  	log.info("get account detail,accountCode is :{}",accountCode);  	AccountDTO accountDTO = accountService.selectByCode(accountCode);  	return ResultData.success(accountDTO);  }  /**   * 自定義異常策略   * 返回值和參數要跟目標函數一樣,參數可以追加BlockException   */  public ResultData<AccountDTO> handleException(String accountCode,BlockException exception){  	log.info("flow exception{}",exception.getClass().getCanonicalName());  	return ResultData.fail(900,"達到閾值了,不要再訪問了!");  }  

注意,自定義的異常方法的參數和返回值要跟目標方法一樣,參數可以追加BlockException

效果如下:

比之前的那個錯誤頁優雅多了有木有!

持久化配置

由於Sentinel的配置默認是放在記憶體中的,每當應用重啟或者sentinel重啟都會丟失數據,我們這裡使用Nacos作為配置中心持久化限流配置。

  • 修改pom文件,引入sentinel-datasource-nacos組件
<dependency>  	<groupId>com.alibaba.csp</groupId>  	<artifactId>sentinel-datasource-nacos</artifactId>  </dependency>  
  • 修改application.yml,配置sentinel的數據源
spring:    cloud:      sentinel:        datasource:          ds:            nacos:              server-addr: 10.0.10.48:8848              data-id: ${spring.application.name}-sentinel              group-id: DEFAULT_GROUP              rule-type: flow  
  • 在nacos中建立限流配置account-service-sentinel(配置格式設置成json)
[      {          "resource": "getByCode",          "limitApp": "default",          "grade": 1,          "count": 3,          "strategy": 0,          "controlBehavior": 0,          "clusterMode": false      }  ]  

可以看到上面配置規則是一個數組類型,數組中的每個對象是針對每一個保護資源的配置對象,每個對象中的屬性解釋如下:

resource:資源名,即限流規則的作用對象 limitApp:流控針對的調用來源,若為 default 則不區分調用來源 grade:限流閾值類型(QPS 或並發執行緒數);0代表根據並發數量來限流,1代表根據QPS來進行流量控制 count:限流閾值 strategy:調用關係限流策略 controlBehavior:流量控制效果(直接拒絕、Warm Up、勻速排隊) clusterMode:是否為集群模式

  • 進入sentinel查看dashboard,發現sentinel自動獲取nacos的配置
  • 頻繁刷新瀏覽器調用介面,驗證介面是否正常限流

熔斷

概念說明

消費者order-service需要先調用product-service獲取具體的product,然後再處理其他的業務邏輯。但是這個product-service介面不是很穩定,經常拋出異常;或者是響應緩慢,導致order-service的響應變慢;如果置之不理,order-service可能會被product-service拖垮。這時候為了保護order-service,我們需要對product-service介面進行熔斷。

image.png

一言以蔽之:熔斷是通過限制自己對外部系統的調用, 起到節約響應時間、維護鏈路穩定的作用。

熔斷配置

Sentinel中的熔斷降級有三個降級策略:

  • RT(平均響應時間):當資源的平均響應時間超過閾值之後,資源進入准降級狀態。接下來如果持續進入 5 個請求,它們的 RT 都持續超過這個閾值,那麼在接下的時間窗口之內,對這個方法的調用都會自動拋出 DegradeException 異常。在下一個時間窗口到來時, 會接著再放入5個請求, 再重複上面的判斷.
  • 異常比例 當資源的每秒異常總數占通過量的比值超過閾值之後,資源進入降級狀態,即在接下的時間窗口之內,對這個方法的調用都會自動地拋出DegradeException異常。異常比率的閾值範圍是 [0.0, 1.0],代表 0% – 100%。
  • 異常數 當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。

首先我們對原介面進行改造,讓其直接拋出Runtimeexception

@GetMapping("/product/getByCode/{productCode}")  @SentinelResource(value = "/product/getByCode",fallback = "fallbackHandler")  public ResultData<ProductDTO> getByCode(@PathVariable String productCode){  	log.info("get product detail,productCode is :{}",productCode);  	ProductDTO productDTO = productService.selectByCode(productCode);  	throw new RuntimeException("error");  //        return ResultData.success(productDTO);  }  

這裡我們將product-service設置如下的熔斷規則:

如果/product/getByCode的異常率超過50%,那麼接下來2秒內直接觸發熔斷降級,默認情況會拋出DegradeException異常,如:

2019-12-10 19:35:53,764 ERROR [dispatcherServlet]:175 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause  com.alibaba.csp.sentinel.slots.block.degrade.DegradeException: null  

自定義異常

自定義熔斷異常跟限流異常類似,我們使用fallback屬性指定自定義異常的方法,如:

@SentinelResource(value = "/product/getByCode",fallback = "fallbackHandler")  public ResultData<ProductDTO> getByCode(@PathVariable String productCode){   ...  }  /**   * 自定義熔斷異常   * 返回值和參數要跟目標函數一樣   */  public ResultData<ProductDTO> fallbackHandler(String productCode){  	return ResultData.fail(800,"服務被熔斷了,不要調用!");  }  

注意,自定義的異常方法的參數和返回值要跟目標方法一樣

效果如下:

持久化配置

  • 引入sentinel-datasource-nacos組件,跟限流一樣配置即可
  • 修改application.yml,配置sentinel的數據源
spring:    cloud:      sentinel:        datasource:          ds:            nacos:              server-addr: 192.168.0.106:8848              data-id: ${spring.application.name}-sentinel-degrade              group-id: DEFAULT_GROUP              rule-type: degrade  
  • 在nacos中建立配置文件product-service-sentinel-degrade,做如下配置
[      {      "resource": "/product/getByCode",      "count": 0.5,      "grade": 1,      "passCount": 0,      "timeWindow": 2    }  ]  

可以看到上面配置規則是一個數組類型,數組中的每個對象是針對每一個保護資源的配置對象,每個對象中的屬性解釋如下:

resource:資源名,即降級規則的作用對象 count:閾值 grade:降級模式 0:RT 1:異常比例 2:異常數 timeWindow:時間窗口(單位秒)

  • 進入sentinel查看dashboard,發現sentinel自動獲取nacos的配置

血與淚

大家在使用sentinel過程中如果出現Failed to fetch metric from的錯誤,具體表現如下:

Failed to fetch metric from <http://192.168.136.1:8719/metric?startTime=1563865044000&endTime=1563865050000&refetch=false>   (ConnectionException: Connection refused: no further information)  

這個時候你需要去檢查下sentinel控制台的服務列表,確認是否跟你ip一致。(我之前是裝過虛擬機,sentinel一直抓取的是我虛擬的ip,不知道為什麼。。。)

如果發現監聽的地址不對的話,可以在sentinel客戶端配置中加入客戶端ip配置

spring:    cloud:      sentinel:        transport:          client-ip: 192.168.0.108  

至此我們已經給我們的微服務加上了限流熔斷保護,再也不用擔心異常流量的衝擊,下游系統不穩定導致自身服務不可用了。那麼本期的「SpringCloud Alibaba微服務實戰五 – 限流熔斷」篇也就該結束啦,咱們下期有緣再見!

系列文章

  • SpringCloud Alibaba微服務實戰一 – 基礎環境準備
  • SpringCloud Alibaba微服務實戰二 – 服務註冊
  • SpringCloud Alibaba微服務實戰三 – 服務調用
  • SpringCloud Alibaba微服務實戰四 – 版本管理