文章目录
系列文章:
【Sentinel入门】01 最简单的例子 helloworld
【Sentinel入门】02 @SentinelResource语法(blockHandler、fallback)
【Sentinel入门】03 规则类型(BlockException、FlowException、ParamFlowException、DegradeException)
【Sentinel入门】04 springmvc 集成Sentinel & 全局异常处理类统一处理异常(BlockExceptionHandler接口)
【Sentinel入门】05 springmvc 集成Sentinel & springboot集成Sentinel & 链路模式失效 & WebContextUnify & CommonFilter
相关文章:
【springboot集成sentinel】 sentinel-spring-webmvc-adapter
【springmvc框架】详解WebMvcConfigurer接口(Interceptor拦截器、ResourceHandler、Formatter、CorsMapping)
前言
官网也有示例,对应sentinel-demo-spring-webmvc
通过sentinel-spring-webmvc-adapter
依赖,可以实现所有的springmvc定义的url自动作为sentinel的资源,并且支持全局异常处理类统一处理异常
注意本篇示例是基于springcloud的,而非集成springcloudAlibaba
WebCallbackManager是sentinel-web-servlet
包中的接口,入参是UrlBlockHandler子类,用法:
WebCallbackManager.setUrlBlockHandler(xxxx)
而BlockExceptionHandler
接口 是sentinel-spring-webmvc-adapter
包中的:
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
config.setBlockExceptionHandler(new MySentinelExceptionHandler());
1. springmvc 集成Sentinel
经过前面文章的介绍,我们知道了如何使用sentinel的一些基础用法,以及常见的异常处理方式,每个url的资源或方法都可以通过@SentinelResource
语法定义为一个资源,那么对于springmvc的url,每个url虽然可以作为资源。
常规声明语法:参见 Demo1Controller.java:
@RestController
public class Demo1Controller {
@Autowired
private TestService service;
//每个url入口都要声明 @SentinelResource
@GetMapping("/foo1")
@SentinelResource(value = "foo1", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public String apiFoo1(@RequestParam(required = false) Long t) throws Exception {
if (t == null) {
t = System.currentTimeMillis();
}
service.test();
return service.hello(t);
}
现在有个新的问题,如果有100个url,则需要100个声明,有没有统一的处理方式呢? 即我们定义一个全局的声明,默认100个url都使用该声明?
答案是有!
引入sentinel-spring-webmvc-adapter
后,就可以通过自带的拦截器配置,默认会把所有的url资源纳入sentinel管理,完整的工程参见官网的 sentinel-demo-spring-webmvc
2. 测试
先启动Sentinel控制台
启动应用的项目,通过在vm参数设置 -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc
或在application.properties中配置:
csp.sentinel.dashboard.server=127.0.0.1:8080
然后访问:http://127.0.0.1:8088/hello 端口改为实际使用的端口,我的10000冲突了,改为8088
此时控制台已经把url纳入了管理,在簇点链路中能看到/hello资源:
我们增加一个流控规则试试:
快速多访问几次http://127.0.0.1:8088/hello:
说明流控生效了!
提示信息“Blocked by Sentinel (flow limiting)
”来自DefaultBlockExceptionHandler
2.1 有什么特别地方吗
对比【Sentinel入门】02 @SentinelResource语法(blockHandler、fallback)里面的项目sentinel-demo-annotation-spring-aop工程,我们这里的工程没有在controller中添加@SentinelResource标签:
@Controller
public class WebMvcTestController {
//注意:这里没有@SentinelResource标签
@GetMapping("/hello")
@ResponseBody
public String apiHello() {
doBusiness();
return "Hello!";
}
3. 全局统一异常处理
此时,我们知道省略了 @SentinelResource语法,流控规则仍能生效,问题来了,出现流控异常后,怎么给处理结果呢?即 blockHandler和blockHandlerClass对应的怎么实现?
常规用法,每个url对应的方法需要增加blockHandler :
@Override
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}
而且以前的用法中,每个方法的入口都要额外用@SentinelResource声明,如果有100个url,则需要100个声明,现在,springmvc 集成Sentinel后,有统一的处理方式,即我们定义一个全局的声明,默认100个url都会使用该声明。
我们看下InterceptorConfig类,使用内置的一个BlockExceptionHandler接口的实现:
//使用内置的一个BlockExceptionHandler接口的实现
config.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
DefaultBlockExceptionHandler
是一个全局异常处理类:
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
public DefaultBlockExceptionHandler() {
}
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
StringBuffer url = request.getRequestURL();
if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) {
url.append("?").append(request.getQueryString());
}
PrintWriter out = response.getWriter();
//对应前面流控异常,浏览器展示的内容
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
3.1 自定义全局统一限流降级处理类
当然,我们也可以自定义一个我们自己的处理类,例如SentinelExceptionHandler
类,实现BlockExceptionHandler ,并将其声明为@Component注入到spring容器中:
package com.alibaba.csp.sentinel.demo.spring.webmvc.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MySentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
String msg = null;
if (ex instanceof FlowException) {
msg = "访问频繁,请稍候再试";
} else if (ex instanceof DegradeException) {
msg = "系统降级";
//ParamFlowException异常需要额外的依赖包
// <dependency>
// <groupId>com.alibaba.csp</groupId>
// <artifactId>sentinel-parameter-flow-control</artifactId>
// </dependency>
// } else if (ex instanceof ParamFlowException) {
// msg = "热点参数限流";
} else if (ex instanceof SystemBlockException) {
msg = "系统规则限流或降级";
} else if (ex instanceof AuthorityException) {
msg = "授权规则不通过";
} else {
msg = "未知限流降级";
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(msg);
}
}
然后指定并使用MySentinelExceptionHandler :
config.setBlockExceptionHandler(new MySentinelExceptionHandler());
4. springboot集成Sentinel原理
前面提到的springmvc集成Sentinel就是 基于springboot集成Sentinel,区别于spring cloud alibaba集成Sentinel ,当然springboot集成Sentinel是spring cloud alibaba集成Sentinel的基础,学会了前者的原理,有助于学习后者的原理
springboot集成Sentinel的核心是拦截器,我们定义了一个InterceptorConfig 类,他是springmvc框架下的WebMvcConfigurer
接口的实现:
WebMvcConfigurer
接口参见 【springmvc框架】详解WebMvcConfigurer接口(Interceptor拦截器、ResourceHandler、Formatter、CorsMapping)
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
//重写父类方法
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add Sentinel interceptor
addSpringMvcInterceptor(registry);
}
private void addSpringMvcInterceptor(InterceptorRegistry registry) {
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
// Depending on your situation, you can choose to process the BlockException via
// the BlockExceptionHandler or throw it directly, then handle it
// in Spring web global exception handler.
// config.setBlockExceptionHandler((request, response, e) -> { throw e; });
// Use the default handler.
config.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
// Custom configuration if necessary
config.setHttpMethodSpecify(true);
// By default web context is true, means that unify web context(i.e. use the default context name),
// in most scenarios that's enough, and it could reduce the memory footprint.
// If set it to false, entrance contexts will be separated by different URLs,
// which is useful to support "chain" relation flow strategy.
// We can change it and view different result in `Resource Chain` menu of dashboard.
config.setWebContextUnify(true);
config.setOriginParser(request -> request.getHeader("S-user"));
// 注册一个 sentinel interceptor(拦截器)
registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**");
}
registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**");
注入了一个SentinelWebInterceptor拦截器,是系统内置的一个拦截器,拦截所有的url:
//继承了AbstractSentinelInterceptor 类
public class SentinelWebInterceptor extends AbstractSentinelInterceptor {
继承了AbstractSentinelInterceptor 类,拦截器的核心方法定义在此父类中:
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
....
// Parse the request origin using registered origin parser.
String origin = parseOrigin(request);
String contextName = getContextName(request);
ContextUtil.enter(contextName, origin);
//核心代码, SphU.entry 硬编码添加sentinel资源
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
return true;
SphU.entry硬编码添加sentinel资源。也就是说通过拦截器拦截所有的url资源,并且硬编码,实现所有的url注册为sentinel资源
5. SentinelSpringMvcBlockHandlerConfig全局异常类的作用
@ControllerAdvice
@Order(0)
public class SentinelSpringMvcBlockHandlerConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(BlockException.class)
@ResponseBody
public ResultWrapper sentinelBlockHandler(BlockException e) {
logger.warn("Blocked by Sentinel: {}", e.getRule());
// Return the customized result.
return ResultWrapper.blocked();
}
}
SentinelSpringMvcBlockHandlerConfig的作用是什么?首先我们知道SentinelSpringMvcBlockHandlerConfig的作用是全局异常类,但是已经定义了MySentinelExceptionHandler处理 springmvc的sentinel异常,这里又多定义一个,为啥?
作用是兜MySentinelExceptionHandler的底,如果MySentinelExceptionHandler里面不小心抛出了BlockException异常,那么就会被catch住!
参见 注释:
* Spring configuration for global exception handler.
* This will be activated when the {@code BlockExceptionHandler}
* throws {@link BlockException directly}.
参考
Sentinel 限流降级,Sentinel持久化 参见 9、Sentinel全局限流降级结果返回