使用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>
这里引入了Sentinel对Spring 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 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。你可以访问客户端的接口几次,然后刷新控制台,出现下面的
然后点击 +流控对接口GET:/foo/bar
新增流控规则。以下是属性对照图:
添加完毕针对GET:/foo/bar
的限流就生效了。
控制台的实时流量监控数据只在内存保留5分钟,如果需要查历史流量甚至对接Grafana平台,就必须将监控数据持久化,网上有很多方案,有需要的可以自行搜索资料。
4.总结
今天对Sentinel的流控功能进行了简单的学习,其实它还可以实现很多有用的功能,熔断降级、热点参数限流、系统自适应限流、黑白名单控制等等。中文文档也算比较完备,有兴趣可以了解。好了今天就到这里,多多关注:码农小胖哥 获取更多的编程干货。
评论系统未开启,无法评论!