Sentinel降級簡介
Sentinel熔斷降級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。
當資源被降級后,在接下來的降級時間窗口之內,對該資源的調用都自動熔斷(默認行為是拋出DegradeException)。
RT(平均響應時間,秒級)
平均響應時間 超出閾值且在時間窗口內通過的請求>=5,兩個條件同時滿足后觸發降級窗口期過后關閉斷路器
RT最大4900(最大的需要通過-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
異常比例(秒級)
QPS>=5且異常比例(秒級統計)超過閾值時,觸發降級;窗口期結束后,關閉降級。
異常數
異常數(分鍾統計)超過閾值時,觸發降級;時間窗口期結束后,關閉降級。
Sentinel降級-RT
修改子項目(8401)的FlowLimitController
@GetMapping("/testD")
public String testD() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "**********testD***********";
}
在sentinel的窗口,設置降級
當一秒鍾打進來十個線程來調用testD,我們希望200毫秒處理完本次任務,如果超過200毫秒還沒處理完,在未來1秒鍾的時間窗口內,斷路器打開,服務不可用。
Sentinel降級-異常比例
異常比例:當資源的每秒請求量>=5,並且每秒異常總數占通過量的比值超過閾值之后,資源進入降級狀態,即在接下來的時間窗口之內,對這個方法的調用都會自動返回,異常比例的閾值范圍[0.0,1.0],代表0%-100%。
修改子項目(8401)的FlowLimitController
@GetMapping("/testD")
public String testD() {
/*try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
int age = 10/0;
return "**********testD異常比例***********";
}
Sentinel降級-異常數
異常數:當資源近1分鍾的異常數目超過閾值滯后進行熔斷。由於統計時間窗口是分鍾級別的,若時間窗口小於60s,則結束熔斷狀態后仍可能再進入熔斷狀態。
時間窗口一定要大於60秒
修改子項目(8401)的FlowLimitController
@GetMapping("/testE")
public String testE() {
int age = 10/0;
return "********testE異常數********";
}
運行報錯。
設置異常數
Sentinel熱點key
熱點參數限流會統計參數中的熱點參數,並根據配置的限流閾值與模式,對包含熱點參數的資源調用進行限流,熱點參數限流可以看作是一種特殊的流量控制,僅包含熱點參數的資源調用生效。
修改子項目(8401)的FlowLimitController
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
return "*****testHotKey successful**********";
}
public String deal_testHotKey(String p1, String p2, BlockException ex){
return "********deal_testHotKey**********";
}
運行
配置熱點規則
參數索引:對訪問的第一個參數進行限流
當每秒訪問多次的時候就會進行服務熔斷。
當把blockHandler參數去掉,一秒一次就會顯示成功頁面,當一秒訪問多次的時候就會直接彈出錯誤頁面(Error Page)。
參數例外項
普通:超過1秒鍾一個之后,達到閾值1后馬上被限流
我們希望p1參數當它是摸個特殊值時,它的限流值和平時不一樣
假如當p1的值等於5時,它的閾值可以達到200
配置參數例外項
當p1的值不等於5時,閾值是1
當p1的值等於5時,閾值為200
當代碼中有錯誤它不會去報deal_testHotKey,而是會直接顯示錯誤界面。
Sentinel系統規則
系統自適應限流
Sentinel系統自適應限流從整體維度對應用入口流量控制,結合應用的Load、CPU使用率、總體平均RT、入口QPS和並發線程數等幾個維度的監控指標,通過自適應的流控策略、讓系統的入口流量和系統的負載達到一個平衡,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
Load自適應(僅對Linux/Unix-like機器生效):系統的load作為啟發指標,進行自適應系統保護。當系統load超過設定的啟發值,且系統當前的並發線程數超過估算的系統容量時才會觸發系統保護(BBR階段)。系統容量有系統的maxQps*minRt估算得出,設定參考值一般是CPU cores * 2.5。
CPU usage(1.5.0+版本):當系統CPU使用率超過閾值即觸發系統保護(取值范圍0.0-1.0),比較靈敏。
平均RT:當單台機器所有入口流量的平均RT達到閾值即觸發系統保護,單位是毫秒
並發線程數:當單台機器所有入口流量的並發線程數達到閾值即觸發系統保護
入口QPS:當單台機器上所有入口流量的QPS達到閾值即觸發系統保護
SentinelResource配置
修改一個RateLimitController
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200,"按資源名稱限流測試OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服務不可用");
}
運行8401
設置限流規則
按照Url地址限流
修改RateLimitController
//按照URl限流
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
return new CommonResult(200,"按Url限流測試OK",new Payment(2020L,"serial002"));
}
設置限流規則
上面兜底方案面臨的問題
- 系統默認的,沒有體現我們自己的業務要求。
- 依照現有條件,我們自定義的處理方法又和業務代碼耦合在一塊,不直觀。
- 每個業務方法都添加一個兜底的,那會造成代碼膨脹加劇。
- 全局統一的處理方法沒有體現。
修改一個RateLimitController
//CustomerBlockHandler
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerException1")
public CommonResult customerBlockHandler() {
return new CommonResult(200,"按客戶自定義",new Payment(2020L,"serial003"));
}
新建一個CustomerBlockHandler.java
public class CustomerBlockHandler {
public static CommonResult handlerException1(BlockException exception){
return new CommonResult(444,"按客戶自定義,global handlerException-------1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(444,"按客戶自定義,global handlerException-------2");
}
}
配置限流規則
Sentinel服務熔斷Ribbon環境
新建子項目(cloudalibaba-provider-payment9003)
pom.xml
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web組件 -->
<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>
<!--引入自己定義的api通用包-->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--日常通用jar包配置-->
<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>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
新建配置文件application.yml
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置nacos地址
management:
endpoints:
web:
exposure:
include: '*'
新建主啟動類
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class,args);
}
}
新建業務類PaymentController
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static {
hashMap.put(1L,new Payment(1L,"從入門到放棄"));
hashMap.put(2L,new Payment(2L,"從刪庫到跑路"));
hashMap.put(3L,new Payment(3L,"從進門到坐牢"));
}
@GetMapping("/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
return result;
}
}
新建子項目(cloudalibaba-provider-payment9003)
步驟按照上述
新建子項目(cloudalibaba-consumer-nacos-order84)
pom.xml
<dependencies>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web組件 -->
<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>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--日常通用jar包配置-->
<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>
新建application.yml配置文件
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
service-url:
nacos-user-service: http://nacos-payment-provider
創建主啟動類OrderNacosMain84
@SpringBootApplication
@EnableDiscoveryClient
public class OrderNacosMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class,args);
}
}
創建配置類ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
創建業務類CircleBreakerController
@RestController
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("非法參數,異常");
}else if(result.getData()==null) {
throw new NullPointerException("該ID沒有對應的記錄,空指針異常");
}
return result;
}
}
Sentinel服務熔斷配置fallback
在子項目84上業務類CircleBreakerController加上兜底方法
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",fallback = "handerFallback")
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("非法參數,異常");
}else if(result.getData()==null) {
throw new NullPointerException("該ID沒有對應的記錄,空指針異常");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444,"兜底異常,異常內容:"+e.getMessage(),payment);
}
}
fallback只負責業務異常。
Sentinel服務熔斷配置blockhandler
修改子項目84上業務類CircleBreakerController
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback",fallback = "handerFallback") //fallback只處理業務出現異常
@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只負責sentinel控制台配置違規
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("非法參數,異常");
}else if(result.getData()==null) {
throw new NullPointerException("該ID沒有對應的記錄,空指針異常");
}
return result;
}
/*public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444,"兜底異常,異常內容:"+e.getMessage(),payment);
}*/
public CommonResult handlerFallback(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, null);
return new CommonResult<>(445, "blockHandler-sentinel限流,無此流水:" + blockException.getMessage(), payment);
}
}
添加降級規則
blockHandler只負責sentinel控制台配置違規
Sentinel服務熔斷配置fallback和blockhandler
若blockHandler和fallback都進行了配置,則被限流降級而拋出BlockException時只會進入blockHandler處理邏輯。
Sentinel服務熔斷exceptionsTolgnore
修改子項目84上業務類CircleBreakerController
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback",fallback = "handerFallback") //fallback只處理業務出現異常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只負責sentinel控制台配置違規
//@SentinelResource(value = "fallback",fallback = "handerFallback",blockHandler = "blockHandler")
@SentinelResource(value = "fallback",fallback = "handerFallback",blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
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("非法參數,異常");
}else if(result.getData()==null) {
throw new NullPointerException("該ID沒有對應的記錄,空指針異常");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444,"兜底異常,異常內容:"+e.getMessage(),payment);
}
public CommonResult handlerFallback(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, null);
return new CommonResult<>(445, "blockHandler-sentinel限流,無此流水:" + blockException.getMessage(), payment);
}
}
運行
假如4報異常,不再有fallback方法兜底,沒有降級效果了。
Sentinel服務熔斷OpenFeign
修改子項目(84)
pom.xml
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
修改application.yml
# 激活Sentinel對Feign的支持
feign:
sentinel:
enabled: true
在主啟動類上面加上注解,開啟Feign
@EnableFeignClients
創建service接口
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
創建接口實現類PaymentFallbackService
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服務降級返回,----PaymentFallbackService",new Payment(id,"errorService"));
}
}
在業務類CirleBreakerController添加
@Resource
private PaymentService paymentService;
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
運行
當9003停止運行后,84會
Sentinel持久化規則
一旦我們重啟應用,sentinel規則將會消失,生產環境需要將配置規則進行持久化。
將限流配置規則持久化進Nacos保存,只要刷新8401某個rest地址,sentinel控制台的流控規則就能看到,只要Nacos里面的配置不刪除,針對8401上的sentinel上的流控規則持續有效。
修改子項目(8401)
application.yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
port: 8719 #默認8719端口,如果被占用會自動從8719開始依次+1,直到找到未被占用的端口
#8719端口是應用和Sentinel控制台交互的端口
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: '*'
在nacos里面新建配置
resource:資源名稱;
limitApp:來源應用;
grade:閾值類型,0表示線程數、1表示QPS;
count:單機閾值;
strategy:流控模式,0表示直接,1表示關聯,2表示鏈路;
controlBehavior:流控效果,0表示快速失敗,1表示Warm up,2表示排隊等待;
clusterMode:是否集群;
啟動8401后刷新sentinel發現業務規則有了