springcloud 整合sentinel
一、参考官网:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
1. 搭建sentinel Dashborad
1. 下载jar包: Releases · alibaba/Sentinel (github.com)
2. 启动:java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
3. 如果8080端口冲突,就更改端口
2. springcloud项目接入:
1.引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2.在application.yml中配置:
spring: cloud: sentinel: transport: port: 8719 dashboard: localhost:8080
随便访问Controller:
点击流控:配置流控规则:每秒只允许一个请求
请求超过限制报错:
3. 自定义报错:
/** * author: yangxiaohui * date: 2023/8/7 */ @Component public class SentinelBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { httpServletResponse.setCharacterEncoding("utf-8"); JSONObject jsonObject = new JSONObject(); jsonObject.put("ErrorCode","500"); jsonObject.put("Msg","To Many Request"); httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject)); } }
源码分析:sentinel为何能够对http请求进行限流以及为何能自定义返回对象:(省略部分代码)
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { String origin = parseOrigin(request); String contextName = getContextName(request); ContextUtil.enter(contextName, origin);
//sentinel 限流核心代码,在这里不分析 如果限流不通过就会抛异常BlockException Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
return true; } catch (BlockException e) { try { handleBlockException(request, response, e); //限流失败的返回值 } finally { ContextUtil.exit(); } return false; } } protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() != null) { //限流失败最终是交给 BaseWebMvcConfig的一个接口处理 baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); } else { // Throw BlockException directly. Users need to handle it in Spring global exception handler. throw e; } } }
通过源码分析,如果了解过springMvc源码可以知道,要拦截请求只要定义一个拦截器HandlerInterceptor 即可,在拦截的方法里,根据请求路径,校验请求有没达到限流的要求,达到就抛异常,捕捉到异常后,在异常里面处理响应逻辑:
之后我们再看看SentinelWebAutoConfiguration这个配置类:
@Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnProperty( name = {"spring.cloud.sentinel.enabled"}, matchIfMissing = true ) @ConditionalOnClass({SentinelWebInterceptor.class}) @EnableConfigurationProperties({SentinelProperties.class}) public class SentinelWebAutoConfiguration implements WebMvcConfigurer { private static final Logger log = LoggerFactory.getLogger(SentinelWebAutoConfiguration.class); @Autowired private SentinelProperties properties; @Autowired private Optional<UrlCleaner> urlCleanerOptional; @Autowired private Optional<BlockExceptionHandler> blockExceptionHandlerOptional; @Autowired private Optional<RequestOriginParser> requestOriginParserOptional; @Autowired private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional; public SentinelWebAutoConfiguration() { } //将拦截器也就是SentinelWebInterceptor(继承了AbstractSentinelInterceptor)注册到Springmvc中 public void addInterceptors(InterceptorRegistry registry) { if (this.sentinelWebInterceptorOptional.isPresent()) { Filter filterConfig = this.properties.getFilter(); registry.addInterceptor((HandlerInterceptor)this.sentinelWebInterceptorOptional.get()).order(filterConfig.getOrder()).addPathPatterns(filterConfig.getUrlPatterns()); log.info("[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.", filterConfig.getUrlPatterns()); } } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true )
//拦截器 public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) { return new SentinelWebInterceptor(sentinelWebMvcConfig); } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true )
//SentinelWebMvcConfig 继承了BaseWebMvcConfig public SentinelWebMvcConfig sentinelWebMvcConfig() { SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig(); sentinelWebMvcConfig.setHttpMethodSpecify(this.properties.getHttpMethodSpecify()); sentinelWebMvcConfig.setWebContextUnify(this.properties.getWebContextUnify()); if (this.blockExceptionHandlerOptional.isPresent()) { //如果容器中提供了限流异常处理器,就用提供的 this.blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler); } else if (StringUtils.hasText(this.properties.getBlockPage())) { //如果提供了限流异常跳转页面 sentinelWebMvcConfig.setBlockExceptionHandler((request, response, e) -> { response.sendRedirect(this.properties.getBlockPage()); }); } else { //都没提供就用默认的 sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); } this.urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner); this.requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser); return sentinelWebMvcConfig; } }
通过代码分析,如果没有提供异常处理器,就会有个默认的异常处理,提供了,就用提供的,我们看看默认的 DefaultBlockExceptionHandler
所以,我们自定义的异常处理器可以替换默认的异常处理器
4. 前面分析,sentinel整合springcloud默认会对http请求进行拦截,本质是springmvc的拦截器导致的,那么如何对feign进行限流配置呢?
1. 在application.yaml中开启配置:
feign:
sentinel:
enabled: true
2. 简单demo示例
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) String echo(@PathVariable("str") String str); } class FeignConfiguration { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } } class EchoServiceFallback implements EchoService { @Override public String echo(@PathVariable("str") String str) { return "echo fallback"; } }
5.如果我们想拦截非http请求的方法: