初识Sentinel

雪崩问题及解决方案

image.png
常见方式:

  • 超时处理:设置超时时间,超时返回错误,不会无休止等待
  • 舱壁模式:规定每个业务线程数,避免耗尽tomcat资源,也叫线程隔离
  • 熔断降级:断路器统计业务异常比例,超出阈值则熔断该业务
  • 流量控制:限制业务访问QPS,避免因流量突增而故障

服务保护技术对比

image.png
Sentinel介绍和安装
Sentinel官网
启动Sentinel

java -jar sentinel-dashboard-1.8.2.jar

访问

http://localhost:8080/

修改配置

java -jar sentinel-dashboard-1.8.2.jar -Dserver.port=8090

微服务整合Sentinel

引入cloud-demo
结构
Day1 微服务保护 - 图7

先启动nacos,修改yaml中mysql配置

startup.cmd -m standalone

  1. 引入sentinel依赖
  2. 配置控制台地址
  3. 访问微服务任意端点,触发sentinel监控

image.png

限流规则

簇点链路

默认sentinel监控SpringMVC的每一个端点(Controller中方法),因此SpringMVC的每个端点(Endpoint)就是调用链路中的一个资源。
Jmeter快速入门.md
启动Jmeter

运行Jmeter.bat

image.png
image.png

快速入门

image.png

流控模式

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

    直接

    关联image.png

    使用场景:用户支付修改订单状态,和用户查询订单操作争抢数据库锁。有限支付修改订单业务,因此当修改业务触发阈值时,对查询业务限流。
    Demo:

  • 新增两个端点:/order/query和/order/update

  • 配置流控规则,当/update的QPS>5时,对/query限流

结果:用sentinel访问/update,网页端请求/query时被限流
image.png

链路

例如有两条请求:

  • /test1 -> /common
  • /test2 -> /common

只统计/test2 到 /common 的请求,配置如下
image.png
使用场景:查询订单和创建订单都需要查询商品。只对查询订单进入到查询商品的请求做统计限流
Demo:

  1. 在OrderService添加queryGoods方法
  2. 在OrderController中,/order/query端点,调用queryGoods方法
  3. 在OrderController中,/order/save端点,调用queryGoods方法
  4. 设置queryGoods限流,从/order/query进入的QPS<2

因为queryGoods不是Controller中的方法,需要做注解并在yml中关闭context整合

  1. @SentinelResource("goods")
  2. public void queryGoods(){
  3. System.err.println("查询商品");
  4. }
  1. sentinel:
  2. transport:
  3. dashboard: localhost:8080 # sentinel控制台地址
  4. web-context-unify: false # 关闭context整合

为goods配置流控规则
image.png
在JMeter中做测试,同时向save和query发送请求,可以发现后者被限流

image.pngimage.png

也可以在sentinel的监控中看到结果
image.png

流控效果

指达到流控阈值时采取的措施,包括

  • 快速失败
  • warm up
  • 排队等待

    快速失败

    上面做的demo

warm up

预热模式,是服务冷启动方案。请求阈值初始threshold/coldFactor,持续指定时长后,逐渐提高到threshold值,coldFactor默认值3。(人话:一开始只发挥1/3功效)
image.png
demo
image.png
使用JMeter测试
image.pngimage.png

排队等待

请求超过QPS阈值时,快速失败和warm up会拒绝并抛异常。排队等待则是让所有请求进入队列,按照阈值允许的时间间隔依次执行。等待时间超出最大时长被拒绝。
image.png

demo:给/order/{orderId}设置限流,QPS为10,利用排队流控效果,超时时长5s
image.png
结果:起到流量整形作用,大部分请求被通过
image.png

热点参数限流

访问/goods/{id}的请求中,id参数值会有变化,热点参数限流会根据参数值分别统计QPS,统计结果:
image.png
当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。

demo:给/order/{orderId}添加热点参数限流

  • 默认热点参数规则每1秒请求不超过2
  • 102参数设置例外,每1秒不超过4
  • 103参数设置例外,每1秒不超过10

注意:热点参数限流对默认SpringMVC资源无效,添加注解

@SentinelResource("hot")
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
    // 根据id查询订单并返回
    return orderService.queryOrderById(orderId);
}

配置
image.png
JMeter测试
结果:不同参数结果不同
image.png

隔离和降级

限流只是避免高并发引起服务故障,若出现其他原因的故障,则需要通过隔离和降级,将故障控制在一定范围,防止雪崩
image.png

image.png
线程隔离和熔断降级,都是对客户端(调用方)的保护。
实现方式👇

Feign整合Sentinel

  1. 修改OrderService的yml文件,开启Feign的Sentinel功能
  2. 给FeignClient编写失败的降级逻辑
    1. 方式一:FallbackClass,无法处理远程调用的异常
    2. 方式二:FallbackFactory,可处理…🆗

步骤一:在feign-api中定义类,实现FallbackFactory

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
                log.error("查询用户异常", throwable);
                return new User();
            }
        };
    }
}

步骤二:将UserClientFallbackFactory注册为Bean

@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
    return new UserClientFallbackFactory();
}

步骤三:在UserClient接口中使用UserClientFallbackFactory

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

注册成功后查到image.png

线程隔离

image.png
两种实现方式:

  • 线程池隔离
  • 信号量隔离(Sentinel默认)

demo:给UserClient查询用户接口设置流控规则,线程<2。
image.png
JMeter测试

熔断降级

断路器统计异常比例,超出阈值会熔断,拦截请求;服务恢复时,断路器恢复放行。
image.png

熔断策略-慢调用

慢调用:业务响应时长(RT)大于指定时长被认定为慢调用。指定时间,慢调用比例大于阈值,触发熔断

demo:UserClient的查询用户接口设置降级规则,慢调用RT阈值50ms,统计时间1s,最小请求数5,失败阈值0.4,熔断时长5

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) throws InterruptedException {
    if (id == 1) {
        // 休眠,触发熔断
        Thread.sleep(60);
    } 
    return userService.queryById(id);
}

image.png
手动访问order/101,快速刷新5次,熔断触发
image.png

熔断策略-异常比例、异常数

demo:给UserClient查询用户接口设置降级规则,统计时间1秒,最小请求数5,失败阈值比例0.4,熔断时长5s

    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id,
                          @RequestHeader(value = "Truth", required = false) String truth) throws InterruptedException {
        if (id == 1) {
            // 休眠,触发熔断
            Thread.sleep(60);
        }
        else if (id == 2) {
            throw new RuntimeException("故意出错,触发熔断");
        }
        return userService.queryById(id);
    }

image.png

授权规则及规则持久化

授权规则

网关?

授权规则

image.png

  • 白名单/黑名单
  • 资源名:受保护的资源
  • 流控应用:来源者名单

获取origin:定义RequestOriginParser实现类

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 1.获取请求头
        String origin = request.getHeader("origin");
        // 2.非空判断
        if (StringUtils.isEmpty(origin)) {
            origin = "blank";
        }
        return origin;
    }
}

给网关添加请求头:修改gateway服务的yml

spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=origin,gateway
      routes:
       # ...略

作用:从gateway路由的请求带上origin头,值为gateway
配置授权规则
image.png
作用:跳过网关直接访问order-service服务被拦截,通过网关访问不受影响
image.png
image.png

自定义异常结果

BlockExceptionHandler接口

public interface BlockExceptionHandler {
    /**
     * 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
     */
    void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}

三个参数:

  • HttpServletRequest request:request对象
  • HttpServletResponse response:response对象
  • BlockException e:被sentinel拦截时抛出的异常

BlockException包含的子类:

异常 说明
FlowException 限流异常
ParamFlowException 热点参数限流的异常
DegradeException 降级异常
AuthorityException 授权规则异常
SystemBlockException 系统规则异常

在order-service中自定义异常处理类

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

测试,不同常见返回不同异常信息
image.png
image.png

规则持久化

因为sentinel的规则都是存储在内存中,重启后规则会丢失,生产环境下需要做持久化。
规则能否持久化取决于管理模式,sentinel支持的三种模式: