springcloud 整合sentinel

yangxiaohui227 / 2023-08-07 / 原文

一、参考官网: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请求的方法: