SpringCloudAlibaba - 分布式流量防卫兵Sentinel

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

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

服务保护的基本概念:

服务限流/熔断

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

【服务降级】

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

【服务雪崩】

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

【服务隔离机制】

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

 SentinelHystrix
隔离策略基于并发数线程池隔离/信号量隔离
熔断降级策略基于响应时间或失败比率基于失败比率
实时指标实现滑动窗口滑动窗口(基于 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平均响应时间还是比较重要的;顺便说一下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:

      

注意:如果不传热点参数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 "您当前访问的频率过高,请稍后重试!";
    }
}

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

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

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

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

【本篇总结】

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

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值