logo头像

From zero to HERO

使用Sentinel对Spring MVC接口进行限流

1.前言

Spring Cloud Alibaba提供了中间件Sentinel,它以流量为切入点,提供了流量控制、熔断降级、系统负载保护等多个功能来保护服务的稳定性。今天就来尝试一下。

本文是在Spring Boot 2.3.4.RELEASE的基础之上构建的

2.依赖引入

和其它教程通过Spring Cloud Starter引入的不同,这里使用更加底层一些的依赖引入来让我们深入的了解一些Sentinel,这里引入的是1.8.0版本。

<!--Sentinel 核心包-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<!--@SentinelResource注解AOP切面支持-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<!--针对Spring MVC的适配器 Spring Webflux 可引入对应的适配器-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-webmvc-adapter</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<!--负责同 dashboard 进行通信  如果你没有使用 sentinel dashboard 它是可选的-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>${sentinel.version}</version>
</dependency>

这里引入了SentinelSpring MVC的相关适配器和注解支持。

3. 使用

Sentinel的限流首先要制定限流规则,然后针对规则进行资源的标记。通过监控标记资源流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

流控规则

规则被封装到FlowRule对象中,需要声明的属性说明如下:

属性 说明 默认值
resource 资源名,资源名是限流规则的作用对象,不建议使用默认值
count 限流阈值,可以是QPS阈值,也可以是并发线程数阈值
grade 限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
limitApp 可针对性的对特定客户端的请求进行流控 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 直接拒绝
clusterMode 是否集群限流

下面定义了一个规则并加载到内存中:

// 规则对应的类为FlowRule,用List保存,可以有多个规则
List<FlowRule> rules = new ArrayList<>();
FlowRule flowRule = new FlowRule();
// 设置资源名称
flowRule.setResource("bar");
// QPS为2
flowRule.setCount(2);
// 需要在限流过滤器中设置对应的解析策略来获取
// flowRule.setLimitApp(appName);
//限流的类型
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
rules.add(flowRule);
FlowRuleManager.loadRules(rules);

上面这种硬编码方式 一般仅用于测试和演示,生产上一般通过动态规则源的方式来动态管理规则。详细参考官方文档中关于动态流控规则的描述

标记限流资源

传统情况下使用SphU 包含了 try-catch 风格的 API进行限流操作。当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。基本范式如下:

// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
  // 被保护的业务逻辑
  // do something here...
} catch (BlockException ex) {
  // 资源访问阻止,被限流或被降级
  // 在此处进行相应的处理操作
}

这种样板代码并不是非常优雅,所以Sentinel提供了@SentinelResource注解来简化开发,需要依赖sentinel-annotation-aspectj模块并显式的启用AOP切面类:

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

然后我们可以在Spring MVC接口上进行如下标记即可:

@SentinelResource(value = "bar", entryType = EntryType.IN)
@GetMapping("/bar")
public String bar() {
    return "bar";
}

无论异常处理还是注解都只建议在学习中使用,实际生产中配合sentinel-dashboard可以更加方便地、集中地进行标记配置限流资源。详情参考控制台文档

全局配置

我们还可以通过SentinelWebInterceptor或者SentinelWebTotalInterceptor来配置一些全局特性。

package cn.felord.sentinel.configuration;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class GlobalSentinelWebMvcConfiguration implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
        //指定 请求方法 POST  GET 等等
        sentinelWebMvcConfig.setHttpMethodSpecify(true);
        //默认使用统一Web上下文   如果希望支持链路关系的流控策略则应该设置为false
        sentinelWebMvcConfig.setWebContextUnify(true);
        // 统一的 BlockException 处理  FlowException(BlockException) 会被 JVM 的 UndeclaredThrowableException 包裹一层  某种原因并不能捕获到异常
//        sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
        // 用来标识来源 可针对性的对特定客户端的请求进行流控   limitApp
//        sentinelWebMvcConfig.setOriginParser(request -> request.getHeader("X-Client"));
 //       sentinelWebMvcConfig.setOriginParser(request -> request.getParameter("app"));

        //对原始的URL进行处理,比如去掉锚点之类的    /foo/bar?a=3#title  ->   /foo/bar?a=3
//        sentinelWebMvcConfig.setUrlCleaner( );
        registry.addInterceptor(new SentinelWebInterceptor(sentinelWebMvcConfig)).addPathPatterns("/**");
    }
}

流控规则中提到的limitApp就是通过setOriginParser 来配置获取策略的。

异常处理

虽然在全局配置中可以进行异常处理,但是经过胖哥测试提供的异常处理还是有些问题的,有时候无法捕捉到BlockException。 我们可以通过控制器通知来切面捕获到它:

/**
 * 捕获BlockException异常.
 */
@Slf4j
@ControllerAdvice
@Order(0)
public class SentinelControllerAdvice {
    /**
     * 异常处理.
     *
     * @param request the request
     * @param e       the e
     * @return the object
     */
    @ExceptionHandler(BlockException.class)
    @ResponseBody
    public ResponseEntity<?> sentinelBlockHandler(HttpServletRequest request, BlockException e) {
        log.warn("Blocked by Sentinel: {}", e.getRule());
        // Return the customized result.
        HashMap<String, Object> map = new HashMap<>();
        map.put("path", request.getServletPath());
        map.put("msg", "limited by sentinel");
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(map);
    }
}

控制台

其实配合控制台使用还是很简单的,先下载或者打包控制台的jar文件,通过下面的命令启动:

java  -Dserver.port=8088     -jar .\sentinel-dashboard-1.8.0.jar

客户端这时就可以移除注解相关的配置和样板代码了,但是拦截器GlobalSentinelWebMvcConfiguration配置务必保留。

客户端 ,例如sentinel-spring-boot.jar注册时,执行命令:

java -Dcsp.sentinel.dashboard.server=localhost:8088  -Dproject.name=sentinel-app -jar .\sentinel-spring-boot.jar

localhost:8088为Sentinel Dashboard服务器地址,sentinel-app为客户端名称。

注册完毕后,控制台依旧是白板。务必确保客户端有访问量Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。你可以访问客户端的接口几次,然后刷新控制台,出现下面的

sentinel dashboard控制台

然后点击 +流控对接口GET:/foo/bar新增流控规则。以下是属性对照图:

FlowRule 属性对照

添加完毕针对GET:/foo/bar的限流就生效了。

控制台的实时流量监控数据只在内存保留5分钟,如果需要查历史流量甚至对接Grafana平台,就必须将监控数据持久化,网上有很多方案,有需要的可以自行搜索资料。

4.总结

今天对Sentinel的流控功能进行了简单的学习,其实它还可以实现很多有用的功能,熔断降级、热点参数限流、系统自适应限流、黑白名单控制等等。中文文档也算比较完备,有兴趣可以了解。好了今天就到这里,多多关注:码农小胖哥 获取更多的编程干货。

评论系统未开启,无法评论!