欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

程序员文章站 2022-03-10 23:08:02
Sentinel: 分布式系统的流量防卫兵具体介绍可以看官方文档:https://github.com/alibaba/Sentinel/wiki/介绍,下面我们说点官方没有的东西:服务保护的基本概念:【服务限流/熔断】服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用......

一. Sentinel: 分布式系统的流量防卫兵 - 阿里巴巴产品

具体介绍可以看官方文档:https://github.com/alibaba/Sentinel/wiki/介绍,下面我们说点官方没有的东西:

服务保护的基本概念:

服务限流/熔断

服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用我们的服务降级的方法,不会执行业务逻辑操作,直接走本地falback的方法,返回一个友好的提示。

【服务降级】

在高并发的情况下, 防止用户一直等待,采用限流/熔断方法,使用服务降级的方式返回一个友好的提示给客户端,不会执行业务逻辑请求,直接走本地的falback的方法。提示语:当前排队人数过多,稍后重试~

【服务雪崩】

默认的情况下,Tomcat或者是Jetty服务器只有一个线程池去处理客户端的请求,这样的话就是在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上, 那么就会产生tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问。

【服务隔离机制】

线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。

  Sentinel Hystrix
隔离策略 基于并发数 线程池隔离/信号量隔离
熔断降级策略 基于响应时间或失败比率 基于失败比率
实时指标实现 滑动窗口 滑动窗口(基于 RxJava)
规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件的形式
基于注解的支持 即将发布 支持
调用链路信息 支持同步调用 不支持
限流 基于 QPS / 并发数,支持基于调用关系的限流 不支持
流量整形 支持慢启动、匀速器模式 不支持
系统负载保护 支持 不支持
实时监控 API 各式各样 较为简单
控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善
常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC 等 Servlet、Spring Cloud Netflix

二. SpringBoot整合Sentinel

1. 手动代码配置

@RestController
public class OrderService {

    private static final String GETORDER_KEY = "getOrder";

    // 手动配置管理Api限流接口
    @RequestMapping("/initFlowQpsRule")
    public String initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GETORDER_KEY);
        // QPS控制在2以内
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        return "....限流配置初始化成功..";
    }
    @RequestMapping("/getOrder")
    public String getOrders() {
        Entry entry = null;
        try {
            entry = SphU.entry(GETORDER_KEY);
            // 执行我们服务需要保护的业务逻辑(省略)
            return "getOrder接口";
        } catch (Exception e) {
            e.printStackTrace();
            return "流量已达上限.";
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
}

启动项目,首先执行initFlowQpsRule接口;由于我们配置的是QPS(QueriesPerSecond意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数)为1,所以一秒内超过一次请求,则走降级,即会执行catch的返回结果。(下图演示效果为一秒内刷新两次):

SpringCloudAlibaba - 分布式流量防卫兵Sentinel    SpringCloudAlibaba - 分布式流量防卫兵Sentinel

那么问题来了,每次重启都需要执行initFlowQpsRule接口,太繁琐了,那么我们可以加入到启动加载类中:

@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
    private static final String GETORDER_KEY = "getOrder";

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GETORDER_KEY);
        // QPS控制在2以内
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        log.info(">>>限流服务接口配置加载成功>>>");
    }
}

2. 注解形式配置管理Api限流

该方式也必须先执行initFlowQpsRule或者加入启动配置类,只是接口中不用Entry和try catch了:

@RestController
public class OrderService {

    @RequestMapping("/initFlowQpsRule")
    public String initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource("getOrderAnnotation");
        // QPS控制在2以内
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        return "限流配置初始化成功..";
    }


    @SentinelResource(value = "getOrderAnnotation", blockHandler = "getOrderQpsException")
    @RequestMapping("/getOrderAnnotation")
    public String getOrderAnnotation() {
        return "getOrderAnnotation接口";
    }
    /**
     * 被限流后返回的提示
     */
    public String getOrderQpsException(BlockException e) {
        e.printStackTrace();
        return "该接口已经被限流啦!";
    }

}

 该效果和方式1效果一样,一秒内连续请求两次getOrderAnnotation接口,则会走限流回调方法getOrderQpsException。

3. Sentinel控制台管理限流接口

  ① 控制台环境搭建

下载对应Sentinel-Dashboardhttps://github.com/alibaba/Sentinel/releases/tag/1.7.1    下载后执行命令:

java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\MyTools\sentinel-dashboard-1.6.2.jar

启动后访问:http://localhost:8718,默认用户名和密码都为sentinel,登录成功页面如下:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

② SpringBoot接入Sentinel控制台

spring:
  application:
    # 服务名称
    name: service-order
  cloud:
    nacos:
      discovery:
        # nacos注册地址
        server-addr: 127.0.0.1:8848
      config:
        # 配置中心连接地址
        server-addr: 127.0.0.1:8848
        # 分组
        group: DEFAULT_GROUP
        # 类型
        file-extension: yaml
    sentinel:
      transport:
        dashboard: 127.0.0.1:8718
      eager: true

  服务加入身sentinel配置后,重启可以看到sentinel控制台多出了一个应用:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

  新写一个测试接口:

@RestController
public class OrderService {

    @SentinelResource(value = "getOrderByConsole", blockHandler = "getOrderQpsException")
    @RequestMapping("/getOrderByConsole")
    public String getOrderByConsole() {
        return "getOrderByConsole接口";
    }
    public String getOrderQpsException(BlockException e) {
        e.printStackTrace();
        return "该接口已经被限流啦!";
    }
}

  控制台需要配置资源名称getOrderByConsole:点击流控规则 → 新增流控规则:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

三. Sentinel持久化

默认的情况下Sentinel的规则是存放在内存中,如果Sentinel客户端重启后,Sentinel数据规则可能会丢失。
Sentinel持久化机制支持四种持久化的机制:(这里我们只讲整合Nacos)
1. 本地文件
2. 携程阿波罗
3. Nacos
4. Zookeeper

 1. 首先,在Nacos配置中心创建流控规则:(注意resource不要加/)

SpringCloudAlibaba - 分布式流量防卫兵Sentinel


resource:资源名,即限流规则的作用对象

limitApp:流控针对的调用来源,若为 default 则不区分调用来源

grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制

count:限流阈值

strategy:调用关系限流策略

controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)

clusterMode:是否为集群模式


2. 修改订单服务sentinel配置如下:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

   引入依赖并新增测试接口:

<!--sentinel 整合nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.5.2</version>
</dependency>
@SentinelResource(value = "getOrderSentinel", blockHandler = "getOrderSentinelBlockHandler")
@RequestMapping("/getOrderSentinel")
public String getOrderSentinel() {
    return "getOrderSentinel";
}

public String getOrderSentinelBlockHandler(BlockException e) {
    return "当前访问人数过多,请稍后重试!";
}

启动订单服务,此时,可以看到Sentinel控制台已经出现1中在Nacos配置的流控规则:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

 访问http://localhost:8090/getOrderSentinel,一秒连续两次请求,则也会走blockHandler方法。

【小结】:注意一定要删掉 SentinelApplicationRunner类,因为SpringCloudAlibaba默认Sentinel整合Nacos持久化已经在服务启动的时候,加载Nacos的配置进服务内存,所以应该注释掉启动加载类,否则会产生冲突。

四. Gateway整合Sentinel实现服务限流

上面我们都是在单个服务(订单服务)演示流控规则,下面我们说一下在微服务网关Gateway中如何整合Sentiel。

官方文档:https://github.com/alibaba/Sentinel/wiki/网关限流

  1. 首先在网关服务中额外引入以下依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.6.0</version>
</dependency>

  2. 编写固定配置类(直接拷贝如下代码即可,无需修改):

@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}

  3. 在网关配置启动加载类,配置限流规则:

@Slf4j
@Component
public class SentinelApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initGatewayRules();
    }
    // 配置限流规则
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("order")
                // 限流阈值
                .setCount(1)
                // 统计时间窗口,单位是秒,默认是 1 秒
                .setIntervalSec(1)
        );
        GatewayRuleManager.loadRules(rules);
    }
}

SpringCloudAlibaba - 分布式流量防卫兵Sentinel
注意new GatewayFlowRule("order")  里面的order即为配置文件中routes中的-id,而不是path。

启动网关,端口为81,访问返回如下:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

可见返回的提示不太直观,我们可以更改返回错误码:

  ① 新建Handler实现WebExceptionHandler

public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
    public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }
}

  ② GatewayConfiguration注释默认的SentinelGatewayBlockExceptionHandler,把自定义的注入到Spring容器中:

//    @Bean
//    @Order(Ordered.HIGHEST_PRECEDENCE)
//    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
//        // Register the block exception handler for Spring Cloud Gateway.
//        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
//    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

重启项目,一秒连续两次访问,返回结果如下:(getOrderSentinel有自己的降级方法,网关优先级更高,先被执行)

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

五. Sentinel熔断降级

官方文档:https://github.com/alibaba/Sentinel/wiki/熔断降级

服务降级的策略:

1. rt(平均响应时间)【注意是单位时间内,并不是累加】

如果在1s内访问五次,平均的响应时间超出了我们在平台设置的阈值的情况下,直接触发我们的熔断执行我们服降级的方法。

 在规定的时间窗口内(单位为秒)一直执行我们的服务降级的方法,不能够执行我们的真实业务逻辑。

    // 平均响应时间RT
    @SentinelResource(value = "getOrderRt", fallback = "getOrderRtFallback")
    @RequestMapping("/getOrderRt")
    public String getOrderRt() {
        try {
            Thread.sleep(300);
        } catch (Exception e) {
        }
        return "正常执行我们业务逻辑";
    }

    public String getOrderRtFallback() {
        return "Rt - 服务降级";
    }

SpringCloudAlibaba - 分布式流量防卫兵Sentinel    SpringCloudAlibaba - 分布式流量防卫兵Sentinel

一秒连续访问5次接口,则会走降级方法,因为平均响应时间肯定大于10,下次访问则走降级方法,3秒后解除。

SpringCloudAlibaba - 分布式流量防卫兵Sentinel    SpringCloudAlibaba - 分布式流量防卫兵Sentinel

注意:① 代码中Thread.sleep(300),如果直接return如上图IDEA代码,则平均响应时间肯定不会大于10,则一秒内访问10次甚至更多,也不会走降级方法

一旦服务重启后,降级规则失效,需要重新配置,同理也可以用Nacos持久化,自行学习,这里不讨论了~

2. 异常比例

比如客户端每秒s内发出5个请求,5个请求全部错误,这说明错误率为百分百。

每秒内发出5个请求,如果请求的异常占比超过我们设置的阈值占比的情况下,就会出发我们熔断,执行我们的服务降级方法,在规定的时间窗口内(单位为秒)不能执行我们真实业务逻辑。

    @SentinelResource(value = "getOrderException", fallback = "getOrderExceptionFallback")
    @RequestMapping("/getOrderException")
    public String getOrderException(int age) {
        int j = 1 / age;
        return "正常执行我们业务逻辑:j" + j;
    }

    public String getOrderExceptionFallback(int age) {
        return "错误率太高,展示无法访问该接口";
    }

SpringCloudAlibaba - 分布式流量防卫兵Sentinel    SpringCloudAlibaba - 分布式流量防卫兵Sentinel

每秒请求 >= 5次,异常比例为100%,则走降级,同样持续3秒。

SpringCloudAlibaba - 分布式流量防卫兵Sentinel    SpringCloudAlibaba - 分布式流量防卫兵Sentinel

注意:如果代码中手动catch,则出异常直接进catch返回了,不会走降级方法,永远不会走~

3. 异常数

当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的(单位为分钟),若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

该方式也用getOrderException测试(同一个资源名称可以有多个降级方式),会发现近一分钟连续出现5次异常,第6次访问则会走降级方法,注意时间窗口单位为分钟,所以解封时间为1分钟。注意该方式手动catch,则也不会走降级方法。

【总结】:个人认为,异常比例和异常数没多大用处,Rt平均响应时间还是比较重要的;顺便说一下fallback与blockHandler的区别:fallback是服务熔断或者业务逻辑出现异常执行的方法(1.6版本以上),blockHandler 限流出现错误执行的方法。

六. Sentinel热点参数限流

官方文档:https://github.com/alibaba/Sentinel/wiki/热点参数限流

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

1. 基本使用:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel</artifactId>
    <version>0.2.2.RELEASE</version>
</dependency>
@RestController
@Slf4j
public class SeckillService {

    // 秒杀的限流规则
    private static final String SEKILL_RULE = "seckill";

    public SeckillService() {
        // Spring容器初始化时把热点参数配置加载到jvm内存
        initSeckillRule();
    }

    private void initSeckillRule() {
        ParamFlowRule rule = new ParamFlowRule(SEKILL_RULE)
                // 对我们秒杀接口第0个参数实现限流
                .setParamIdx(0)
                .setGrade(RuleConstant.FLOW_GRADE_QPS)
                // 每秒QPS最多访问1次,如一秒2次即开始限流
                .setCount(1);
        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
        log.info(">>>秒杀接口限流策略配置成功<<<");
    }

    // 秒杀接口
    @RequestMapping("/seckill")
    public String seckill(Long userId, Long orderId) {
        try {
            Entry entry = SphU.entry(SEKILL_RULE, EntryType.IN, 1, userId);
            return "秒杀成功";
        } catch (Exception e) {
            return "当前该用户访问频率过多,请稍后重试!";
        }
    }
}

启动服务,访问接口(带userId),一秒第二次访问则会走catch:

SpringCloudAlibaba - 分布式流量防卫兵Sentinel      SpringCloudAlibaba - 分布式流量防卫兵Sentinel

注意:如果不传热点参数userId,则永远都不会走限流,如http://localhost:8090/seckill?order=123123

2. 控制台自定义:(推荐)

@RestController
@Slf4j
public class SeckillService {

    // 秒杀的限流规则
    private static final String SEKILL_RULE = "seckill";

    @RequestMapping("/seckill")
    @SentinelResource(value = SEKILL_RULE)
    public String seckill(Long userId, Long orderId) {
        return "秒杀成功";
    }

}
@RestControllerAdvice
public class InterfaceExceptionHandler {

    @ResponseBody
    @ExceptionHandler(ParamFlowException.class)
    public String businessInterfaceException(ParamFlowException e) {
        return "您当前访问的频率过高,请稍后重试!";
    }
}

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

重启服务,控制台配完热点规则,效果和上面一样。

注意:① 窗口时间只能设为1秒,可能是版本bug (sentinel-dashboard-1.6.2.jar)。

热点参数限流后,不会走fallback和blockHandler,只能全局捕获异常来自定义返回信息。

Sentinel支持热词参数Vip通道,即可以指定某些参数的规则,这个比如在某些秒杀活动中可以作弊使用,当然仅做了解,还是不推荐这样做的 ! 方式:编辑热点规则 - 高级选项,如下图,指定第一个参数为136,qps阈值为10,即一秒最多可以有10次请求,添加后则可以在浏览器无限刷新,手速快的除外 ~ 

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

SpringCloudAlibaba - 分布式流量防卫兵Sentinel

【本篇总结】

Sentinel可以说是分布式流量防御的神器,在SpringCloudAlibba中可以完全替代SpringCloud的Hystrix,易于维护,可持久化,官方文档详细,最后再贴出一遍官网文档:https://github.com/alibaba/Sentinel/wiki/介绍,有不懂的欢迎留言 ~ 

本文地址:https://blog.csdn.net/AkiraNicky/article/details/104183130

相关标签: SpringCloudAlibaba