sentinel


服务雪崩

由于某一服务 导致整条服务链像滚雪球一样 导致整条服务都宕机不可用
例如 有服务ABCD 服务A调用服务B 服务B调用服务C 服务C调用服务D
由于某种原因 服务B宕机了 由于服务B宕机了 无法访问服务C 服务D 致使大量请求累计到服务B 最后致使整条服务链宕机

解决方案

限流

设置一个流量阈值 如果超过这个阈值就进行降级或其他处理 这样就降低了服务压力

线程隔离

类似限流 不同的是限制线程数量 满了的话就进行降级或其他处理

信号隔离

限制并发的访问数量

熔断和降级

熔断是指 若某一服务不可用 则立即返回 避免大量请求堆积在不可用服务 与超时不同 超时则进行访问不可用服务 等待服务修复再打开熔断器 继续访问

降级处理是兜底方法 服务不可用则可以调用降级方法 进行一些处理

sentail的使用

快速使用

引入sentinel的依赖

		
			com.alibaba.csp
			sentinel-core
			1.8.2
		

资源是sentinel的核心概念 资源就是被保护的内容 定义资源名称为sayHello 若业务正常则直接返回hello world 若遇到流控则执行catch语句段

       Entry entry = null;

        try {
            entry = SphU.entry("sayHello");

            return "Hello world";
        } catch (BlockException e) {
            e.printStackTrace();
            return "被限流";
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }

之后在定义一个流控规则 FlowRule 表示流量控制规则 setResource方法设置为哪个资源进行流控 setGrade设置流控规则为QPS
若QPS > 1 则被流控

    @PostConstruct
    private static void initFlowRules() {
        List rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("sayHello");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 20.
        rule.setCount(1);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

正常执行

QPS > 1

使用注解来定义资源以及流控 降级

导入依赖

		
			com.alibaba.csp
			sentinel-annotation-aspectj
			1.8.2
		

根据文档 首先得注入一个Bean

@Configuration
public class SentinelAspectConfiguration {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

value表示资源名称 blockHandler表示触发限流后执行的方法 fallback触发异常是执行的方法 方法必须为public 返回参数与返回值必须与原方法一致 fallback可以多添加一个throwable参数
具体规则文档有写: https://sentinelguard.io/zh-cn/docs/annotation-support.html

    @GetMapping("sayName")
    @SentinelResource(value = "sayName", blockHandler = "sayNameBlockHandler", fallback = "sayNameFallbackHandle")
    public String sayName() {
        int a = 12 / 0;

        return "my name is lyra heartstrings";
    }


    public String sayNameFallbackHandle(Throwable e) {
        e.printStackTrace();
        return "异常";
    }

    public String sayNameBlockHandler(BlockException e) {
        e.printStackTrace();
        return "被限流";
    }


    @PostConstruct
    public static void initFlowRules() {
        List rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("sayName");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 20.
        rule.setCount(1);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

熔断降级

当服务熔断时 将调用降级方法 原方法将不会访问
当熔断时间执行完毕 服务将开启半熔断模式 这是如果客户端发送一次请求 还是失败的话 继续触发熔断
基本和流控规则一样 不同的是 降级规则为DegradeRule
例子中使用的为异常数

降级参数

   @PostConstruct
    public void degradeRuleInit() {
        List degradeRules = new ArrayList<>();
        DegradeRule degradeRule = new DegradeRule();
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        degradeRule.setResource("sayName");
        // 30秒内2个请求有2个异常 则进行熔断 执行降级方法
        degradeRule.setCount(2);
		// 最小请求数2个才记录熔断规则
        degradeRule.setMinRequestAmount(2);
        degradeRule.setStatIntervalMs(1000 * 30);
        // 熔断一分钟 一分钟后开启版熔断模式 开启半熔断模式后 若之后再次发送一个请求 若请求继续失败 则继续进行熔断
        degradeRule.setTimeWindow(1000 * 60);
        degradeRules.add(degradeRule);
        DegradeRuleManager.loadRules(degradeRules);
    }

sentinel dashboard

  1. 下jar包
    https://github.com/alibaba/Sentinel/releases
  2. 根据配置参数进行配置并运行
    https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0#%E6%8E%A7%E5%88%B6%E5%8F%B0%E9%85%8D%E7%BD%AE%E9%A1%B9
    我这里设置端口为8080 帐号为setinel 密码为365373011
    java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=365373011 -jar sentinel-dashboard-1.8.2.jar

将服务注册到sentinel dashboard中

  1. 导入依赖

		
			com.alibaba.csp
			sentinel-transport-simple-http
			1.8.2
		
  1. 添加jvm参数

sping cloud aibaba整合

导入依赖

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-sentinel
        

编写sentinel dashboard服务以及端口号

spring:
  application:
    name: provider
  cloud:
    sentinel:
      transport:
        dashboard: 10.0.4.3:8080

流控规则

应用场景

  1. 服务调用端有大量的流量
  2. 不同的调用者
  3. 网关接口

使用

在簇点链路 点击相应的接口 添加限流规则


然后根据资源 添加流控方法

线程流控

服务调用这向服务提供者发送请求 由于因网络原因或慢sql等其他原因 导致服务提供者迟迟无法返回响应 导致大量线程累计到服务调用方
这时就需要线程流控了

线程流控数为1时只能有有一个用户同时进行请求 两个或两个以上的用户就会被限流

关联流控

一边用于两个请求抢占资源 两个资源同时使用 系统会变慢 这时 需要停掉一个请求 使另个请求同行
如 支付和下单两个操作 当下单和支付并发执行时 订单过多 来不及处理 这时可以将下单操作进行限流 当订单处理完毕后 再继续进行下单操作
有两个接口 一个是get 用于模拟支付操作 一个get用于模拟查询订单信息操作


    @GetMapping("/get")
    public String get() {
        return "查询";
    }

    @GetMapping("/insert")
    public String insert() {
        return "插入";
    }

当下单操作达到阈值时 将查询操作进行限流 以提高下单操作的执行速度

链路限流

根据调用方法来进行限流 controller调用service时 不同的controller调用相同的service 根据限流来设置哪个controller可以正常通过不受限流影响
在appliction中设置web-context-unify: false
新建一个service 接口 资源名称为getUser


    @SentinelResource(value = "getUser", blockHandler = "getUserHandler")
    @Override
    public String getUser() {
        return "查询用户";
    }
    
    public String getUserHandler(BlockException e) {
        return "限流";
    }

声明两个controller接口

    @GetMapping("/getLyra")
    public String getLyra() {
        return userService.getUser();
    }

    @GetMapping("/getBonBon")
    public String getBonBon() {
        return userService.getUser();
    }

设置限流规则

当getBonBon调用getUser方法到达阈值时 进行限流

预热流控(激增流量)

避免一下激增流量 打爆服务器 缓慢增加请求 达到设置的阈值
如 一个商品 在平常时 无人问津 缓存区中也没有这个商品的缓存信息 当双十一进行秒杀时 因为缓存中没有这个商品的信息 所以要从数据库中查询 当大量的请求访问数据库时 容易将数据库打停机
可以十一预热流控来解决这个问题
一次先放行少量的请求 缓慢将请求增大 慢慢的访问数据库并将数据库中差到的信息商品保存到缓存中 之后的请求直接访问缓存即可 避免了缓存击穿
计算公式为阈值/3 然后缓慢增加到阈值数

排队等待(脉冲流量)

突然大量流量然后空闲一段时间 然后大量流量进入一直循环
排队等待用于利用中间的空闲时间 来进行处理请求

没有使用排队等待前的脉冲流量 可以看到每次发生请求都有一段时间的空闲时间 我们可以使用排队等待来利用这段空闲时间

使用了排队等待之后

超时时间表示 若在6000ms内 10个QPS执行完毕 在让10个请求进入
若超时 直接失败

熔断

熔断一边用在服务调用者 也就是consumer

慢调用比例

超过 最大rt时间为慢调用 最小请求数为触发熔断的最小请求数 大于这个数的请求 才有资格被熔断

表示在1000ms中 有10%的慢调用请求就触发熔断

异常比例: 按异常比例进行熔断 到达异常比例数 触发熔断
异常数: 根据异常数进行熔断 到达异常数 触发熔断

统一流控异常

实现BlockExceptionHandler接口并注入到方法中 然后根据流量异常 返回响应内容

package com.lyra.provider.exception;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.nacos.common.http.param.MediaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lyra.provider.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyBlockHandlerException implements BlockExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(MyBlockHandlerException.class);

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        logger.info("rule: ==========" + e.getRule());

        Result result = null;

        if (e instanceof FlowException) {
            result = Result.errorResult(400, "被流控");
        } else if (e instanceof ParamFlowException) {
            result = Result.errorResult(401, "热点参数流控");
        } else if (e instanceof DegradeException) {
            result = Result.errorResult(402, "被降级");
        } else if (e instanceof AuthorityException) {
            result = Result.errorResult(403, "授权规则未通过");
        } else if (e instanceof SystemBlockException) {
            result = Result.errorResult(404, "触发系统保护规则");
        }
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON);

        httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }
}

热点限流

一般用于限流方法参数中出现的高频数值进行限流
业务场景: 商品秒杀操作 如iphone之类的热门商品一定会被高频搜索 可以设置对iphone进行限流
不知道为什么dashboard一直设置不成功
dashboard只要添加热点规则 然后添加热点参数即可完成 不知道为什么一直无法保存
参素索引为方法所在的下标 单击阈值为参数所出现的QPS 统计时长为每x秒进行一次统计QPS

这个方法的name参数 索引就为0

还可以对参数中的指定值进行限流

当参数为Dreamer时 切阈值大于2 则进行限流、

使用代码进行配置 根据文档 https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
将热点配置添加即可

    @PostConstruct
    public void paramFlowRoleSet() {
        List paramFlowItems = new ArrayList<>();
        ParamFlowRule rule = new ParamFlowRule("sayHello");
        rule.setParamIdx(0);
        rule.setCount(10);

        ParamFlowItem paramFlowItem = new ParamFlowItem();
        paramFlowItem.setObject("Dreamer");
        paramFlowItem.setCount(2);
        rule.setParamFlowItemList(Collections.singletonList(paramFlowItem));
        paramFlowItems.add(rule);

        ParamFlowRuleManager.loadRules(paramFlowItems);
    }

系统保护规则

因不明确的原因设置一个兜底方法 用于保护系统
可以以一下参数进行设置
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 达到阈值即触发系统保护。

sentinel整合open-fign进行降级

消费者调用服务提供者 服务提供者产生异常 会将异常传递给消费者 我们不希望服务提供者将异常传递给消费者 可以使用feign进行降级处理

首先feign服务导入依赖

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

在consumer开启sentinel支持

feign:
  sentinel:
    enabled: true

降级方法实现feign接口

@Component
public class TestControllerFallBack implements TestControllerAPI {
    @Override
    public String sayHello(String name) {
        return "feign降级";
    }
}

feign接口 feign client注解添加faillback类

@FeignClient(name = "provider", fallback = TestControllerFallBack.class)
public interface TestControllerAPI {
    @GetMapping("/sayHello")
    public String sayHello(@RequestParam("name") String name);
}

服务提供者抛出一个异常

    @Override
    public String sayHello(@RequestParam String name) {
        int a = 10 / 0;
        return "My name is: " + name;
    }

然后消费者调用