1.Sentinel是什麼?
隨着分佈式系統變得越來越流行,服務之間的可靠性變得比以往任何時候都更加重要。Sentinel是強大的流控制組件,以“流”爲切入點,涵蓋多個領域,包括流控制,併發限制,電路中斷和自適應系統保護,以確保微服務的可靠性。
一句話講就是Spring Cloud Alibaba
用來替換之前的Hystrix
的技術。
2.有什麼用?
用來做系統流量控制、熔斷降級、系統的負載保護等。
3.下載並啓動Sentinel
1.下載地址
2.啓動
java -jar sentinel-dashboard-1.7.2.jar
3.訪問Dashboard,用戶名密碼都是sentinel
4.測試準備
1.新建一個springboot項目
2.引入依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- sentinel-datasource-nacos 後續持久化用 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</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>
3.啓動類添加註解@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaSentinelServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AlibabaSentinelServiceApplication.class, args);
}
}
4.application.properties
配置
server.port=8401
spring.application.name=cloudalibaba-sentinel-service
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name}
spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.dsl.nacos.data-type=json
spring.cloud.sentinel.datasource.dsl.nacos.rule-type.=flow
management.endpoints.web.exposure.include=*
5.提供測試接口
@RestController
public class FlowLimitController {
private static Logger log = LoggerFactory.getLogger(FlowLimitController.class);
@GetMapping("/testA")
public String testA(){
// try {
// TimeUnit.MILLISECONDS.sleep(800);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "testA-----";
}
@GetMapping("/testB")
public String testB(){
log.info(Thread.currentThread().getName() + "...testB ");
return "testB -----";
}
}
6.啓動sentinel
和nacos
不懂nacos可以參考
5.測試流控規則
- 資源名:唯一名稱,默認請求路徑
- 針對來源:Sentinel可以針對調用者進行限流,填寫微服務名,默認
default
(不區分來源) - 閾值類型/單機閾值
- QPS(每秒鐘的請求數量):當調用該api的QPS達到閾值的時候,進行限流
- 線程數:當調用該api的線程數達到閾值的時候,進行限流
- 流控模式
- 直接:api達到限流條件時,直接限流
- 關聯:當關聯的資源達到閾值時就限流自己。
A接口與B接口關聯,當B接口到達閾值,讓A接口限流起到保護B接口的作用。 例如支付接口與下單接口,當支付接口到達閾值,讓下單接口限流,起到保護支付接口的作用。 - 鏈路:只記錄指定鏈路上的流量(指定資源入口資源進來的流量,如果達到閾值,就進行限流)
- 流控效果
- 快速失敗:直接失敗拋異常
- Warm up:根據codeFactor(冷加載因子,默認3)的值,從閾值/codeFactor,經過預熱時長,才達到設置的QPS閾值。
一般用於類似秒殺的功能。 - 排隊等候:勻速排隊,讓請求以勻速的速度通過,閾值類型必須設置爲QPS,否則無效。
5.1測試QPS到達閾值
配置如下:
當你連續點擊看到如下限流提示(1s請求數量超過配置閾值)
5.2測試線程數到達閾值
配置如下:
爲了方便測試讓每個被調用的方法睡眠一會
@GetMapping("/testA")
public String testA(){
//測試線程閾值
try {
TimeUnit.MILLISECONDS.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "testA-----";
}
當你連續點擊看到如下限流提示(1s請求數量超過配置閾值)
5.3測試關聯限流
配置如下
用postman模擬與/testA
關聯的/testB
到達閾值
postman啓動之後調用/testA
可以看到該接口被限流。
5.4測試Warm up(限流冷啓動)
配置如下(訪問流量忽然增大時,從閾值/冷加載因子 開始經過預熱時長達到每秒可訪問閾值,即從流量增大時剛開始可以支持每秒2次訪問,經過3s可以支持每秒6次訪問接口),默認冷加載因子(coldFactor)爲3
可以點擊訪問/testA
接口測試,開始慢慢慢點擊沒問題,然後加快速度會出現限流,過一會之後,限流就沒有了。
5.5.排隊等候
配置如下
爲了方便查看調用,接口中打印線程名稱
@GetMapping("/testB")
public String testB(){
log.info(Thread.currentThread().getName() + "...testB ");
return "testB -----";
}
重啓項目調用,可以在控制檯中看到調用記錄
6.測試降級規則
- RT:
- 當資源的平均響應時間超過閾值(這裏閾值即自己配置的毫秒值)且在時間窗口內通過的請求>= 5,兩個條件同時滿足觸發熔斷降級
- 窗口期後關閉斷路器。
- RT最大4900(更大通過 -Dcsp.sentinel.statistic.max.rt=xxx 來配置)
- 異常比例:
QPS>=5且異常比例(秒級統計)超過閾值時,觸發降級;時間窗口結束後,關閉降級。 - 異常數:
異常數(分鐘統計)超過閾值,觸發降級;時間窗口結束後,關閉降級
Sentienl熔斷降級會在調用調用鏈路中某個資源出現不穩定時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其他的資源而導致級聯錯誤。
當資源降級後,在接下來的降級時間窗口之內,對該資源的調用自動熔斷(默認行爲是拋出DegradeException)
6.1測試RT
1.添加測試接口
@GetMapping("/testD")
public String testD(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 測試RT");
return "testD -----";
}
爲了讓平均響應時間增大,在代碼中加入sleep休眠。
2.利用JMater進行壓力測試
1s 10個線程訪問/testD
接口
3.配置如下
4.啓動JMeter,自己調用/testD
,可以看到該接口已被熔斷降級。
熔斷分析:1.平均響應時間(1000ms)超出閾值(200ms) 2.在時間窗口內通過請求10*5=50>=5。滿足兩個條件,所以發生熔斷降級
6.2測試異常比例
資源每秒請求量>=5&&每秒異常總數佔通過量的比值超過閾值之後,資源進入降級狀態,即在接下的時間窗口之內,對這個方法的調用都會自動的返回(服務熔斷)。異常比率閾值範圍[0.0,1.0],代表0%-100%。
1.提供測試異常比例接口
@GetMapping("/testException")
public String testException(){
log.info("testException 異常比例");
int age = 10 /0 ;
return "testException -----";
}
2.利用JMater進行壓力測試,除接口外其他配置同上面測試RT
3.sentinel配置
4.重啓項目並啓動JMeter進行測試,調用/testException
可以服務熔斷提示信息
6.3測試異常數
當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。注意由於統計時間窗口是分鐘級別的,若時間窗口小於60s,則結束熔斷狀態後可能再進入熔斷狀態。
1.提供測試接口
@GetMapping("/testExceptionCount")
public String testExceptionCount(){
log.info("testExceptionCount 異常數");
int age = 10 /0 ;
return "testExceptionCount -----";
}
2.sentinel配置
3.重啓項目進行測試
前面5次訪問都報錯,後面訪問進入熔斷後降級。
熔斷後降級如下圖
7.熱點參數限流
7.1普通熱點參數限流
1.提供接口進行測試
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2){
return "testHotKey -----";
}
public String dealTestHotKey(String p1, String p2, BlockException blockException){
return "dealTestHotKey---------";
}
2.sentinel配置
上面的配置第一個參數p1,當QPS超過1秒1次點擊後馬上被限流。
3.重啓項目測試
7.2參數例外項熱點限流
1.sentinel配置如下
以上配置含義,當第一個參數p1的值爲5時接口/testHotKey
的流量閾值爲200
注意:
@SentinelResource處理的是控制檯配置的違規情況,有blockHandler方法配置的兜底處理。
但是@SentinelResource不管代碼中出現的運行時期異常(RuntimeException)
8.@SentinelResource配置
8.1按資源名稱或URL地址限流加後續處理
1.提供接口
/**
* (違反sentinel配置)手動配置兜底處理blockHandler
* @return
*/
@GetMapping(value = "/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200, "按資源名稱限流測試OK");
}
public CommonResult handleException(BlockException blockException){
return new CommonResult<>(444, blockException.getClass().getCanonicalName()+"\t服務不可用" );
}
/**
* 默認處理
* @return
*/
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl(){
return new CommonResult(200, "by url限流測試OK");
}
2.sentinel配置
資源名稱:
url:
3.重新啓動測試
訪問byResource
當每秒訪問超過1次時,使用自定義的兜底處理。
訪問/rateLimit/byUrl
當每秒訪問超過1次時,使用默認處理。
以上配置存在問題:
- 1.系統默認的,沒有體現我們自己的業務需求
- 2.依照現有條件,我們自定義的處理方法和業務代碼耦合在一塊,不直觀
- 3.每個業務方法都有一個兜底的,代碼膨脹加劇
- 4.全局同一處理方法沒有體現
8.1解決存在的問題
1.提供接口
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444, "客戶自定義,global handlerException---1");
}
public static CommonResult handlerException2(BlockException exception) {
return new CommonResult(444, "客戶自定義,global handlerException---2");
}
}
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200, "客戶自定義 限流測試OK");
}
2.sentinel中配置
3.重啓測試
9.整合Ribbon和OpenFeign
9.1準備
1.創建三個springboot項目,分別爲alibaba-consumer2、alibaba-provider3、alibaba-provider4(alibaba-provider3/alibaba-provider4一樣)
由於東西過多這裏不羅列,詳細參考https://github.com/xiaoxiaoshou/springclouddemo
9.2Ribbon系列
主要示例代碼:
@RestController
@Slf4j
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class CircleBreakerController {
private static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback") //沒有配置
// @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只負責業務異常
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") // 配置了blockHandler,只負責sentinel控制檯配置違規
@SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler") // 配置了blockHandler和fallback
// @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) // 忽略運行時IllegalArgumentException異常不進行自定義處理
public CommonResult<Payment> fallback(@PathVariable("id") Long id){
CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException,非法參數異常");
}else if(commonResult.getData() == null){
throw new NullPointerException("NullPointerException,該ID沒有記錄,空指針異常");
}
return commonResult;
}
// 本例是fallback
public CommonResult handlerFallback(Long id, Throwable e){
Payment payment = new Payment(id, null);
return new CommonResult(444, "兜底異常handler,exception內容"+e.getMessage(), payment);
}
public CommonResult blockHandler(Long id, BlockException exception){
Payment payment = new Payment(id, null);
return new CommonResult<>(445, "blockHandler-sentinel 限流,無此流水號:blockException" + exception.getMessage(), payment);
}
}
fallback
對應方法(handlerFallback方法)處理代碼運行時期異常blockHandler
對應方法(blockHandler)處理Sentinel中違規exceptionsToIgnore
忽略運行時期某個異常不進行自定義處理
配置Sentinel(對資源fallback閾值爲1)並啓動項目測試,可以看到既可以處理調用過程中對應Sentinel違規也可以處理運行時期異常。
9.3 OpenFeign系列
1.主要配置
#激活sentinel對feign的支持
feign:
sentinel:
enabled: true
2.綁定對應服務
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping("/paymentSQL/{id}")
CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444, "fallback");
}
}
3.提供訪問接口
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
4.重啓項目即可衝過consumer接口調用第三方接口
各熔斷器的比較:
10.規則持久化
當你每次重啓對應服務,你會發現在Sentinel中配置對應的規則就沒有了,在生產環境中我們需要配置規則的持久化(持久化工具都可以,官方推薦Nacos)。
1.引入依賴
<!-- sentinel-datasource-nacos 後續持久化用 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.配置文件配置
spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name}
spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.dsl.nacos.data-type=json
spring.cloud.sentinel.datasource.dsl.nacos.rule-type=flow
3.在nacos中配置
[
{
"resource":"/rateLimit/byUrl",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
配置項含義:
- resource:資源名稱
- limitApp:來源應用
- grade:閾值類型,0表示線程數,1表示QPS
- count:單機閾值
- strategy:流控模式,0表示直接,1表示級聯,2表示鏈路
- controlBehavior:流控效果,0表示快速失敗,1表示Warm Up,2表示排隊等候
- clusterMode:是否集羣