第六章 Sentinel 流量守卫
在微服务远程调用的过程中,还存在几个问题需要解决:
- 业务健壮性问题:consumer调用provider,provider故障会导致consumer也出现故障
在cart-service中,需要调用item-service中的查询商品信息接口。如果查询商品信息接口出现故障,会导致cart-service也出现故障。但是从业务角度来说,即使item-service出现故障,cart-service的查询购物车列表也应该被正确展示,只是不包含最新的商品信息
- 级联失败问题:链路上某些微服务故障导致整个链路的微服务不可用
正常情况下,用户请求:
如果微服务I出现故障:
User Request会一直等待微服务I的响应,直到超时出现异常,返回500
假设在高并发场景下:
就会导致很多User Request等待,就会一直占用App Container的资源,其他正常提供服务的接口也不能进行访问了,这就是服务雪崩。
以hmall为例:
假设item-service并发量较高,接口响应时间增加(或者长时间阻塞/查询失败),cart-service的查询购物车列表接口需要调用item-service,就需要等待item-serivce的响应。如果cart-service收到很多查询购物车列表的请求,这些请求会大量占用Tomcat的资源直至Tomcat资源耗尽,原本正常的业务(添加商品到购物车)也会受到影响,最终购物车微服务是不可用的。
商品服务不可用导致了购物车服务不可用,购物车服务不可用导致了服务E、服务H是不可用的,以此类推,整个系统中和商品服务、购物车服务有关的服务都会出现问题,最终导致整个集群不可用。
雪崩问题产生的原因:
-
微服务互相调用,服务提供者出现故障
-
服务调用者没有做好异常处理,导致自身故障
-
所有服务级联失败
服务保护方案
常见的服务保护方案:
-
请求限流
-
线程隔离
-
服务熔断
这些方案或多或少都会影响服务的体验:
-
请求限流:降低并发上限。
-
线程隔离:降低接口可用资源上限。
-
服务熔断:降低了服务的完整性,部分服务不可用或者弱可用。
这些方案都属于服务降级的方案,但是通过这些方案,服务的健壮性得到提升。
请求限流
服务故障的主要原因之一就是并发量过高,解决这个问题就能解决大部分故障。接口的并发量并非一直很高,而是可能会遇到突发流量
请求限流就是控制或限制接口访问的并发流量,避免服务因为流量激增出现故障
请求限流需要使用限流器,高低起伏的并发请求曲线经过限流器就会变的非常平稳。
线程隔离
线程隔离就是避免慢调用导致消费者资源耗尽。
当一个业务接口响应时间长并且并发很高时,服务器资源会很快被耗尽,导致服务内其他接口受到影响。我们必须将这样的影响降低,或者缩减这种影响的范围。线程隔离就是解决这个问题的一个办法。
线程隔离的思想来源于轮船的舱壁模型:
轮船的船舱被隔板分为N个互相隔离的密闭舱,假如轮船触礁进水,只有损坏的船舱会进水,而其他船舱由于相互隔离并不会进水。这样就将进水控制在部分船体,避免了整个船舱因为进水而沉没。
为了避免某个接口故障或者压力过大导致微服务不可用,需要限定每个接口可以使用的资源范围,也就是将其隔离起来:
我们设置查询购物车接口最多可用的线程数量是20,即便查询购物车的请求因为商品服务出现故障,也不会导致服务器的资源耗尽,进而不会影响当前服务的其他接口,也就避免了服务雪崩的问题。
服务熔断降级
服务熔断就是避免慢调用导致消费者接口响应慢/响应故障
线程隔离避免了雪崩问题,但是故障服务(商品服务)依然会拖慢服务调用方(购物车服务)的接口响应速度,而商品查询故障依然会导致购物车服务故障,购物车业务也是不可用的。
服务降级是一种增强用户体验的方式。当前用户的请求由于各种原因被拒绝后,系统返回一个事先设定好的、用户可以接受的,但又令用户不满意的结果。这种请求方式处理被称为服务降级。
所以需要做两件事:
-
编写服务降级逻辑:服务调用失败后处理的逻辑,根据业务场景可以抛出异常或者返回友好提示或默认数据。
-
异常统计和熔断:统计服务提供方的异常比例,比例过高时表明该接口会影响到其他业务,应该拒绝该接口的调用,直接进入降级逻辑。
在熔断期间,所有请求快速失败,返回默认异常信息或者fallback逻辑
服务保护技术-Sentinel
服务保护有两种技术:
Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应保护过载、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
GitHub、中文官网,但是建议参照GitHub wiki
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel的主要特性:
Sentinel是分布式系统的防御系统。
Sentinel控制台
参照wiki下载sentinel-dashboard-1.8.6.jar
使用如下命令启动控制台:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
-Dcsp.sentinel.dashboard.server=localhost:8090 dashboard本身就是应用,可以管理自身
注:若应用为 Spring Boot 或 Spring Cloud 应用,可以通过 Spring 配置文件来指定配置,详情请参考 Spring Cloud Alibaba Sentinel 文档。
启动时输出:
Tomcat started on port(s): 8090 (http) with context path ''
上下文路径是空的,直接访问http://localhost:8090,用户名密码都是sentinel
修改用户名密码:
java -Dserver.port=8090 ^
-Dcsp.sentinel.dashboard.server=localhost:8090 ^
-Dsentinel.dashboard.auth.username=sentinel ^
-Dsentinel.dashboard.auth.password=root ^
-Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
一般不会让sentinel自己监控自己,也就是:
java -Dserver.port=8090 ^
-Dsentinel.dashboard.auth.username=sentinel ^
-Dsentinel.dashboard.auth.password=root ^
-Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
服务降级
服务降级是一种增强用户体验的方式。当前用户的请求由于各种原因被拒绝后,系统返回一个事先设定好的、用户可以接受的,但又令用户不满意的结果。这种请求方式处理被称为服务降级。
Sentinel提供的服务降级有:
-
Sentinel式方法级降级:针对Sentinel管理的资源出现异常进行服务降级
-
Sentinel式类级降级:针对Sentinel管理的资源出现异常进行服务降级
-
Feign式类级降级:针对Feign调用过程中出现异常进行服务降级
如果是RestTemplate,只能使用Sentinel式的两类降级,不能使用Feign式降级。
环境准备
06-consumer-degrade-sentinel-method-8080
sentinel整合Spring Cloud Alibaba,需要参照引入sentinel依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
Sentinel式方法级降级
先不进行服务降级,只启动consumer,访问:http://localhost:8080/consumer/provider/2
结果:
status=500 [Load balancer does not contain an instance for the service depart-provider]
找不到服务提供者,会出现500错误
进行Sentinel式方法级降级:
注意:降级方法除方法名外方法签名要和原方法保持一致
//@SentinelResource:指定当前方法是Sentinel管理的资源,fallback:指定当前方法的服务降级方法
@SentinelResource(fallback = "getHandlerFallback")
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
return departClient.getHandler(id);
}
// 定义getHandler方法的服务降级方法
// 要求:除方法名外方法签名必须相同
public ResponseResult<Depart> getHandlerFallback(Long id) {
Depart depart = new Depart();
depart.setId(Integer.valueOf(id.toString()));
depart.setName("degarde-sentinel-method");
return ResponseResult.success(depart);
}
- @SentinelResource(fallback = "getHandlerFallback") : 指明当前方法是Sentinel管理的资源,fallback指定当前方法的服务降级方法
不启动provider进行访问:http://localhost:8080/consumer/provider/2
当前还是没有服务提供者,getHandler方法调用出现异常,此时会进入指定的服务降级方法,返回值:
{
"code": 200,
"data": {
"id": 0,
"name": "degarde-sentinel-method"
},
"message": null
}
问题是:一旦Controller方法中出现异常就会进入服务降级,无法区分是不是远程调用产生的异常。
在服务降级方法中获取到发生的异常:
public ResponseResult<Depart> getHandlerFallback(Long id,Throwable t) {
Depart depart = new Depart();
depart.setId(Integer.valueOf(id.toString()));
depart.setName("degarde-sentinel-method : " + t.getMessage());
return ResponseResult.success(depart);
}
Sentinel式类级降级
当前的DepartController中:
@Slf4j
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
@Autowired
private DepartClient departClient;
@PostMapping
public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {
return departClient.postDepart(depart);
}
//@SentinelResource:指定当前方法是Sentinel管理的资源,fallback:指定当前方法的服务降级方法
@SentinelResource(fallback = "getHandlerFallback")
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
return departClient.getHandler(id);
}
// 定义getHandler方法的服务降级方法
// 要求:除方法名外方法签名必须相同
public ResponseResult<Depart> getHandlerFallback(Long id,Throwable t) {
Depart depart = new Depart();
depart.setId(Integer.valueOf(id.toString()));
depart.setName("degarde-sentinel-method : " + t.getMessage());
return ResponseResult.success(depart);
}
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler() {
return departClient.listHandler();
}
}
getHandler()定义了服务降级方法,但是saveHandle()和listHandler()也需要服务降级方法,如果在每个方法下都定义其服务降级方法,controller显得非常臃肿,此时就需要使用类级服务降级。
- 定义DepartController的服务降级类
public class ControllerFallback {
/**
* getHandler()的服务降级方法
* @param id
* @param t
* @return
*/
public static ResponseResult<Depart> getHandlerFallback(Long id,Throwable t) {
Depart depart = new Depart();
depart.setId(Integer.valueOf(id.toString()));
depart.setName("degarde-sentinel-method : " + t.getMessage());
return ResponseResult.success(depart);
}
/**
* listHandler()的服务降级方法
* @param t
* @return
*/
public static ResponseResult<List<Depart>> listHandlerFallback(Throwable t) {
Depart depart = new Depart();
depart.setName("degarde-sentinel-method : " + t.getMessage());
List<Depart> departList = List.of(depart);
return ResponseResult.success(departList);
}
}
使用服务降级类:
@Slf4j
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
@Autowired
private DepartClient departClient;
@PostMapping
public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {
return departClient.postDepart(depart);
}
@SentinelResource(fallback = "getHandlerFallback",fallbackClass = ControllerFallback.class)
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
return departClient.getHandler(id);
}
@SentinelResource(fallback = "listHandlerFallback",fallbackClass = ControllerFallback.class)
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler() {
return departClient.listHandler();
}
}
Feign式类级降级
参照Feign支持
Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel
的依赖外还需要 2 个步骤:
-
配置文件打开 Sentinel 对 Feign 的支持:
feign.sentinel.enabled=true
-
加入
spring-cloud-starter-openfeign
依赖使 Sentinel starter 中的自动化配置类生效:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
我们已经引入了openfeign的依赖,只需要打开Sentinel对Feign的支持即可:
spring:
application:
name: depart-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
openfeign:
okhttp:
enabled: true
# sentinel对feign的支持
feign:
sentinel:
enabled: true
在某些版本中,还需要进行一个配置:
spring:
application:
name: depart-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
openfeign:
okhttp:
enabled: true
lazy-attributes-resolution: true
feign:
sentinel:
enabled: true
使用Feign式类级降级,必须要实现Feign接口:
- @RequestMapping("/fallback/provider/depart") : 必须和FeignClient具有相同的path,并且加上 /fallback前缀
/**
* @RequestMapping("/fallback/provider/depart") : 必须和FeignClient具有相同的path,并且加上 /fallback前缀
*/
@Component
@RequestMapping("/fallback/provider/depart")
public class ControllerFallback implements DepartClient{
@Override
public ResponseResult<Depart> getHandler(Long id) {
System.out.println("getHandler() 降级");
Depart depart = new Depart();
depart.setId(Integer.valueOf(id.toString()));
depart.setName("degarde-feign-method");
return ResponseResult.success(depart);
}
@Override
public ResponseResult<List<Depart>> listHandler() {
System.out.println("listHandler() 降级");
Depart depart = new Depart();
depart.setName("degarde-feign-method");
List<Depart> departList = List.of(depart);
return ResponseResult.success(departList);
}
@Override
public ResponseResult<Boolean> postDepart(Depart depart) {
System.out.println("postDepart() 降级");
return null;
}
@Override
public ResponseResult<Boolean> deleteHandler(Long id) {
System.out.println("deleteHandler() 降级");
return null;
}
@Override
public ResponseResult<List<String>> discoveryHandler() {
System.out.println("discoveryHandler() 降级");
return null;
}
}
- 指定使用该降级类:
@FeignClient(value = "depart-provider" ,path = "/provider/depart",fallback = ControllerFallback.class)
public interface DepartClient {
@PostMapping
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
Feign式类级降级的好处是:只有在FeignClient调用的过程中出现异常才会进入服务降级,Controller中的其他异常是不会进入服务降级的
比如:
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
ResponseResult<Depart> handler = departClient.getHandler(id);
if (1 == 1){
throw new RuntimeException("这是一个异常");
}
return handler;
}
在远程调用之后出现了异常,此时直接返回了500,没有进入服务降级。
熔断规则
熔断规则(老版本称为降级规则)用于完成服务熔断。
熔断概念
服务雪崩
大量用户请求服务I出现异常,每个阻塞的请求对应一个线程。并发访问量非常大时,这些请求会迅速用完所有线程,导致对于其他正常服务的访问请求无法获取系统资源而被拒绝,发生系统崩溃,即发生服务雪崩。
服务熔断
线程隔离防止了服务雪崩的发生,在发现对某些资源请求的响应缓慢或调用异常较多时,直接将对这些资源的请求掐断一段时间。在这段时间内请求不再超时等待,而是直接返回事先设定好的降级结果。这些请求将不占用系统资源,从而避免了服务雪崩的发生。这就是服务熔断
在熔断期间,所有请求快速失败,全部走fallback逻辑
环境准备
为consumer设置熔断规则,需要先让consumer连接到sentinel控制台。
引入sentinel依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
参照官网进行配置:
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
spring.cloud.sentinel.port端口配置会应用的服务器上启动一个Http Server,该Server会与Sentinel控制台进行交互。比如Sentinel控制台添加了一个限流规则,会把规则数据push给这个Http Server接收,Http Server再将规则注册到Sentinel中。
Http Server实际上就是Sentinel依赖中引入的sentinel-transport-simple-http
我们可以访问这个server:http://localhost:8719/api
[
{
"url":"/cnode",
"desc":"get clusterNode metrics by id, request param: id={resourceName}"
},
{
"url":"/gateway/updateRules",
"desc":"Update gateway rules"
},
{
"url":"/setParamFlowRules",
"desc":"Set parameter flow rules, while previous rules will be replaced."
},
{
"url":"/gateway/getApiDefinitions",
"desc":"Fetch all customized gateway API groups"
},
.......
]
Sentinel Dashboard 就会调用Http Server的这些API接口来完成功能。
此时直接启动06-consumer和06-provider,查看sentinel控制台:
看不到我们的consumer:depart-consumer,先对consumer进行一次访问,再查看sentinel控制台:
在application.yml中添加一个配置:
sentinel:
transport:
port: 8719
dashboard: localhost:8090
eager: true
eager默认是false,只有在访问过微服务才会出现在控制台中,设置为true就会在启动应用后就显示在控制台中
定义熔断规则有两种方式:
- 动态设置熔断规则
- API设置熔断规则
Sentinel的资源
多次访问consumer,查看sentinel控制台:
实时监控:
簇点链路:
可以看到:资源名称是请求URI,例如:/consumer/depart/list
Sentinel可以管理的资源是请求访问对应Controller的方法:
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler() {
return departClient.listHandler();
}
listHandler()就是Sentinel所管理的资源,对应的资源名称是/consumer/depart/list
如果访问不存在的路径:http://localhost:8080/consumer/depart/all,查看控制台:
有两个资源访问QPS曲线:
访问不存在的路径http://localhost:8080/consumer/depart/all,就是请求了资源/error
访问存在的路径http://localhost:8080/consumer/depart/2,就是请求了资源/consumer/depart/{id}
只要是对应用提交了请求就是访问了资源,如果不存在就是请求了资源/error
再次强调:Controller方法就是Sentinel管理的资源,资源名就是URI
动态设置熔断规则
对指定资源 /consumer/depart/{id} 进行熔断规则设置:
慢调用比例
如果在统计时长(ms)中请求数达到最小请求数,并且其中响应时间超过最大RT的请求数达到了比例阈值,就触发熔断,熔断时间为(s),在熔断时间内不对外提供服务。
注意:如果统计时长内请求数不能达到最小请求数,即便慢调用比例达到阈值也不会触发熔断
含义是:在2000ms内,请求数达到5个,并且其中占比0.2的请求数(1个)响应时间达到2ms就触发熔断,熔断时间5s
测试:
快速访问这个资源,触发熔断会出现:Blocked by Sentinel (flow limiting),被Sentinel阻塞,被限流
在熔断时间(5s)过后,又能继续访问了。
熔断后降级
当前consumer配置了Feign式类级降级的:
@FeignClient(value = "depart-provider" ,path = "/provider/depart",fallback = ControllerFallback.class)
public interface DepartClient {
@PostMapping
public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);
@DeleteMapping("/{id}")
public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id);
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler();
@GetMapping("/discovery")
public ResponseResult<List<String>> discoveryHandler();
}
@Component
@RequestMapping("/fallback/provider/depart")
public class ControllerFallback implements DepartClient{
@Override
public ResponseResult<Depart> getHandler(Long id) {
System.out.println("getHandler() 降级");
Depart depart = new Depart();
depart.setId(Integer.valueOf(id.toString()));
depart.setName("degarde-feign-method");
return ResponseResult.success(depart);
}
@Override
public ResponseResult<List<Depart>> listHandler() {
System.out.println("listHandler() 降级");
Depart depart = new Depart();
depart.setName("degarde-feign-method");
List<Depart> departList = List.of(depart);
return ResponseResult.success(departList);
}
@Override
public ResponseResult<Boolean> postDepart(Depart depart) {
System.out.println("postDepart() 降级");
return null;
}
@Override
public ResponseResult<Boolean> deleteHandler(Long id) {
System.out.println("deleteHandler() 降级");
return null;
}
@Override
public ResponseResult<List<String>> discoveryHandler() {
System.out.println("discoveryHandler() 降级");
return null;
}
}
但是在/consumer/depart/{id}触发熔断规则后,并未返回服务降级逻辑中定义的内容,而是返回了:Blocked by Sentinel (flow limiting)
因为Feign式类级降级针对的是Feign调用过程中出现的异常情况,但是当前的Feign远程调用并未出现异常情况,当前熔断是因为controller方法RT超过2ms,无法进入对于Feign的降级逻辑。
对provider配置熔断规则,让provider发生熔断,consumer的Feign远程调用就会出现异常,就能进入到Feign的降级逻辑了。
对provider配置熔断规则:
如果在当前的熔断规则下对consumer进行熔断后降级,需要使用Sentinel式服务降级:
@Slf4j
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
@Autowired
private DepartClient departClient;
@PostMapping
public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {
return departClient.postDepart(depart);
}
@SentinelResource(fallback = "getHandlerFallback")
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
ResponseResult<Depart> handler = departClient.getHandler(id);
return handler;
}
public ResponseResult<Depart> getHandlerFallback(Long id){
Depart depart = new Depart();
depart.setName("degrade by sentinel on method level");
return ResponseResult.success(depart);
}
@GetMapping("/list")
public ResponseResult<List<Depart>> listHandler() {
return departClient.listHandler();
}
}
重启consumer,进行测试:
多次访问http://localhost:8080/consumer/depart/2,发现不能触发熔断规则,因为微服务重启后对应的熔断规则就被清除了,需要重新设置
添加@SentinelResource(fallback = "getHandlerFallback")后,簇点链路的资源列表中会出现方法的全限定方法签名
Sentinel式方法级降级必须将熔断规则设置在资源名称为方法的全限定方法签名的资源上
这个资源名称也是可以更改的:
@SentinelResource(value = "getHandler",fallback = "getHandlerFallback")
@GetMapping("/{id}")
public ResponseResult<Depart> getHandler(@PathVariable Long id) {
ResponseResult<Depart> handler = departClient.getHandler(id);
return handler;
}
public ResponseResult<Depart> getHandlerFallback(Long id,Throwable t){
Depart depart = new Depart();
depart.setName("degrade by sentinel on method level");
return ResponseResult.success(depart);
}
注意:一旦指定了资源名称,降级方法必须指定,否则返回500
此时就需要把熔断规则定义在资源名称是getHandler上
API设置熔断规则
-
应用重启后,熔断规则都消失了,每次都重新配置太麻烦了。
-
在电商系统中,在促销时段除了会增加服务器数量,还会把非核心模块的抗压能力降低,比如:修改收货地址、查看历史订单等。一旦访问量过高立刻进行熔断,避免浪费系统性能在处理这些非核心模块的业务上。降低或升高抗压能力是在系统启动时就有的熔断规则基础之上调整的。
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class)
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
@SpringBootApplication
public class Consumer03Application {
public static void main(String[] args) {
SpringApplication.run(Consumer03Application.class, args);
initDegradeRule();
}
private static void initDegradeRule() {
DegradeRule rule = configDegradeRule();
DegradeRuleManager.loadRules(List.of(rule));
}
private static DegradeRule configDegradeRule() {
DegradeRule rule = new DegradeRule();
// 将当前规则应用于指定的sentinel资源名称
rule.setResource("getHandler");
// 指定熔断策略:慢调用比例
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// 设置最大RT
rule.setCount(2);
// 设置比例阈值
rule.setSlowRatioThreshold(0.2);
// 设置熔断时长
rule.setTimeWindow(10);
// 设置统计时长
rule.setStatIntervalMs(1000);
// 最小请求数量
rule.setMinRequestAmount(2);
return rule;
}
}
异常比例和异常数
异常比例
1000ms内,有至少100个请求,并且其中20%出现异常,触发熔断,熔断持续10s
异常数
1000ms内,有至少100个请求,并且其中30个出现异常,触发熔断,熔断持续5s
API方式:
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class)
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
@SpringBootApplication
public class Consumer03Application {
public static void main(String[] args) {
SpringApplication.run(Consumer03Application.class, args);
initDegradeRule();
}
private static void initDegradeRule() {
DegradeRule rule = configSlowDegradeRule();
DegradeRuleManager.loadRules(List.of(rule));
}
/**
* 慢调用比例熔断规则
* @return
*/ private static DegradeRule configSlowDegradeRule() {
DegradeRule rule = new DegradeRule();
// 将当前规则应用于指定的sentinel资源名称
rule.setResource("getHandler");
// 指定熔断策略:慢调用比例
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// 设置最大RT
rule.setCount(2);
// 设置比例阈值
rule.setSlowRatioThreshold(0.2);
// 设置熔断时长
rule.setTimeWindow(10);
// 设置统计时长
rule.setStatIntervalMs(1000);
// 最小请求数量
rule.setMinRequestAmount(2);
return rule;
}
/**
* 异常比例熔断规则
* @return
*/ private static DegradeRule configErrorRatioDegradeRule() {
DegradeRule rule = new DegradeRule();
// 将当前规则应用于指定的sentinel资源名称
rule.setResource("getHandler");
// 指定熔断策略:异常比例
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
// 设置异常比例阈值
rule.setCount(0.3);
// 设置熔断时长
rule.setTimeWindow(10);
// 设置统计时长
rule.setStatIntervalMs(1000);
// 最小请求数量
rule.setMinRequestAmount(2);
return rule;
}
/**
* 异常数熔断规则
* @return
*/ private static DegradeRule configErrorCountDegradeRule() {
DegradeRule rule = new DegradeRule();
// 将当前规则应用于指定的sentinel资源名称
rule.setResource("getHandler");
// 指定熔断策略:异常数
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 设置异常数阈值
rule.setCount(1);
// 设置熔断时长
rule.setTimeWindow(10);
// 设置统计时长
rule.setStatIntervalMs(1000);
// 最小请求数量
rule.setMinRequestAmount(2);
return rule;
}
}
自定义异常处理器
注意:如果指定了服务降级逻辑,就会执行服务降级逻辑方法,不会经过自定义异常处理器了。
返回响应流
熔断触发后默认的返回值信息:Blocked by Sentinel (flow limiting)
可以自定义熔断触发后的返回值信息,当前的异常信息是由DefaultBlockExceptionHandler返回的:
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
public DefaultBlockExceptionHandler() {
}
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
PrintWriter out = response.getWriter();
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
}
父接口BlockExceptionHandler:
public interface BlockExceptionHandler {
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
handle可以获取到Request、Response,就可以输出我们想自定义的内容。
第三个参数:BlockException,这是一个抽象类,有五个子类:
对应了Sentinel控制台的5种规则:授权、熔断(降级)、流控、热点、系统
为了使我们自定义的异常处理器具有通用性,我们对这几种异常都进行处理:
@Component
public class CustomerBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(getHttpStatus(e).value());
PrintWriter out = response.getWriter();
//不同的异常对应不同的信息
String message = "Blocked by Sentinel - ";
String exceptionMessage = getExceptionMessage(e);
message += exceptionMessage;
out.print(message);
out.flush();
out.close();
}
private String getExceptionMessage(BlockException e){
String simpleName = e.getClass().getSimpleName();
String message = null;
switch (simpleName){
case "FlowException" : message = "Flow Exception"; break;
case "AuthorityException" : message = "Authority Exception"; break;
case "DegradeException" : message = "Degrade Exception"; break;
case "ParamFlowException" : message = "ParamFlow Exception"; break;
case "SystemBlockException" : message = "SystemBlock Exception"; break;
}
return message;
}
private HttpStatus getHttpStatus(BlockException e){
if (e instanceof AuthorityException){
return HttpStatus.UNAUTHORIZED;
}
return HttpStatus.TOO_MANY_REQUESTS;
}
}
此时的返回值信息就变为了:Blocked by Sentinel - Degrade Exception
返回页面
页面需要定义在resource/META-INF/resources下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>degrade</title>
</head>
<body>
<h2>发生熔断异常</h2>
</body>
</html>
@Component
public class CustomerBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String page = "/degrade.html";
request.getRequestDispatcher(page).forward(request,response);
}
}
重定向到URL
@Component
public class CustomerBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.sendRedirect("https://www.baidu.com");
}
}
标签:降级,depart,守卫,ResponseResult,id,第六章,熔断,Sentinel,public
From: https://www.cnblogs.com/euneirophran/p/18073905