06 微服务组件 - Sentinel流量治理

205 阅读7分钟

写在最前面:谈谈服务雪崩、降级与熔断

向上限流,向下熔断!

服务降级有很多种降级方式,如开关降级、限流降级、熔断降级!
服务熔断属于降级方式的一种!

1. 分布式系统遇到的问题

在分布式服务中,一个业务逻辑通常会依赖多个服务,如果其中的积分服务不可用,就会出现线程池里所有的线程因等待响应而被阻塞,从而造成整个服务链路不可用,进而导致整个系统的服务雪崩。如图: image.png

服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应。

导致服务不可用的原因: image.png 在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致进一步加大请求流量。所以导致雪崩效应的根本原因是:大量请求线程同步等待造成的资源耗尽。

解决方案

常见的容错机制:

  • 超时机制:调用下游时加入超时机制,一旦超时就释放资源,在一定程度上可以抑制资源耗尽的问题;
  • 服务限流:下游提供固定QPS处理的能力,超过后的请求直接拒绝;
  • 隔离:用户的请求将不再直接访问服务,而是通过线程池中空闲的线程来访问,如果线程池满了,则执行降级处理;
  • 服务熔断:下游服务不稳定或网络抖动时暂时关闭请求;
  • 服务降级:当某个服务熔断之后,服务将不再被调用,而是用本地的fallback回调返回固定值。(这里熔断和降级概念上不是很清晰!)

2. Sentinel:分布式系统的流量防卫兵

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

3. 快速开始

官网:快速开始基本使用-资源与规则

  1. 引入Sentinel核心库依赖:
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-core</artifactId>
    </dependency>
    
  2. 定义资源、流控规则:
    @Slf4j
    @RestController
    @RequestMapping("/sentinel")
    public class SentinelController {
    
        private static final String RESOURCE_NAME = "resource";
    
        @PostConstruct
        private void initFlowRules(){
            // 配置规则
            List<FlowRule> rules = new ArrayList<>();
            FlowRule rule = new FlowRule();
            rule.setResource(RESOURCE_NAME);
            rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
            rule.setCount(1);
            rules.add(rule);
            FlowRuleManager.loadRules(rules);
        }
    
        @PostMapping("/query")
        public String query(@RequestBody Object obj) {
            log.debug("sentinel 请求req:{}", JSON.toJSON(obj));
            Entry entry = null;
            try {
                // 定义资源
                entry = SphU.entry(RESOURCE_NAME);
                // 被保护的逻辑
                log.info("hello world");
                return "success";
            } catch (BlockException e) {
                // 处理被流控的逻辑
                log.info("block!");
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
            return "error";
        }
    }
    
  3. 跑一把,nice: image.png

缺点:

  1. 业务侵入性很强,需要在controller中写入非业务代码;
  2. 配置不灵活,若需要添加新的受保护资源,需要手动添加init方法来添加流控规则。

4. @SentinelResource注解

官网:sentinel 注解支持

  1. 引入sentinel切面依赖:
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-annotation-aspectj</artifactId>
    </dependency>
    
  2. 定义规则(流量控制规则),注解定义资源:
    /**
     * SentinelResource注解
     * value:定义资源
     * blockHandlerClass/blockHandler:指定限流降级处理的类/方法
     * fallbackClass/fallback: 指定接口业务异常处理的类/方法
     * exceptionsToIgnore:指定哪些异常被排除掉,不会计入异常统计中,也不会进入fallback逻辑中,而是会原样抛出
     * 特别地,若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
     */
    @PostMapping("/annotation/query")
    @SentinelResource(value = RESOURCE_NAME, blockHandlerClass = StockFeignClientBlockHandler.class, blockHandler = "query",
            fallbackClass = StockFeignClientFallBackHandler.class, fallback = "query")
    public String query2(@RequestBody Object obj) {
        log.debug("sentinel 请求2req:{}", JSON.toJSONString(obj));
        // 业务逻辑
        Integer num = 1 / 0;
        log.info("hello world");
        return "success";
    }
    
    @Slf4j
    public class StockFeignClientBlockHandler {
    
        /**
         * 降级方法:
         * 1. 一定是public方法;
         * 2. 如果和原方法不在同一个类,用static修饰;
         * 3. 入参返参和原方法保持一致,且加BlockException入参
         */
        public static String query(Object req, BlockException blockException) {
            log.warn("请求库存StockFeignClient.query,接口熔断,req={}", JSON.toJSONString(req));
            return StringUtils.EMPTY;
        }
    }
    
    @Slf4j
    public class StockFeignClientFallBackHandler {
        /**
         * 业务异常处理方法:
         * 1. 一定是public方法;
         * 2. 如果和原方法不在同一个类,用static修饰;
         * 3. 入参返参和原方法保持一致,且可以额外多一个Throwable类型的参数用于接收对应的异常。
         */
        public static String query(Object req, Throwable throwable) {
            log.warn("请求库存StockFeignClient.query,业务异常,req={}", JSON.toJSONString(req), throwable);
            return StringUtils.EMPTY;
        }
    }
    
  3. 跑一把,nice: image.png

5. Sentinel 规则的分类

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。

Sentinel支持以下几种规则:流量控制规则/熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则。

5.1 流量控制规则

image.png 详见:流量控制规则

5.2 熔断降级规则

image.png 详见:熔断降级规则

Sentinel的熔断策略:对弱依赖应用才能配置熔断降级!!!

image.png

6. Sentinel 控制台

Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上,我们可以配置规则并实时查看流量控制效果。 image.png

Sentinel 控制台

  1. 下载控制台jar包在本地启动:java -jar -Dserver.port=8848 sentinel-dashboard-1.8.0.jar image.png

  2. 访问控制台:http://localhost:8848/#/login  默认用户名密码:sentinel/sentinel image.png image.png

Sentinel会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量!

7. SpringCloud Alibaba整合Sentinel

  1. 引入依赖:

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  2. 添加配置,为应用配置sentinel控制台地址:

    #sentinel的控制台地址
    spring.cloud.sentinel.transport.dashboard=127.0.0.1:8848
    #指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
    spring.cloud.sentinel.transport.port=8846
    
  3. 启动应用,发起一次调用,nice: image.png

7.1 微服务和Sentinel Dashboard通信原理

image.png

sentinel控制台获取客户端数据api文档

通信细节Sentinel学习(六) —— 控制台和客户端通信原理 image.png

8. Setinel控制台规则配置详解

8.1 流控规则

流量控制(flow control),其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。 image.png

  1. 阀值类型:

    • QPS;
    • 线程数(并发数控制用于保护业务线程池不被慢调用耗尽)
  2. 流控模式:

    • 直接(默认);
    • 关联(两个资源之间具有资源争抢或者依赖关系时);image.png
    • 链路(根据调用链路入口限流)。image.png
  3. 流控效果:

    • 快速失败(默认);
    • Warm Up(避免激增流量打垮系统);image.pngimage.png
    • 排队等待(脉冲流量:用于处理间隔性突发的流量)。

8.2 熔断降级规则

我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。 image.png

  1. 熔断策略: 经过熔断时长后熔断器会进入探测恢复状态(HALF OPEN状态),若接下来的一个请求响应时间小于设置的慢调用 RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。(异常比例和异常数同理!!)
    • 慢调用比例;image.png
    • 异常比例;image.png
    • 异常数。image.png

9. 热点参数流控

9.1 使用场景

热点参数限流 image.png

9.2 配置方式

注意:资源名必须是@SentinelResource(value="资源名")中配置的资源名,热点规则依赖于注解! image.png

参数为基本数据类型 OR 对象 的不同写法image.png

// 入参实现ParamFlowArgument对象写法
@Data
public class HotParamFlow implements ParamFlowArgument {

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 场景
     */
    private String scene;

    /**
     * 其它参数
     */
    private Object obj;

    @Override
    public Object paramFlowKey() {
        return this.scene;
    }
}

根据场景限流的思考:

两种限流思路:
1. 普通限流:entry = SphU.entry(scene);  // scene是一个入参,String类型
    此时资源个数是scene的枚举数量,可以在控制台根据不同的scene值设置不同的限流值,场景不配置不限流。
2. 热点参数限流:@SentinelResource(value = "scene.Resource") // 注解下方法入参有scene
    此时资源个数是1个,配置的限流阀值对所有的scene值都生效,可以配置例外项。

扩展