首页 > 其他分享 >第六章-Sentinel 流量守卫

第六章-Sentinel 流量守卫

时间:2024-03-14 20:46:43浏览次数:23  
标签:降级 depart 守卫 ResponseResult id 第六章 熔断 Sentinel public

第六章 Sentinel 流量守卫

在微服务远程调用的过程中,还存在几个问题需要解决:

  • 业务健壮性问题:consumer调用provider,provider故障会导致consumer也出现故障

在cart-service中,需要调用item-service中的查询商品信息接口。如果查询商品信息接口出现故障,会导致cart-service也出现故障。但是从业务角度来说,即使item-service出现故障,cart-service的查询购物车列表也应该被正确展示,只是不包含最新的商品信息

  • 级联失败问题链路上某些微服务故障导致整个链路的微服务不可用

正常情况下,用户请求:

image.png | 550

如果微服务I出现故障:

image.png | 550

User Request会一直等待微服务I的响应,直到超时出现异常,返回500

假设在高并发场景下:

image.png | 550

就会导致很多User Request等待,就会一直占用App Container的资源,其他正常提供服务的接口也不能进行访问了,这就是服务雪崩。

以hmall为例:

假设item-service并发量较高,接口响应时间增加(或者长时间阻塞/查询失败),cart-service的查询购物车列表接口需要调用item-service,就需要等待item-serivce的响应。如果cart-service收到很多查询购物车列表的请求,这些请求会大量占用Tomcat的资源直至Tomcat资源耗尽,原本正常的业务(添加商品到购物车)也会受到影响,最终购物车微服务是不可用的。

image-20240115193334981

商品服务不可用导致了购物车服务不可用,购物车服务不可用导致了服务E、服务H是不可用的,以此类推,整个系统中和商品服务、购物车服务有关的服务都会出现问题,最终导致整个集群不可用。

image-20240115193513679

雪崩问题产生的原因:

  • 微服务互相调用,服务提供者出现故障

  • 服务调用者没有做好异常处理,导致自身故障

  • 所有服务级联失败

服务保护方案

常见的服务保护方案:

  • 请求限流

  • 线程隔离

  • 服务熔断

这些方案或多或少都会影响服务的体验:

  • 请求限流:降低并发上限。

  • 线程隔离:降低接口可用资源上限。

  • 服务熔断:降低了服务的完整性,部分服务不可用或者弱可用。

这些方案都属于服务降级的方案,但是通过这些方案,服务的健壮性得到提升。

请求限流

服务故障的主要原因之一就是并发量过高,解决这个问题就能解决大部分故障。接口的并发量并非一直很高,而是可能会遇到突发流量

请求限流就是控制或限制接口访问的并发流量,避免服务因为流量激增出现故障

请求限流需要使用限流器,高低起伏的并发请求曲线经过限流器就会变的非常平稳。

flow-limit | 650

线程隔离

线程隔离就是避免慢调用导致消费者资源耗尽。

当一个业务接口响应时间长并且并发很高时,服务器资源会很快被耗尽,导致服务内其他接口受到影响。我们必须将这样的影响降低,或者缩减这种影响的范围。线程隔离就是解决这个问题的一个办法。

线程隔离的思想来源于轮船的舱壁模型:

image-20240115194951681 | 650

轮船的船舱被隔板分为N个互相隔离的密闭舱,假如轮船触礁进水,只有损坏的船舱会进水,而其他船舱由于相互隔离并不会进水。这样就将进水控制在部分船体,避免了整个船舱因为进水而沉没。

为了避免某个接口故障或者压力过大导致微服务不可用需要限定每个接口可以使用的资源范围,也就是将其隔离起来:

image-20240115195029475 | 650

我们设置查询购物车接口最多可用的线程数量是20,即便查询购物车的请求因为商品服务出现故障,也不会导致服务器的资源耗尽,进而不会影响当前服务的其他接口,也就避免了服务雪崩的问题。

服务熔断降级

服务熔断就是避免慢调用导致消费者接口响应慢/响应故障

线程隔离避免了雪崩问题,但是故障服务(商品服务)依然会拖慢服务调用方(购物车服务)的接口响应速度,而商品查询故障依然会导致购物车服务故障,购物车业务也是不可用的。

服务降级是一种增强用户体验的方式。当前用户的请求由于各种原因被拒绝后,系统返回一个事先设定好的、用户可以接受的,但又令用户不满意的结果。这种请求方式处理被称为服务降级。

所以需要做两件事:

  • 编写服务降级逻辑:服务调用失败后处理的逻辑,根据业务场景可以抛出异常或者返回友好提示或默认数据。

  • 异常统计和熔断:统计服务提供方的异常比例,比例过高时表明该接口会影响到其他业务,应该拒绝该接口的调用,直接进入降级逻辑

在熔断期间,所有请求快速失败,返回默认异常信息或者fallback逻辑

image-20240115195241491 | 650

服务保护技术-Sentinel

服务保护有两种技术:

image.png | 850

Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应保护过载、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

GitHub中文官网,但是建议参照GitHub wiki

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel的主要特性:

image.png | 650

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,没有进入服务降级。

熔断规则

熔断规则(老版本称为降级规则)用于完成服务熔断。

熔断概念

服务雪崩

image.png | 550

大量用户请求服务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控制台:

image.png

看不到我们的consumer:depart-consumer,先对consumer进行一次访问,再查看sentinel控制台:

image.png

在application.yml中添加一个配置:

sentinel:  
  transport:  
    port: 8719  
    dashboard: localhost:8090  
  eager: true

eager默认是false,只有在访问过微服务才会出现在控制台中,设置为true就会在启动应用后就显示在控制台中

定义熔断规则有两种方式:

  • 动态设置熔断规则
  • API设置熔断规则

Sentinel的资源

多次访问consumer,查看sentinel控制台:

实时监控:

image.png

簇点链路:

image.png

可以看到:资源名称是请求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,查看控制台:

image.png

有两个资源访问QPS曲线:

访问不存在的路径http://localhost:8080/consumer/depart/all,就是请求了资源/error

访问存在的路径http://localhost:8080/consumer/depart/2,就是请求了资源/consumer/depart/{id}

只要是对应用提交了请求就是访问了资源,如果不存在就是请求了资源/error

再次强调:Controller方法就是Sentinel管理的资源,资源名就是URI

动态设置熔断规则

image.png

对指定资源 /consumer/depart/{id} 进行熔断规则设置:

慢调用比例

image.png | 650

如果在统计时长(ms)中请求数达到最小请求数,并且其中响应时间超过最大RT的请求数达到了比例阈值,就触发熔断,熔断时间为(s),在熔断时间内不对外提供服务。

注意:如果统计时长内请求数不能达到最小请求数,即便慢调用比例达到阈值也不会触发熔断

image.png | 650

含义是:在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配置熔断规则:

image.png


如果在当前的熔断规则下对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,发现不能触发熔断规则,因为微服务重启后对应的熔断规则就被清除了,需要重新设置

image.png

添加@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;  
    }  
  
}

异常比例和异常数

异常比例

image.png | 650

1000ms内,有至少100个请求,并且其中20%出现异常,触发熔断,熔断持续10s

异常数

image.png | 650

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,这是一个抽象类,有五个子类:

image.png

对应了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

相关文章

  • 第六章二叉树——二叉树的最大深度
    吾日三省吾身还记得的梦想吗正在努力实现它吗可以坚持下去吗目录吾日三省吾身力扣题号:104.二叉树的最大深度-力扣(LeetCode)题目描述:思路解法一:递归实现思路代码逻辑解释注意事项代码实现内存优化总结(╯°□°)╯︵┻━┻(⌐■_■)(¬‿¬)(´・ω・`)(͡°͜......
  • Vue — 导航守卫
    Vue的导航守卫是VueRouter提供的一种机制,用于在导航过程中对路由进行控制和管理。通过导航守卫,你可以在路由导航前、导航后、以及路由更新前后等不同阶段执行特定的逻辑操作。全局前置守卫(GlobalBeforeGuards):beforeEach(to,from,next):在路由跳转前执行,可以用来进行......
  • Redis安装之集群-哨兵模式(sentinel)模式
    官网说明:https://redis.io/docs/management/sentinel/一、背景Redis主从复制模型在一定程度上解决了读写分离、容灾恢复、数据备份、水平扩容支撑高并发的问题,但仍存在单点故障问题,即Mater宕机后,集群将无法提供写服务。二、方案原理主从复制的问题在于Mater宕机后,Slave......
  • 程序是怎样跑起来的第六章有感
    读完第六章后,我对函数的理解更加深入了。这一章详细介绍了函数的定义、调用、参数传递以及作用域等重要概念。我明白了函数是将复杂任务分解为可重复使用的模块,提高了代码的可读性和可维护性。通过定义函数并传入相应的参数,我们可以在不同的地方调用它们,避免代码的重复编写。此......
  • [省选联考 2024] 迷宫守卫 题解
    首先Bob肯定是贪心操作,即如果能操作且右儿子中第一个数小于左儿子中的第一个数就一定操作(因为排列中的数两两不同),否则不操作。考虑一个dp,即\(f_{i,j}\)表示\(i\)中的子树操作完以后使得第一个数为\(j\)的最小代价。发现总状态数是\(\mathcalO(2^nn)\)的,对于一个点的......
  • 《程序是怎样跑起来的》第六章
    《程序是怎样跑起来的》第六章此章有着众多全新名词需要去认识了解有助于未来更好的认识计算机文件储存的基本单位是1字节LZH是压缩文件的拓展名数值的值×循环次数为RLE算法RLE不适合文本文件压缩会使文件变大在SHIFTJIS字符编码中,1个半角英数用1字节数据表示压缩后能......
  • 程序是怎么跑起来的第六章
    以下是关于《程序是怎么跑起来的》第六章的观后感:在阅读第六章后,我对程序的运行原理有了更深入的理解。这一章主要介绍了CPU的相关知识,包括CPU的内部结构、指令集、运算器、寄存器等内容。通过对CPU内部结构的了解,我明白了它是如何实现指令的读取、执行和结果的存储。指令......
  • 第六章类例题
    A会出现错误。本来我们不写构造函数的话,编译程序会给我们自动加一个,然后A就没错了,但是我们现在写了,编译程序就不会自动加了,如果我们每次创建对象的时候都用两个double进行初始化那也没有问题,但是现在A却想要调用默认构造函数,是不行的会错误;B不能显式地使用构造函数(书上只写了......
  • react-router路由懒加载/路由守卫
    路由懒加载目前在网络上搜到的路由懒加载方式基本都是通过React.lazy()方法配合Suspense的方式,可能是受vue-router的影响,我很不喜欢这种方式,不喜欢就改变。上代码import{createBrowserRouter}from"react-router-dom";constrouter=createBrowserRouter([{......
  • 第六章 面向对象进阶
    一,分包思想1、分包思想概述(理解)如果将所有的类文件都放在同一个包下,不利于管理和后期维护,所以,对于不同功能的类文件,可以放在不同的包下进行管理2、包的概述(记忆)包本质上就是文件夹创建包多级包之间使用"."进行分割多级包的定义规范:公司的网站地址翻转(去掉w......