一些收藏springcloud

微服务架构 | 5.2 基于 Sentinel 的服务限流及熔断

2022-01-30  本文已影响0人  多氯环己烷

前言

参考资料
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
《Sentinel GitHub 官网》
《Sentinel 官网》

Sentinel 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、服务降级、系统负载保护等多个维度来帮助我们保障微服务的稳定性;


1. Sentinel 基础知识

1.1 Sentinel 的特性

Sentinel 的特性.png Sentinel 的开源生态.png

1.2 Sentinel 的组成

1.3 Sentinel 控制台上的 9 个功能

功能 说明
实时监控 实时监控每个资源名(接口、请求路径)的通过 QPS、拒绝 QPS 和响应时间;
簇点链路 通过树状视图和列表视图展示接口调用的关系以及通过 QPS、拒绝 QPS、并发数、平均 RT、分钟通过和拒绝等信息;
流控规则 又称:流量控制(flow control)。其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性;
熔断规则 对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩;
热点规则 又称:热点参数限流规则。热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制;
系统规则 系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性;
授权规则 根据调用来源来判断该次请求是否允许放行;
集群流控 集群流控可以解决流量不均匀导致总体限流效果不佳的问题;
机器列表 收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线;
Sentinel 控制台上的 9 个功能.png

1.4 Sentinel 工作原理

Slot 插槽 说明
NodeSelectorSlot 负责收集资源的调用路径,以树状结构存储调用栈,用于根据调用路径来限流降级;
ClusterBuilderSlot 负责创建以资源名维度统计的 ClusterNode ,以及创建每个 ClusterNode 下按调用来源 origin 划分的 StatisticNode;
LogSlot 在出现限流、熔断、系统保护时负责记录日志;
AuthoritySlot 权限控制,支持黑名单和白名单两种策略;
SystemSlot 控制总的入口流量,限制条件依次是总 QPS、总线程数、RT 阈值、操作系统当前 load1、操作系统当前 CPU 利用率;
FlowSlot 根据限流规则和各个Node中的统计数据进行限流判断;
DegradeSlot 根据熔断规则和各个Node中的统计数据进行服务降级;
StatisticSlot 统计不同维度的请求数、通过数、限流数、线程数等 runtime 信息,这些数据存储在 DefaultNode、OriginNode 和 ClusterNode 中;

1.5 Sentinel 源码分析

2. 安装并运行 Sentinel 控制台

Sentinel 与 Nacos 类似,有两种安装方式:使用已经编译好的安装包和源码部署;由于要对 Sentinel 源码进行分析,这里推荐源码部署;
这里选择的版本是 1.8.3;

2.1 安装包安装 Sentinel 控制台

2.1.1 下载 Sentinel

从官网下载 Sentinel.png

2.1.2 使用命令启动 Sentinel 控制台

打开 cmd 命令窗口.png 启动 Sentinel.png

2.1.3 访问 Sentinel 控制台

成功启动 sentinel.png

2.2 源码部署 Sentinel 控制台

2.2.1 拉取源码

拉取 Sentinel 源码.png

2.2.2 启动 Sentinel 控制台

3. Spring Cloud Nacos 集成 Sentinel

3.1 引入 pom.xml 依赖文件

<!-- Sentinel 核心包 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

3.2 修改 bootstrap.yml 配置文件

spring:
  application:
    name: nacos-config-client #必须,构成 Nacos 配置管理 Data ID 字段的一部分
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos 服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos 作为配置中心地址
        file-extension: yaml #指定 yaml 格式的配置
    #以下新增
    sentinel:
      transport:
        dashboard: localhost:28080  #配置Sentinel dashboard地址
        port: 8719  #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口

3.3 编写业务类

@RestController
public class ConfigClientController {
    @GetMapping("/easytest")
    public String testMothod(){
        return "test";
    }
}
监控效果界面.png

3.4 基于 Sentinel 控制台添加容灾规则

3.4.1 流控规则页面说明

下图表示 1 秒钟内查询 1 次就是 OK,若超过次数 1,就直接-快速失败,报默认错误;

流控规则控制页面.png

3.4.2 熔断规则页面说明

下图表示当我们的请求响应超过 1000ms ,并且该统计的请求比例超过 50% 时(统计的请求数量需要大于 5),触发熔断;
经过熔断时长 5s 后进入探测恢复状态,若下一个请求响应时间小于 1000ms,则熔断结束;反之再次熔断;

熔断规则控制界面.png

3.4.3 热点规则页面说明

1. 简单示例

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandlerTestHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2){
    return "testHotKey";
}
public String dealHandlerTestHotKey(String p1, String p2, BlockException exception) {
    return "dealHandler_testHotKey";
}

下图表示第一个参数(索引为0)有值的话(对应上述代码的 p1),1 秒的 QPS 为 1,超过就限流,限流后调用 dealHandler_testHotKey 支持方法;

热点规则控制界面.png

2. 参数例外项配置

当 p1 的值等于 5 时,它的阈值可以达到 200;

热点规则控制界面-参数例外配置.png

3.4.4 系统规则页面说明

系统规则控制界面.png

3.4.5 授权规则页面说明

授权规则控制界面.png

4. 使用 @SentinelResource 自定义限流处理逻辑

4.1 注解参数属性说明

public @interface SentinelResource {
    //资源名称,必需项(不能为空)
    String value() default "";
    
    //entry 类型,有 IN 和 OUT 两个选项,(默认为 EntryType.OUT)
    EntryType entryType() default EntryType.OUT;

    int resourceType() default 0;

    //对应处理 BlockException 的函数名称,可选项
    String blockHandler() default "";

    /** 
     * blockHandler 函数默认需要和原方法在同一个类中,如果希望使用其他类的函数,则需要指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
    Class<?>[] blockHandlerClass() default {};
    
    /**
     * fallback 函数默认需要和原方法在同一个类中;
     * 若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象;
     * 对应的函数必需为 static 函数,否则无法解析;
     **/
    String fallback() default "";

    /**
     * 默认的 fallback 函数名称,可选项,通常用于通用的 fallback逻辑(即可以用于很多服务或方法);
     * 默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理;
     * 若同时配置了 fallback 和 defaultFallback,则只有 fallback会生效;
     * defaultFallback 函数签名要求:返回值类型必须与原函数返回值类型一致;方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;
     * defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析;
     **/ 
    String defaultFallback() default "";

    /**
     * fallback 函数名称,可选项,用于在抛出异常的时候提供  fallback 处理逻辑;
     * fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理;
     * fallback 函数签名和位置要求:返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常
     **/
    Class<?>[] fallbackClass() default {};
    
    //用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出
    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};

    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

4.2 配置与代码的关系

图形配置和代码关系.png

4.3 自定义限流处理逻辑

4.3.1 创建 CustomerBlockHandler 类用于自定义限流处理逻辑

在 handler 包下新建 CustomerBlockHandler 类;

public class CustomerBlockHandler{
    public static CommonResult handlerException(BlockException exception){
        return new CommonResult(4444,"按客戶自定义,global handlerException----1");
    }
    public static CommonResult handlerException2(BlockException exception){
        return new CommonResult(4444,"按客戶自定义,global handlerException----2");
    }
}

4.3.2 在 controller 接口上配置自定义逻辑

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
public CommonResult customerBlockHandler(){
    return new CommonResult(200,"按客户自定义限流处理逻辑");
}
代码对应.png

4.3.3 在 Sentinel 控制台上配置

在 Sentinel 控制台上配置.png

5. 手动配置流控规则

5.1 controller 接口

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandlerTestHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2){
    return "testHotKey";
}
public String dealHandlerTestHotKey(String p1, String p2, BlockException exception) {
    return "dealHandler_testHotKey";
}

5.2 实现 InitFunc 接口

public class FlowRuleInitFunc implements InitFunc{
    @Override 
    public void init() throws Exception{
        List<FlowRule>rules=new ArrayList<>(); 
        FlowRule rule=new FlowRule(); 
        rule.setcount(1); 
        rule.setResource("testHotKey"); 
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); 
        rule.setLimitApp("default"); 
        rules.add(rule); 
        FlowRuleManager.loadRules(rules);
}

6. Sentinel 规则持久化

6.1 添加 pom.xml 依赖文件

<!-- nacos 配置中心 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Sentinel 持久化相关 -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

6.2 修改 bootstrap.yml 配置文件

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml
    sentinel:
      transport:
        dashboard: localhost:28080
        port: 8719
      # 以下新增  
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848  #将规则保存进 Nacos 配置中心
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json #指定配置项的内容格式,可选:JSON、XML。如果需要自定义,则可以将值配置为 custom,并配置 converter-class 指向 converte r类;
            rule-type: flow #数据源中规则的类型,可选:flow、degrade、param-flow、gw-flow

6.3 在 Nacos 服务器上添加配置

对 easytest 接口添加流控规则.png

6.4 一些说明

7. Sentinel 控制台集成 Nacos 实现规则同步

7.1 修改依赖与配置文件

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-nacos</artifactId>
  <!--<scope>test</scope>-->
<dependency>
<li ui-sref-active="active" ng-if="!entry.isGateway">
  <!--<a ui-sref="dashboard.flowV1({app: entry.app})">-->
  <a ui-sref="dashboard.flow({app: entry.app})">
    <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>
</li>

7.2 新建 Nacos 规则包,里面存放与同步的配置类

新建五个类.png

7.2.1 新建 NacosPropertiesConfiguration 类

@ConfigurationProperties(prefix="sentinel.nacos")
public class NacosPropertiesConfiguration {
    private String serverAddr;
    private String dataId;
    private String groupId = "DEFAULT_GROUP";
    private String namespace;
    //这里省略 get/set 方法
}    

7.2.2 新建 NacosConfiguration 类

@EnableConfigurationProperties(NacosPropertiesConfiguration.class)
@Configuration
public class NacosConfiguration {

    //Converter 转换器,将 FlowRuleEntity 转化成 FlowRule,以及反向转化
    @Bean
    public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder(){
        return JSON::toJSONString;
    }

    @Bean
    public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder(){
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    }

    //注入 Nacos 服务 ConfigService
    @Bean
    public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws NacosException {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
        properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
        return ConfigFactory.createConfigService(properties);
    }
}

7.2.3 新建 NacosConstants 类

public class NacosConstants {
    public static final String DATA_ID_POSTFIX = "-sentinel-flow";
    public static final String GROUP_ID = "DEFAULT_GROUP";
}

7.2.4 新建 FlowRuleNacosProvider 类

@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {

    private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosProvider.class);

    @Autowired
    private NacosPropertiesConfiguration nacosConfigProperties;

    @Autowired
    private ConfigService configService;

    @Autowired
    private Converter<String, List<FlowRuleEntity>> converter;

    @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
        String dataID = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
        //通过ConfigServic.getConfig方法从Nacos Config Server中读取指定配置信息,并通过converter转化为FlowRule规则
        String rules = configService.getConfig(dataID, nacosConfigProperties.getGroupId(), 3000);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}

7.2.5 新建 FlowRuleNacosPublisher 类

@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {

    @Autowired
    private NacosPropertiesConfiguration nacosPropertiesConfiguration;

    @Autowired
    private ConfigService configService;

    @Autowired
    private Converter<List<FlowRuleEntity>, String> converter;

    @Override
    public void publish(String appName, List<FlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(appName, "appName cannot be empty");
        if (rules == null) {
            return;
        }
        String dataID = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
        configService.publishConfig(dataID, nacosPropertiesConfiguration.getGroupId(), converter.convert(rules));
    }
}

7.3 修改 FlowControllerV2 类

修改 FlowControllerV2 类.png

7.4 修改 Nacos 客户端

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 
      config:
        server-addr: localhost:8848
        file-extension: yaml
    sentinel:
      transport:
        dashboard: localhost:28080 
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848 
            # 修改下面这条
            dataId: ${spring.appliction.name}-sentinel-flow   
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

8. 自定义 URL 限流异常和 URL 资源清洗

8.1 自定义 URL 限流异常

8.1.1 问题描述

8.1.2 实现 UrlBlockHandler 并且重写 blocked() 方法

@Service public class CustomurlBlockHandler implements Ur1BlockHandler{
    @Override 
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpservletResponse, BlockException e)throws IOException{
    httpServletResponse.setHeader("Content-Type","application/json;charset=UTF-8");
    String message="{\"code\":999,\"msg\":\"访问人数过多\"}";
    httpServletResponse.getWriter().write(message);
}

8.2 URL 资源清洗

8.2.1 问题描述

8.2.2 实现 UrICleaner 并重写 clean() 方法

@Service 
public class CustomerUrlCleaner implements UrlCleaner{
    @Override 
    public String clean(String originurl){
        if(Stringutils.isEmpty(originur1)){
            return originUrl;
    }
    if(originUr1.startswith("/clean/")){
        return"/clean/*";
    }
    return originUrl;
}

最后

\color{blue}{\rm\small{新人制作,如有错误,欢迎指出,感激不尽!}}

\color{blue}{\rm\small{欢迎关注我,并与我交流!}}

\color{blue}{\rm\small{如需转载,请标注出处!}}

上一篇下一篇

猜你喜欢

热点阅读