一、Sentinel安裝介紹
- 雪崩問題
微服務(wù)調(diào)用鏈路中的某個服務(wù)故障,引起整個鏈路中的所有微服務(wù)都不可用,這就是雪崩。
- 解決雪崩問題的常見方式有四種:
- 超時處理:設(shè)定超時時間,請求超過一定時間沒有響應(yīng)就返回錯誤信息,不會無休止等待
- 艙壁模式:限定每個業(yè)務(wù)能使用的線程數(shù),避免耗盡整個tomcat的資源,因此也叫線程隔離。
- 熔斷降級:由斷路器統(tǒng)計業(yè)務(wù)執(zhí)行的異常比例,如果超出閾值則會熔斷該業(yè)務(wù),攔截訪問該業(yè)務(wù)的一切請求
- 流量控制:限制業(yè)務(wù)訪問的QPS,避免服務(wù)因流量的突增而故障。
安裝控制臺
GitHub下載任意一個版本的jar文件,然后使用
$ java -jar sentinel-dashboard-1.8.4.jar
如果出現(xiàn)錯誤,可以嘗試先指定jdk版本,路徑改成自己本地的目錄地址
$ set Path=K:\java\jdk1.8.0_321\jre\bin
訪問http://localhost:8080/
默認用戶名和密碼都是 sentinel。
微服務(wù)整合Sentinel
- 引入依賴
<!--引入Sentinel依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 配置控制臺地址
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848 # nacos服務(wù)地址
sentinel:
transport:
dashboard: localhost:8080 # Sentinel控制臺地址
- 訪問微服務(wù)的任意端點,觸發(fā)sentinel監(jiān)控,之后即可在控制臺看到信息。
二、Sentinel限流規(guī)則
點擊資源/order/{orderId}后面的流控按鈕,就可以彈出表單。表單中可以添加流控規(guī)則,如下圖所示:

其含義是限制 /order/{orderId}這個資源的單機QPS為1,即每秒只允許1次請求,超出的請求會被攔截并報錯。
- 在添加限流規(guī)則時,點擊高級選項,可以選擇三種流控模式:
- 直接:統(tǒng)計當前資源的請求,觸發(fā)閾值時對當前資源直接限流,也是默認的模式
- 關(guān)聯(lián):統(tǒng)計與當前資源相關(guān)的另一個資源,觸發(fā)閾值時,對當前資源限流
- 鏈路:統(tǒng)計從指定鏈路訪問到本資源的請求,觸發(fā)閾值時,對指定鏈路限流
關(guān)聯(lián)模式
- 關(guān)聯(lián)模式:統(tǒng)計與當前資源相關(guān)的另一個資源,觸發(fā)閾值時,對當前資源限流
-
使用場景:比如用戶支付時需要修改訂單狀態(tài),同時用戶要查詢訂單。查詢和修改操作會爭搶數(shù)據(jù)庫鎖,產(chǎn)生競爭。業(yè)務(wù)需求是有限支付和更新訂單的業(yè)務(wù),因此當修改訂單業(yè)務(wù)觸發(fā)閾值時,需要對查詢訂單業(yè)務(wù)限流。
image.png
當/write資源訪問量觸發(fā)閾值時,就會對/read資源限流,避免影響/write資源。
流控模式-鏈路
鏈路模式:只針對從指定鏈路訪問到本資源的請求做統(tǒng)計,判斷是否超過閾值。
例如有兩條請求鏈路:
- /test1 到 /common
-
/test2 到/common
如果只希望統(tǒng)計從/test2進入到/common的請求,則可以這樣配置:
image.png
- Sentinel默認只標記Controller中的方法為資源,如果要標記其它方法,需要利用@SentinelResource注解,示
@SentinelResource("goods")
public void queryGoods() {
System.out.println("商品信息");
}
- Sentinel默認會將Controller方法做context整合,導(dǎo)致鏈路模式的流控失效,需要修改application.yml,添加配置:
spring:
application:
name: orderservice
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel控制臺地址
web-context-unify: false #關(guān)閉context整合
流控模式有哪些?
- 直接:對當前資源限流
- 關(guān)聯(lián):高優(yōu)先級資源觸發(fā)閾值,對低優(yōu)先級資源限流。
- 鏈路:閾值統(tǒng)計時,只統(tǒng)計從指定資源進入當前資源的請求,是對請求來源的限流
二、流控效果
- 流控效果是指請求達到流控閾值時應(yīng)該采取的措施,包括三種:
- 快速失?。哼_到閾值后,新的請求會被立即拒絕并拋出FlowException異常。是默認的處理方式。
- warm up:預(yù)熱模式,對超出閾值的請求同樣是拒絕并拋出異常。但這種模式閾值會動態(tài)變化,從一個較小值逐漸增加到最大閾值。
-
排隊等待:讓所有的請求按照先后次序排隊執(zhí)行,兩個請求的間隔不能小于指定時長
image.png
流控效果-warm up
warm up也叫預(yù)熱模式,是應(yīng)對服務(wù)冷啟動的一種方案。請求閾值初始值是 threshold / coldFactor,持續(xù)指定時長后,逐漸提高到threshold值。而coldFactor的默認值是3.
例如,我設(shè)置QPS的threshold為10,預(yù)熱時間為5秒,那么初始閾值就是 10 / 3 ,也就是3,然后在5秒后逐漸增長到10
流控效果-排隊等待
當請求超過QPS閾值時,快速失敗和warm up 會拒絕新的請求并拋出異常。而排隊等待則是讓所有請求進入一個隊列中,然后按照閾值允許的時間間隔依次執(zhí)行。后來的請求必須等待前面執(zhí)行完成,如果請求預(yù)期的等待時間超出最大時長,則會被拒絕。
例如:QPS = 5,意味著每200ms處理一個隊列中的請求;timeout = 2000,意味著預(yù)期等待超過2000ms的請求會被拒絕并拋出異常
三、熱點參數(shù)限流
之前的限流是統(tǒng)計訪問某個資源的所有請求,判斷是否超過QPS閾值。而熱點參數(shù)限流是分別統(tǒng)計參數(shù)值相同的請求,判斷是否超過QPS閾值。

代表的含義是:對hot這個資源的0號參數(shù)(第一個參數(shù))做統(tǒng)計,每1秒相同參數(shù)值的請求數(shù)不能超過5
-
在熱點參數(shù)限流的高級選項中,可以對部分參數(shù)設(shè)置例外配置:
image.png
結(jié)合上一個配置,這里的含義是對0號的long類型參數(shù)限流,每1秒相同參數(shù)的QPS不能超過5,有兩個例外:
1.如果參數(shù)值是100,則每1秒允許的QPS為10
2.如果參數(shù)值是101,則每1秒允許的QPS為15 - 注意:熱點參數(shù)限流對默認的SpringMVC資源無效,即必須為用@SentinelResource注解配置的資源才能生效
四、隔離降級
雖然限流可以盡量避免因高并發(fā)而引起的服務(wù)故障,但服務(wù)還會因為其它原因而故障。而要將這些故障控制在一定范圍,避免雪崩,就要靠線程隔離(艙壁模式)和熔斷降級手段了
不管是線程隔離還是熔斷降級,都是對客戶端(調(diào)用方)的保護。
Feign整合Sentinel
SpringCloud中,微服務(wù)調(diào)用都是通過Feign來實現(xiàn)的,因此做客戶端保護必須整合Feign和Sentinel。
- 修改OrderService的application.yml文件,開啟Feign的Sentinel功能
feign:
sentinel:
enabled: true
- 給FeignClient編寫失敗后的降級邏輯
- 步驟一:在feing-api項目中定義類,實現(xiàn)FallbackFactory:
package cn.itcast.feign.clients.fallback;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
/**
* @author ylf
* @version 1.0
*/
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
// 填寫異常后的處理
log.error("查詢用戶異常", throwable);
return new User();
}
};
}
}
- 步驟二:在feing-api項目中的DefaultFeignConfiguration類中將UserClientFallbackFactory注冊為一個Bean:
package cn.itcast.feign.config;
import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfiguration {
@Bean
public Logger.Level logLevel() {
return Logger.Level.BASIC;
}
@Bean
public UserClientFallbackFactory userClientFallbackFactory() {
return new UserClientFallbackFactory();
}
}
- 步驟三:在feing-api項目中的UserClient接口中使用UserClientFallbackFactory:
package cn.itcast.feign.clients;
import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
線程隔離
- 線程池隔離
- 優(yōu)點:支持主動超時,支持異步調(diào)用。
- 缺點:線程的額外開銷大
- 場景: 低扇出
- 信號量隔離
- 優(yōu)點:輕量級,無額外開銷
- 缺點:不支持主動超時,不支持異步調(diào)用
- 場景:高扇出
線程隔離(艙壁模式)
在添加限流規(guī)則時,可以選擇兩種閾值類型:

- QPS:就是每秒的請求數(shù),在快速入門中已經(jīng)演示過
- 線程數(shù):是該資源能使用用的tomcat線程數(shù)的最大值。也就是通過限制線程數(shù)量,實現(xiàn)艙壁模式。
熔斷降級
熔斷降級是解決雪崩問題的重要手段。其思路是由斷路器統(tǒng)計服務(wù)調(diào)用的異常比例、慢請求比例,如果超出閾值則會熔斷該服務(wù)。即攔截訪問該服務(wù)的一切請求;而當服務(wù)恢復(fù)時,斷路器會放行訪問該服務(wù)的請求。

熔斷策略-慢調(diào)用
斷路器熔斷策略有三種:慢調(diào)用、異常比例、異常數(shù)
-
慢調(diào)用:業(yè)務(wù)的響應(yīng)時長(RT)大于指定時長的請求認定為慢調(diào)用請求。在指定時間內(nèi),如果請求數(shù)量超過設(shè)定的最小數(shù)量,慢調(diào)用比例大于設(shè)定的閾值,則觸發(fā)熔斷。例如:
image-20210716145934347.png
解讀:RT超過500ms的調(diào)用是慢調(diào)用,統(tǒng)計最近10000ms內(nèi)的請求,如果請求量超過10次,并且慢調(diào)用比例不低于0.5,則觸發(fā)熔斷,熔斷時長為5秒。然后進入half-open狀態(tài),放行一次請求做測試。
熔斷策略-異常比例、異常數(shù)
-
異常比例或異常數(shù):統(tǒng)計指定時間內(nèi)的調(diào)用,如果調(diào)用次數(shù)超過指定請求數(shù),并且出現(xiàn)異常的比例達到設(shè)定的比例閾值(或超過指定異常數(shù)),則觸發(fā)熔斷。例如:
image.png
image.png
解讀:統(tǒng)計最近1000ms內(nèi)的請求,如果請求量超過10次,并且異常比例不低于0.5,則觸發(fā)熔斷,熔斷時長為5秒。然后進入half-open狀態(tài),放行一次請求做測試。
Sentinel熔斷降級的策略有哪些?
- 慢調(diào)用比例:超過指定時長的調(diào)用為慢調(diào)用,統(tǒng)計單位時長內(nèi)慢調(diào)用的比例,超過閾值則熔斷
- 異常比例:統(tǒng)計單位時長內(nèi)異常調(diào)用的比例,超過閾值則熔斷
- 異常數(shù):統(tǒng)計單位時長內(nèi)異常調(diào)用的次數(shù),超過閾值則熔斷
五、授權(quán)規(guī)則
授權(quán)規(guī)則可以對調(diào)用方的來源做控制,有白名單和黑名單兩種方式。
白名單:來源(origin)在白名單內(nèi)的調(diào)用者允許訪問
黑名單:來源(origin)在黑名單內(nèi)的調(diào)用者不允許訪問
-
給/order/{orderId} 配置授權(quán)規(guī)則:
image.png Sentinel是通過RequestOriginParser這個接口的parseOrigin來獲取請求的來源的。例如,在order-service服務(wù)中我們嘗試從request中獲取一個名為origin的請求頭,作為origin的值:
package cn.itcast.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* @author ylf
* @version 1.0
*/
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 1.獲取請求頭
String origin = httpServletRequest.getHeader("origin");
System.out.println("xxxx" + origin);
// 2.非空判斷
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
- 我們還需要在gateway服務(wù)中,利用網(wǎng)關(guān)的過濾器添加名為gateway的origin頭:
spring:
application:
name: gateway
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway
六、自定義異常結(jié)果
package cn.itcast.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e)
throws Exception {
String msg = "未知異常";
int status = 429;
if (e instanceof FlowException) {
msg = "請求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "請求被熱點參數(shù)限流";
} else if (e instanceof DegradeException) {
msg = "請求被降級了";
} else if (e instanceof AuthorityException) {
msg = "沒有權(quán)限訪問";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
七、規(guī)則持久化
規(guī)則管理模式
- 原始模式:保存在內(nèi)存,重啟服務(wù)就會消失
- pull模式:保存在本地文件或數(shù)據(jù)庫,定時去讀取
- push模式:保存在nacos,監(jiān)聽變更實時更新
實現(xiàn)push模式
一、修改order-service服務(wù)
修改OrderService,讓其監(jiān)聽Nacos中的sentinel規(guī)則配置。
具體步驟如下:
1.引入依賴
在order-service中引入sentinel監(jiān)聽nacos的依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.配置nacos地址
在order-service中的application.yml文件配置nacos地址及監(jiān)聽的配置信息:
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848 # nacos地址
dataId: orderservice-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow # 還可以是:degrade、authority、param-flow
二、修改sentinel-dashboard源碼
SentinelDashboard默認不支持nacos的持久化,需要修改源碼。
1. 解壓
解壓課前資料中的sentinel源碼包:

或者到GitHub下載GitHub
然后并用IDEA打開這個項目,結(jié)構(gòu)如下:

2. 修改nacos依賴
在sentinel-dashboard源碼的pom文件中,nacos的依賴默認的scope是test,只能在測試時使用,這里要去除:

將sentinel-datasource-nacos依賴的scope去掉:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
3. 添加nacos支持
在sentinel-dashboard的test包下,已經(jīng)編寫了對nacos的支持,我們需要將其拷貝到main下。

4. 修改nacos地址
然后,還需要修改測試代碼中的NacosConfig類:

修改其中的nacos地址,讓其讀取application.properties中的配置:

在sentinel-dashboard的application.properties中添加nacos地址配置:
nacos.addr=localhost:8848
5. 配置nacos數(shù)據(jù)源
另外,還需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2類:

讓我們添加的Nacos數(shù)據(jù)源生效:

6. 修改前端頁面
接下來,還要修改前端頁面,添加一個支持nacos的菜單。
修改src/main/webapp/resources/app/scripts/directives/sidebar/目錄下的sidebar.html文件:

將其中的這部分注釋打開:

修改其中的文本:

7. 重新編譯、打包項目
運行IDEA中的maven插件,編譯和打包修改好的Sentinel-Dashboard:

8.啟動
啟動方式跟官方一樣:
java -jar sentinel-dashboard.jar
如果要修改nacos地址,需要添加參數(shù):
java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar







