CodeAshen's blog CodeAshen's blog
首页
  • Spring Framework

    • 《剖析Spring5核心原理》
    • 《Spring源码轻松学》
  • Spring Boot

    • Spring Boot 2.0深度实践
  • Spring Cloud

    • Spring Cloud
    • Spring Cloud Alibaba
  • RabbitMQ
  • RocketMQ
  • Kafka
  • MySQL8.0详解
  • Redis从入门到高可用
  • Elastic Stack
  • 操作系统
  • 计算机网络
  • 数据结构与算法
  • 云原生
  • Devops
  • 前端
  • 实用工具
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • Reference
GitHub (opens new window)

CodeAshen

后端界的小学生
首页
  • Spring Framework

    • 《剖析Spring5核心原理》
    • 《Spring源码轻松学》
  • Spring Boot

    • Spring Boot 2.0深度实践
  • Spring Cloud

    • Spring Cloud
    • Spring Cloud Alibaba
  • RabbitMQ
  • RocketMQ
  • Kafka
  • MySQL8.0详解
  • Redis从入门到高可用
  • Elastic Stack
  • 操作系统
  • 计算机网络
  • 数据结构与算法
  • 云原生
  • Devops
  • 前端
  • 实用工具
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • Reference
GitHub (opens new window)
  • 剖析Spring5核心原理

  • Spring源码轻松学

  • Spring Boot 2.0深度实践

  • Spring-Cloud

  • Spring-Cloud-Alibaba

    • 第01章-简介
    • 第02章-Nacos服务发现
    • 第03章-Ribbon负载均衡
    • 第04章-Feign声明式HTTP客户端
    • 第05章-Sentinel集群容错
      • 5.1 雪崩效应
      • 5.2 常见的容错方案
      • 5.3 整合Sentinel
        • 5.3.1 整合工作
        • 5.3.2 整合sentinel控制台
      • 5.4 流控规则和效果
        • 5.4.1 流控规则
        • 5.4.2 流控效果
      • 5.5 降级规则
      • 5.6 热点参数规则
      • 5.7 授权规则
      • 5.8 系统自适应限流
      • 5.9 代码配置流控规则
      • 5.10 Sentinel控制台
        • 5.10.1 Sentinel客户端和控制台通信
        • 5.10.2 控制台相关配置项
      • 5.11 Sentinel API介绍
      • 5.12 SentinelResource注解支持
      • 5.13 Http客户端整合Sentinel
        • 5.13.1 RestTemplate整合Sentinel
        • 5.13.2 Feign整合Sentinel
      • 5.14 规则持久化
      • 5.15 集群流控
      • 5.16 Sentinel拓展
        • 5.16.1 Sentinel错误页优化
        • 5.16.2 Sentinel区分来源
        • 5.16.3 Sentinel资源RESTful支持
        • 5.16.4 Sentinel拓展的本质
    • 第06章-RocketMQ消息队列
    • 第07章-Spring Cloud Gateway网关
    • 第08章-认证授权
    • 第09章-Nacos配置管理
    • 第10章-Sletuh调用链
  • Spring
  • Spring-Cloud-Alibaba
CodeAshen
2023-02-10
目录

第05章-Sentinel集群容错

# 5.1 雪崩效应

image-20210220193021122

  • 如图所示,假设这是一个高并发的微服务系统,一开始所有服务都是正常的
  • A服务挂了,导致B服务调用A服务失败,从B发往A的请求就会强制等待直至超时
  • Java中一次请求往往对应一个线程,如果请求被强制等待了,线程就会被强制阻塞,一直到请求超时的时候这个线程才会被释放,由于这是一个高并发的系统,B服务上阻塞的线程会越来越多,而线程对应着计算机的资源,如内存、CPU
  • 如果不做处理的话,B服务终将资源耗尽无法再创建新的线程,这样B服务也挂了
  • 同样道理,C、D服务最后也会因为B服务而挂掉,这种基础服务故障导致上层服务故障就是服务的雪崩效应,也叫级联故障

# 5.2 常见的容错方案

  • 超时
  • 限流
  • 舱壁模式
  • 断路器模式

舱壁模式可参考:https://blog.csdn.net/dot_life/article/details/80823272

断路器三态转换:

image-20210220194816627

# 5.3 整合Sentinel

# 5.3.1 整合工作

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--actuator-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10

引入sentinel依赖后就整合好了。这里整合actuator是为了通过actuator/sentinel这个endpoint检验时候已经整合sentinel,配合actuator暴露端点:

# actuator端点控制
management:
  endpoints:
    web:
      exposure:
        include: '*'
1
2
3
4
5
6

此时访问actuator/sentinel,就能看到相关信息。

# 5.3.2 整合sentinel控制台

  1. github下载对应版本的sentinel-dashboard的jar包

  2. 本地使用java -jar sentinel-dashboard-1.8.0.jar启动控制台

  3. 为应用指定控制台的地址

    spring:
      cloud:
        sentinel:
          transport:
            # 指定sentinel控制台的地址
            dashboard: localhost:8080
    
    1
    2
    3
    4
    5
    6

此时访问控制台还是看不到应用信息,是因为sentinel是懒加载的,访问应用任意controller接口后即可在控制台观察到。

# 5.4 流控规则和效果

# 5.4.1 流控规则

image-20210222113211282

通过控制台可以看到有3种流控模式

1、直接

表示当前资源达到阈值后触发流控

2、关联

当关联的资源达到阈值,就限流本资源

image-20210222113646578

如图所示,表示当/actuator/sentinel的QPS达到1后,就限流/shares/{id}

3、链路

针对指定入口资源做流控

如 /endpoint_1 和 /endpoint_2 都调用资源 common,可以针对入口资源 /endpoint_1 做流控。相当于细粒度的来源控制。

参考:sentinel流量控制 (opens new window)

# 5.4.2 流控效果

  • 快速失败:直接失败,抛异常
  • Warm Up:根据codeFactor(默认3)的值,从阈值/codeFactor,经过预热时长,才到达设置的QPS阈值
  • 排队等待:匀速排队,让请求以均匀的速度通过,阈值类型必须设成QPS,香则无效

# 5.5 降级规则

image-20210222134936828

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

参考:sentinel熔断降级 (opens new window)

# 5.6 热点参数规则

热点参数规则可对指定资源的参数做流控,以下例子

@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 测试sentinel热点参数限流的资源
     */
    @GetMapping("/sentinel/param")
    @SentinelResource("hot")
    public String sentinelParamControl(@RequestParam(required = false) String a,
                                       @RequestParam(required = false) String b) {
        return a + " " + b;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

设置热点规则:

image-20210222155526098

上图表示参数索引为0(第一个参数)的参数,在1s内QPS达到1,则触发限流

即访问 /test/sentinel/param?a=1&b=2,达到阈值会触发限流

/test/sentinel/param?b=2,不传热点参数,不会触发限流

参考:热点参数限流 (opens new window)

# 5.7 授权规则

基于服务名配置黑名单白名单

image-20210222164122438

# 5.8 系统自适应限流

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。可从控制台-系统规则处添加。

流控规则:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

# 5.9 代码配置流控规则

参考:Alibaba Sentinel 规则参数总结 (opens new window)

# 5.10 Sentinel控制台

# 5.10.1 Sentinel客户端和控制台通信

image-20210222164922057

  • 微服务需要继承transport模块,继承该模块之后,就把自己注册到控制台上,并定时发送心跳
  • 此时控制台就知道各微服务的地址和通信端口等

image-20210222165259198

如图是服务的机器列表,其中端口不是服务的server.port,是微服务和sentinel通信的端口,通过注册的IP和端口就可以实现通信,包括推送规则等。

# 5.10.2 控制台相关配置项

应用端连接控制台配置项:

spring.cloud.sentinel.transport:
  #指定控制台的地址
  dashboard: localhost:8080
  #指定和控制台通信的IP#如不配置,会自动选择一个Ip注册
  client-ip: 127.0.0.1
  #指定和控制台通信的端口,默认值8719
  #如不设置,会自动从8719开始扫描,依次+1,直到找到未被占用的端口
  port: 8719
  #心跳发送周期,默认值nul1
  #但在simpleHttpHeartbeatSender会用默认值10秒
  heartbeat-interval-ms: 10000
1
2
3
4
5
6
7
8
9
10
11

控制台配置项参考:sentinel控制台 (opens new window)

# 5.11 Sentinel API介绍

之前都是使用Sentinel自动将Spring MVC的端点作为资源的,其实Sentinel可以将任意代码作为资源保护起来。可以通过配置 spring.cloud.sentinel.filter.enable=false 关闭对Spring MVC端点的保护,即关闭自动将端点作为资源。

Sentinel核心API的SphU类,用于定义资源。

参考:Sentinel定义资源方式 (opens new window)

其他API:

  • 业务异常统计 Tracer

    Sentinel默认只会统计BlockException,其他异常不会统计异常数和异常比例,可以使用Tracer的API记录其他业务异常

  • 上下文工具类 ContextUtil

    用于标识调用链路入口,用于区分不同的调用链路

参考:Sentinel其他API (opens new window)

# 5.12 SentinelResource注解支持

Sentinel 提供了 @SentinelResource 注解用于定义资源

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT)

  • blockHandler / blockHandlerClass: 降级处理的函数名称和类名,可选项

  • fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。

  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项

  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

具体配置参考:Sentinel注解支持 (opens new window)

# 5.13 Http客户端整合Sentinel

# 5.13.1 RestTemplate整合Sentinel

在RestTemplate的Bean定义方法上加@SentinelRestTemplate注解就可以将RestTemplate请求的标记为资源

@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)  //整合注解
public RestTemplate restTemplate() {
    return new RestTemplate();
}
1
2
3
4
5

测试代码:

@Autowired
private RestTemplate restTemplate;

@GetMapping("/sentinel/rest_template/{userId}")
public UserDTO sentinelRestTemplate(@PathVariable Integer userId) {
    return restTemplate.getForObject("http://user-center/users/{userId}", UserDTO.class, userId);
}
1
2
3
4
5
6
7

整合RestTemplate开关,resttemplate.sentinel.enable=true,默认为true

参考:RestTemplate支持 (opens new window)

# 5.13.2 Feign整合Sentinel

#Feign整合Sentinel开关,默认为false
feign.sentinel.enable=true
1
2
// FeignClient接口
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {
    @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
    String echo(@PathVariable("str") String str);
}

// FeignClient配置
class FeignConfiguration {
    @Bean
    public EchoServiceFallback echoServiceFallback() {
        return new EchoServiceFallback();
    }
}

// FeignClient错误处理实现
class EchoServiceFallback implements EchoService {
    @Override
    public String echo(@PathVariable("str") String str) {
        return "echo fallback";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

参考:Feign支持 (opens new window)

# 5.14 规则持久化

Sentinel提供两种规则持久化手段

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

参考:

  • 动态规则扩展 (opens new window)
  • 在生产环境中使用 Sentinel (opens new window)
  • 拉模式示例 (opens new window)
  • 推模式Nacos示例 (opens new window)
  • 推模式Nacos详解 (opens new window)

# 5.15 集群流控

Sentinel 集群限流服务端有两种启动方式:

  • 独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。

    image-20210223191114213

  • 嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。

    image-20210223190946930

独立模式实现单个节点的流控,嵌入模式实现集群整体流控,控制整体集群的QPS。

部署token server切换到嵌入模式后即可在sentinel dashboard上配置集群流控。

参考:集群流控 (opens new window)

# 5.16 Sentinel拓展

# 5.16.1 Sentinel错误页优化

Sentinel默认降级限流等都统一写出一句话响应,如何自定义错误响应:

/**
 * BlockExceptionHandler接口用于处理限限流后处理
 * 来版本接口名叫UrlBlockHandler
 */
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler  {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        // 根据不同的异常设置不同返回值
        BlockErrorMsg errorMsg = null;
        if (e instanceof FlowException) {
            errorMsg = new BlockErrorMsg(100, "限流了");
        } else if (e instanceof DegradeException) {
            errorMsg = new BlockErrorMsg(101, "降级了");
        } else if (e instanceof ParamFlowException) {
            errorMsg = new BlockErrorMsg(102, "热点参数限流");
        } else if (e instanceof AuthorityException) {
            errorMsg = new BlockErrorMsg(103, "授权规则不通过");
        } else if (e instanceof SystemBlockException) {
            errorMsg = new BlockErrorMsg(104, "系统规则限流");
        }
        
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");

        // 写出json响应
        response.getWriter().write(new ObjectMapper().writeValueAsString(errorMsg));
    }
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class BlockErrorMsg {
    private Integer status;
    private String msg;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

经测试上述规则对 @SentinelResource 注解标识的资源不起作用,注解资源需要用注解中的属性 blockHandler/blockHandlerClass,详见 [5.12 SentinelResource注解支持](#5.12 SentinelResource注解支持)

# 5.16.2 Sentinel区分来源

Sentinel提供了RequestOriginParser来源解析器接口,用来解析请求来源。只需要编写该接口实现类,就可按照指定规则解析请求来源。

/**
 * 实现Sentinel的 RequestOriginParser 来源解析器接口,
 * 解析来自HTTP请求的请求来源(例如IP,用户,appName)
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    /**
     * 解析给定HTTP请求的来源
     * @param request http请求
     * @return 解析出的来源,返回什么就表示来源是什么
     */
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 此处将origin参数解析为请求来源,也可以规定使用header等其他信息
        // String origin = request.getHeader("origin");
        String origin = request.getParameter("origin");
        if (StringUtils.isBlank(origin)) {
            throw new IllegalArgumentException("error!");
        }
        return origin;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

其实就是根据规定将来源信息设置上,用以之后各种针对来源的流控规则校验。配置好以上来源解析后,就可以在控制台配置根据来源的流控规则了,会根据上述解析的来源判断是否限流。

# 5.16.3 Sentinel资源RESTful支持

对于RESTful请求,如 /shares/{id},对于不同的参数,Sentinel会将其视为不同的资源。为解决这种问题,可以使用 UrlCleaner 接口,使用方式如下

/**
 * UrlCleaner:Sentinel提供用于重置用于标识资源Url的接口
 */
@Component
public class MyUrlCleaner implements UrlCleaner {
    /**
     * 重置Url
     * @param originUrl 原始Url
     * @return 返回用于标识资源的Url
     */
    @Override
    public String clean(String originUrl) {
        // 让 /shares/1 和 /shares/2 都记作 /shares/{number},视为相同资源
        String[] split = originUrl.split("/");
        return Arrays.stream(split)
                .map(e -> NumberUtils.isNumber(e) ? "{number}" : e)   //数字转换
                .reduce((a, b) -> a + "/" + b)                        //累加操作
                .orElse("");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这里自定义的UrlCleaner实现类重新处理的请求的Url,将其视为相同的资源。

参考:Sentinel RESTful 接口流控处理优化 (opens new window)

# 5.16.4 Sentinel拓展的本质

上述三小节讲述了如下3个拓展接口

  • BlockExceptionHandler:提供Sentinel异常处理
  • RequestOriginParser:来源支持
  • UrlCleaner:重新定义资源名称

这些拓展点的本质是由 SentinelWebInterceptor 调用的,利用Spring MVC的拦截器实现的。老版本使用的是过滤器CommonFilter,1.7.1版本之后被SentinelWebInterceptor代替(更新说明 (opens new window))。

所以可以修改SentinelWebInterceptor源码融入自己的逻辑。

编辑 (opens new window)
上次更新: 2023/06/04, 12:34:19
第04章-Feign声明式HTTP客户端
第06章-RocketMQ消息队列

← 第04章-Feign声明式HTTP客户端 第06章-RocketMQ消息队列→

最近更新
01
第01章-RabbitMQ导学
02-10
02
第02章-入门RabbitMQ核心概念
02-10
03
第03章-RabbitMQ高级特性
02-10
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CodeAshen | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式