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请求的方法:基于 @SentinelResource注解实现拦截

/**
 * author: yangxiaohui
 * date:   2023/8/7
 */
@Service
public class TestService {

    @SentinelResource(value = "sayHello",blockHandler = "blockHandler",blockHandlerClass ={BlockClass.class} )
    public String sayHello(String name) {
        return "Hello, " + name;
    }

    public static class BlockClass{
        //这个方法必须是静态方法
        public static String blockHandler(String name, BlockException blockException){

            System.out.println("被阻塞了。。。。。。。。。。。。");

            return "haha";

        }

    }
}

实现原理是AOP:

@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }

    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);

        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            Object result = pjp.proceed();
            return result;
        } catch (BlockException ex) {
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex);
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }
}

 6. gateWay网关配置:

     1.引入依赖:

   

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

     2. 配置yml

spring:
  cloud:
    sentinel:
      filter:
        enabled: false
      transport:
        port: 8719
        dashboard: localhost:8080       
  

调用后:

 

   3. 限流失败后,返回默认错误

/**
 * author: yangxiaohui
 * date:   2023/8/7
 */
@Configuration
public class SentinelGatewayConfig {
    public SentinelGatewayConfig() {
        GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("ErrorCode","500");
                jsonObject.put("Msg","To Many Request");
                return ServerResponse.ok().body(Mono.just(JSON.toJSONString(jsonObject)), String.class);
            }
        });
    }
}

 7.前面各种框架的整合,核心代码,其实还是:

try (Entry entry = SphU.entry("HelloWorld")) {
    // Your business logic here.
    System.out.println("hello world");
} catch (BlockException e) {
    // Handle rejected request.
    e.printStackTrace();
}

 

 源码大概:

 

 用官网的一张原理图展示:

 具体源码过程

 8.持久化流控规则 使用nacos 

     1.引入依赖 在自己项目中引入

  <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
  </dependency>

      2. 在yaml中配置:这里以流控规则Flow为例,其他的规则如热点规则等,只要改下名称、dataId 、rule-type

     

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      datasource:
        # 名称随意
        flow:
          nacos:
            server-addr: 192.168.50.161:8848
            namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99
            dataId: sentinel-flow
            groupId: DEFAULT_GROUP
            rule-type: flow

对应nacos的值:

 

 

规则是一个数组,可以配置多个:

Flow规则:

 

[
  {
    // 资源名
    "resource": "/test",
    // 针对来源,若为 default 则不区分调用来源
    "limitApp": "default",
    // 限流阈值类型(1:QPS;0:并发线程数)
    "grade": 1,
    // 阈值
    "count": 1,
    // 是否是集群模式
    "clusterMode": false,
    // 流控效果(0:快速失败;1:Warm Up(预热模式);2:排队等待)
    "controlBehavior": 0,
    // 流控模式(0:直接;1:关联;2:链路)
    "strategy": 0,
    // 预热时间(秒,预热模式需要此参数)
    "warmUpPeriodSec": 10,
    // 超时时间(排队等待模式需要此参数)
    "maxQueueingTimeMs": 500,
    // 关联资源、入口资源(关联、链路模式)
    "refResource": "ddd"
  }
]

 降级规则:

[
  {
      // 资源名
    "resource": "/test1",
    "limitApp": "default",
    // 熔断策略(0:慢调用比例,1:异常比率,2:异常计数)
    "grade": 0,
    // 最大RT、比例阈值、异常数
    "count": 200,
    // 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
    "slowRatioThreshold": 0.2,
    // 最小请求数
    "minRequestAmount": 5,
    // 当单位统计时长(类中默认1000)
    "statIntervalMs": 1000,
    // 熔断时长
    "timeWindow": 10
  }
]

热点规则:

[
  {
      // 资源名
    "resource": "/test1",
    // 限流模式(QPS 模式,不可更改)
    "grade": 1,
    // 参数索引
    "paramIdx": 0,
    // 单机阈值
    "count": 13,
    // 统计窗口时长
    "durationInSec": 6,
    // 是否集群 默认false
    "clusterMode": 默认false,
    // 
    "burstCount": 0,
    // 集群模式配置
    "clusterConfig": {
      // 
      "fallbackToLocalWhenFail": true,
         // 
      "flowId": 2,
      // 
      "sampleCount": 10,
      // 
      "thresholdType": 0,
      // 
      "windowIntervalMs": 1000
    },
    // 流控效果(支持快速失败和匀速排队模式)
    "controlBehavior": 0,
    // 
    "limitApp": "default",
    // 
    "maxQueueingTimeMs": 0,
    // 高级选项
    "paramFlowItemList": [
      {
          // 参数类型
        "classType": "int",
          // 限流阈值
        "count": 222,
          // 参数值
        "object": "2"
      }
    ]
  }
]

系统规则:

[
  {
      // RT
    "avgRt": 1,
    // CPU 使用率
    "highestCpuUsage": -1,
    // LOAD
    "highestSystemLoad": -1,
    // 线程数
    "maxThread": -1,
    // 入口 QPS
    "qps": -1
  }
]

授权规则:

[
  {
    // 资源名
    "resource": "sentinel_spring_web_context",
      // 流控应用
    "limitApp": "/test",
    // 授权类型(0代表白名单;1代表黑名单。)
    "strategy": 0
  }
]

注意:配置时,去掉上面的注释,保证json格式正确

配置多个规则时,在yml中:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      datasource:
        # 名称随意
        flow:
          nacos:
            server-addr: 192.168.50.161:8848
            namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99
            dataId: sentinel-flow
            groupId: DEFAULT_GROUP
            rule-type: flow
        #系统规则
        system:
          nacos:
            server-addr: 192.168.50.161:8848
            namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99
            dataId: sentinel-system
            groupId: DEFAULT_GROUP
            rule-type: system

rule-type 配置表示该数据源中的规则属于哪种类型的规则(flow, degrade, authority, system, param-flow, gw-flow, gw-api-group)。注意网关流控规则 (GatewayFlowRule) 对应 gw-flow。

 

posted @ 2023-08-07 10:13  yangxiaohui227  阅读(983)  评论(0编辑  收藏  举报