SpringCloudAlibaba - 分布式流量防卫兵Sentinel

一. 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的返回结果。(下图演示效果为一秒内刷新两次):

    

那么问题来了,每次重启都需要执行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,登录成功页面如下:

② 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控制台多出了一个应用:

  新写一个测试接口:

@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:点击流控规则 → 新增流控规则:

三. Sentinel持久化

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

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


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

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

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

count:限流阈值

strategy:调用关系限流策略

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

clusterMode:是否为集群模式


2. 修改订单服务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配置的流控规则:

 访问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);
    }
}


注意new GatewayFlowRule("order")  里面的order即为配置文件中routes中的-id,而不是path。

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

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

  ① 新建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有自己的降级方法,网关优先级更高,先被执行)

五. 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 - 服务降级";
    }

    

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

    

注意:① 代码中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 "错误率太高,展示无法访问该接口";
    }

    

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

    

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

3. 异常数

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

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

【总结】:个人认为,异常比例和异常数没多大用处,Rt平均响应时间还是比较重要的。

发布了45 篇原创文章 · 获赞 20 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/AkiraNicky/article/details/104183130