SpringCloud Alibaba Sentinel實現熔斷與限流
限流與降級
限流 blockHandler
降級 fallback
降級需要運行時出現異常才會觸發,而限流一旦觸發,你連運行的機會都沒有,當然就不會降級。
也就是說,兩者如果同時觸發,那么一定是限流觸發(降級連機會都沒有)。
Sentiel
官網
https://github.com/alibaba/Sentinel
中文:https://github.com/alibaba/Sentinel/wiki/介紹
是什么
一句話解釋就是我們之前講過的hystrix
去哪下
https://github.com/alibaba/Sentinel/releases
能干嘛
怎么玩
官方文檔:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
服務中的各種問題
- 服務雪崩
- 服務降級
- 服務熔斷
- 服務限流
安裝Sentiel控制台
sentinel組件由兩部分構成
- 后台
- 前台8080
安裝步驟
下載
https://github.com/alibaba/Sentinel/releases
運行命令
前提
java8環境OK
8080端口不能被占用
命令
java -jar sentinel-dashboard-1.7.0.jar
訪問sentinel管理界面
http://localhost:8080
登錄賬號密碼均為sentinel
初始化演示功能
啟動Nacos8848成功
http://localhost:8848/nacos/#/login
Module
cloudalibaba-sentinel-service8401
POM
<dependencies>
<!-- SpringCloud ailibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-->
<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>
<!--熱部署-->
<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>
YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinal-service
cloud:
nacos:
discovery:
#Nacos服務注冊中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentin dashboard地址
dashboard: localhost:8080
# 默認8719端口,假如被占用了會自動從8719端口+1進行掃描,直到找到未被占用的 端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class,args);
}
}
業務類FlowLimitController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "----testA";
}
@GetMapping("/testB")
public String testB() {
return "----testB";
}
}
啟動Sentinel8080
java -jar sentinel-dashboard-1.7.0.jar
啟動微服務8401
啟動8401微服務后台查看sentinel控制台
空空如也,啥也沒有
Sentinel采用懶加載說明
執行一次訪問
http://localhost:8401/testA
http://localhost:8401/testB
效果
結論
sentinel8080正在監控微服務8401
流控規則
基本介紹
進一步解釋說明
流控模式
直接(默認)
直接->快速失敗
系統默認
配置及說明
測試
- 快速點擊訪問http://localhost:8401/testA
- 結果:Blocked by Sentinel(flow limiting)
- 思考???
直接調用默認報錯信息,技術方面ok,but,是否應該有我們自己的后續處理
類似有個fallback的兜底方法
關聯
應用場景:比如支付時達到閾值,可以從源頭上比如購買界面,進行限流
類比:下流洪災,上流關水
是什么
- 當關聯的資源達到閾值時,就限流自己
- 當與A關聯的資源B達到閾值后,就限流自己
- B惹事,A掛了
配置A
postman模擬並發密集訪問testB
訪問B成功
postman里新建多線程集合組
將訪問地址添加進新線程組
RUN
大批量線程高並發訪問B,導致A失效了
運行后發現testA掛了
- 點擊訪問A
- 結果:Blocked by Sentinel(flow limiting)
鏈路
- 多個請求調用同一個微服務
- 家庭作業試試
流控效果
直接->快速失敗(默認的流控處理)
- 直接失敗,拋出異常
Blocked by Sentinel(flow limiting) - 源碼
com.alibaba.csp.sentinel.slots.block.controller.DefaultController
預熱
說明
公式:閾值除以coldFactor(默認值為3),經過預熱時長后才會達到閾值
官網
- 默認coldFactor為3,即請求QPS從threshold/3開始,經預熱時長逐漸升至設定的QPS閾值
- 限流 冷啟動
https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8
源碼
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
WarmUp配置
多次點擊http://localhost:8401/testB
剛開始不行,后續慢慢OK
應用場景
如:秒殺系統在開啟的瞬間,會有很多流量上來,很有可能把系統打死,潁熱方式就是把為了保護系統,可慢慢的把流量放進來,慢慢的把閥值增長到設置的閥值。
排隊等待
勻速排隊,閾值必須設置為QPS
官網
源碼
com.ailibaba.csp.sentinel.slots.block.controller.RateLimiterController
測試
降級規則
官網
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
基本介紹
進一步說明
Sentinel的斷路器是沒有半開狀態的
Sentinel熔斷隆級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高) ,對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。
當資源被降級后,在接下來的降級時間窗口之內,對該資源的調用都自動熔斷(默認行為是拋出DegradeException)。
半開的狀態系統自動去檢測是否請求有異常,沒有異常就關閉斷路器恢復使用,有異常則繼續打開斷路器不可用,具體參考Hystrix
復習Hystrix
降級策略實戰
RT
是什么
測試
代碼
配置
jmeter壓測
結論
異常比例
是什么
測試
代碼
配置
jmeter
結論
異常數
是什么
異常數是按照分鍾統計的
測試
代碼
同異常比例
配置
jmeter
熱點key限流
基本介紹
是什么
官網
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
承上啟下復習start
SentinelResource
兜底方法
分為系統默認和客戶自定義,兩種
之前的case,限流出問題后,都是用sentine|系統默認的提示: Blocked by Sentinel (flow limiting)
我們能不能自定?類似hystrix,某個方詘問題了,就找對應的兜底降級方法?
結論
從HystrixCommand到@SentinelResource
代碼
com.alibaba.csp.sentinel.slots.block.BlockException
配置
配置
1
- @SentinelResource(value = "testHotKey")
- 異常打到了前台用戶界面看到,不友好
2
- @SentinelResource(value = "testHotKey",blockHandler="dealHandler_testHotKey")
- 方法testHotKey里面第一個參數只要QPS超過每秒一次,馬上降級處理
- 用了我們自己定義的
測試
- × error
http://localhost:8401/testHotKey?p1=abc - × error
http://localhost:8401/testHotKey?p1=abc&p2=33 - √ right
http://localhost:8401/testHotKey?p2=abc
參數例外項
上述案例演示了第一個參數p1,當QPS超過1秒1次點擊后馬上被限流
特殊情況
普通
- 超過1秒鍾一個后,達到閾值1后馬上被限流
- 我們期望p1參數當它是某個特殊值時,它的限流值和平時不一樣
- 特例:假如當p1的值等於5時,它的閾值可以達到200
配置
添加
按鈕不能忘
測試
- √ http://localhost:8401/testHotKey?p1=5
- × http://localhost:8401/testHotKey?p1=3
- 當p1等於5的時候,閾值變為200
- 當p1不等於5的時候,閾值就是平常的1
前提條件
熱點參數的注意點,參數必須是基本類型或者String
其他
手賤添加異常看看o(╥﹏╥)o
后面講
@SentinelResource
處理的是Sentinel控制台配置的違規情況,有blockHandler方法配置的兜底處理;
RuntimeException
int age = 10/0, 這個是java運行時報出的運行時異常RunTimeException, @SentinelResource不管
總結
@SentinelResource主管配置出錯,運行出錯該走異常走異常
系統規則
是什么
https://github.com/alibaba/Sentinel/wiki/ 系統自適應限流
各項配置說明
配置全局QPS
不合適,使用危險,一竹竿打死一船人
@SentinelResource
按資源名稱限流+后續處理
啟動nacos成功
啟動Sentinel成功
Module
cloudalibaba-sentinel-service8401
pom
<dependency><!-- 引用自己定義的api通用包,可以使用Payment支付Entity -->
<groupId>com.eiletxie.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
yml
業務類RateLimitController
@RestController
public class RateLimitController {
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200,"按照資源名稱限流測試",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception) {
return new CommonResult(444,exception.getClass().getCanonicalName() + "\t 服務不可用");
}
}
主啟動
配置流控規則
配置步驟
圖形配置和代碼關系
表示1秒鍾內查詢次數大於1,就跑到我們自定義的限流處,限流
測試
- 1秒鍾點擊1下,OK
- 超過上述,瘋狂點擊,返回了自己定義的限流處理信息,限流發生
額外問題
- 此時關閉服務8401看看
- Sentinel控制台,流控規則消失了?
臨時?持久?
按照Url地址限流+后續處理
通過訪問URL來限流,會返回Sentinel自帶默認的限流處理信息
業務類RateLimitController
訪問一次
Sentinel控制台配置
測試
- 瘋狂點擊http://localhost:8401/rateLimit/byUrl
- 結果
上面兜底方案面臨的問題
- 系統默認的, 沒有體現我們自己的業務要求。
- 依照現有條件,我們自定義的處理方法又和業務代碼耦合在-塊,不直觀。
- 每個業務方法都添加一個兜底的,那代碼膨脹加劇。
- 全局統一的處理方法沒有體現。
客戶自定義限流處理邏輯
創建CustomerBlockHandler類用於自定義限流處理邏輯
自定義限流處理類
CustomerBlockHandler
RateLimitController
啟動微服務后再調用一次
http://localhost:8401/rateLimit/customerBlockHandler
Sentinel控制台配置
測試后我們的自定義出來了
進一步說明
更多注解說明
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
多說一句
Sentinel主要有三個核心Api
- sphU定義資源
- Tracer定義統計
- ContextUtil定義了上下文
服務熔斷功能
sentinel整合ribbon+openFeign+fallback
Ribbon系列
啟動nacos和sentinel
提供者9003/9004
新建cloudalibaba-provider-payment9003/9004
POM
<dependencies>
<!-- SpringCloud ailibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency><!-- 引用自己定義的api通用包,可以使用Payment支付Entity -->
<groupId>com.eiletxie.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
<!--熱部署-->
<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>
YML
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
主啟動
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class,args);
}
}
業務類
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, Payment > map = new HashMap<>();
static {
map.put(1L,new Payment(1L,"1111"));
map.put(1L,new Payment(2L,"2222"));
map.put(1L,new Payment(3L,"3333"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
Payment payment = map.get(id);
CommonResult<Payment> result = new CommonResult<>(200,"from mysql,serverPort: " + serverPort,payment);
return result;
}
}
測試地址
http://localhost:9003/paymentSQ/
消費者84
新建cloudalibaba-consumer-nacos-order84
POM
與提供者pom一致
YML
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
#消費者將去訪問的微服務名稱
server-url:
nacos-user-service: http://nacos-payment-provider
主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class,args);
}
}
業務類
ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
CircleBreakerController
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id,CommonResult.class,id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgument ,非法參數異常...");
}else if(result.getData() == null) {
throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
}
return result;
}
}
修改后請重啟微服務
- 熱部署對java代碼級生效及時
- 對@SentinelResource注解內屬性,有時效果不好
目的
- fallback管運行異常
- blockHandler管配置違規
測試地址
http://localhost:84/consumer/fallback/3
沒有任何配置
直接給用戶error界面。,,不太友好
只配置fallback
@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")
@SentinelResource(value = "fallback",fallback = "handlerFallback")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id,CommonResult.class,id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgument ,非法參數異常...");
}else if(result.getData() == null) {
throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult(444,"異常handlerFallback,exception內容: " + e.getMessage(), payment);
}
結果
只配置blockHandler
編碼
@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")
// @SentinelResource(value = "fallback",fallback =
@SentinelResource(value = "fallback",blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id,CommonResult.class,id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgument ,非法參數異常...");
}else if(result.getData() == null) {
throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
}
return result;
}
public CommonResult blockHandler(@PathVariable Long id,BlockException e) {
Payment payment = new Payment(id,"null");
return new CommonResult(444,"blockHandler-sentinel 限流,BlockException: " + e.getMessage(), payment);
}
記得配置一個qps的控制
結果
同時配置fallback和blockHandler
異常忽略
Feign系列
修改84模塊
- 84消費者調用提供者9003
- Feign組件一般是消費端
POM
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
YML
業務類
- 帶@FeignClient注解的業務接口
- fallback = PaymentFallbackService.class
- Controller
主啟動
添加@EnableFeignClients啟動Feign的功能
http://localhost:84/consumer/paymentSQL/2
測試84調用9003,此時故意關閉9003微服務提供者,看84消費自動降級,不會被耗死
熔斷框架比較
Sentinel | Hystrix | resilience4j | |
---|---|---|---|
隔離策略 | 信號量隔離(並發線程數限流) | 線程池隔離/信號量隔離 | 信號量隔離 |
熔斷降級策略 | 基於響應時間、異常比率、異常數 | 基於異常比率 | 基於異常比率、響應時間 |
實時統計實現 | 滑動窗口(LeapArray) | 滑動窗口(基於RxJava) | Ring Bit Buffer |
動態規則配置 | 支持多種數據源 | 支持多種數據源 | 有限支持 |
擴展性 | 多個擴展點 | 插件的形式 | 接口的形式 |
基於注解的支持 | 支持 | 支持 | 支持 |
限流 | 基於QPS.支持基於調用關系的限流 | 有限的支持 | Rate Limiter |
規則持久化
是什么
一旦我們重啟應用,sentinel規則消失,生產環境需要將配置規則進行持久化
怎么玩
將限流規則持久進Nacos保存,只要刷新8401某個rest地址,sentinel控制台的流控規則就能看得到,只要Nacos里面的配置不刪除,針對8401上的流控規則持續有效
步驟
修改cloudalibaba-sentinel-server8401
POM
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinal-service
cloud:
nacos:
discovery:
#Nacos服務注冊中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentin dashboard地址
dashboard: localhost:8080
# 默認8719端口,假如被占用了會自動從8719端口+1進行掃描,直到找到未被占用的 端口
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true #激活Sentinel 對Feign的支持
添加Nacos業務規則配置
內容解析
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
這一步的作用是每次消費者微服務啟動時在nacos中定義sentinel的流控規則,從而做到持久化的效果
啟動8401刷新sentinel發現業務規則變了
快速訪問測試接口
http://localhost:8401/rateLimit/byUrl
默認
停止8401再看sentinel
重新啟動8401再看sentinel
- 咋一看還是沒有了,稍等一會兒
- 多次調用
http://localhost:8401/rateLimit/byUrl - 重新配置出現了,持久化驗證通過