Spring Cloud | Spring Cloud集成Sentinel实现服务限流

145 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

Spring Cloud系列文章

Sentinel概述

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。

—— 引自Sentinel官网

Sentinel支持两种资源维度的限流:

  • Route维度
  • 自定义API维度

Route维度限流

  1. 引入Maven依赖
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
  1. 网关配置类
@Configuration
@AllArgsConstructor
public class GatewayConfig {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    // 配置限流异常处理器,即触发限流后的默认处理类
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public CustomSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new CustomSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @PostConstruct
    public void doInit() {
        initGatewayRules();
    }

    // 初始化限流规则
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("jasmine-auth");
        gatewayFlowRule.setCount(1D);
        gatewayFlowRule.setIntervalSec(5L);
        rules.add(gatewayFlowRule);
        GatewayRuleManager.loadRules(rules);
    }
}
  1. 自定义全局限流异常处理器

在实际的应用中(笔者接触的项目大体是前后端分离的),我们通常以JSON的格式返回数据。我们自定义了一个异常处理器,当触发限流时返回JSON格式的异常数据。

public class CustomSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
    private final List<ViewResolver> viewResolvers;
    List<HttpMessageWriter<?>> messageWriters;

    public CustomSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers;
        this.messageWriters = serverCodecConfigurer.getWriters();
    }

    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("msg", "访问人数过多!");

        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(new Gson().toJson(map).getBytes(StandardCharsets.UTF_8));
        return serverHttpResponse.writeWith(Mono.just(dataBuffer));
    }

    // 省略无关代码
}

自定义全局限流异常处理器的代码是直接从SentinelGatewayBlockExceptionHandler中复制过来,我们只修改writeResponse方法,该方法的作用是将限流的异常信息写回客户端。

配置类的主要功能如下:

  • 注入一个全局限流控制器
  • 注入自定义全局限流异常处理器
  • 初始化限流规则

网关限流规则(GatewayFlowRule)中提供了如下属性:

  • 资源名称(resource):可以是网关中的route名称或者用户自定义的API分组名称;
  • 资源模型(resourceMode):
  • 限流指标维度(grade):同限流规则的grade字段;
  • 限流阈值(count)
  • 统计时间窗口(intervalSec):单位是秒,默认1秒;
  • 流量整形的控制效果(controlBehavior)
  • 应对突发请求时额外允许的请求数目(burst)
  • 匀速排队模式下的最长排队时间(maxQueueingTimeoutMs):单位是毫秒,仅在匀速排除模式下生效;
  • 参数限流配置(paramItem)
    • 从请求中提取参数的策略(parseStrategy),目前支持提取来源IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意Header (PARAM_PARSE_STRATEGY_HEADER)和任意URL参数(PARAM_PARSE_STRATEGY_URL_PARAM)四种模式。
    • 若提取策略选择Header模式或URL参数模式,则需要指定对应的Header名称或URL参数名称。
  1. 项目配置文件
server:
  port: 9010
spring:
  profiles:
    active: dev
  application:
    name: jasmine-sentinel
  cloud:
    nacos:
      config:
        file-extension: yaml
        group: ${spring.profiles.active}
        prefix: ${spring.application.name}
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true
          # 是否使用service-id的小写,默认是大写
          lower-case-service-id: true
      routes:
        - id: jasmine-auth
          uri: lb://jasmine-auth
          predicates:
            - Path=/auth/**

关键配置说明:

  • uri: 配置的lb://表示从注册中心获取服务,后面的jasmine-auth表示目标服务在注册中心上的服务名。
  1. 测试

最后在浏览器中访问http://127.0.0.1:9010/auth/hello,多次刷新,当触发限流规则时看到的返回结果如下所示:

{"msg":"访问人数过多!","code":500}

自定义API分组限流

自定义API分组限流可以让多个Route共用一个限流规则。

自定义Api分组限流

// 自定义Api分组限流
private void initCustomizedApis() {
    Set<ApiDefinition> definitions = new HashSet<>();
    ApiDefinition apiDefinition = new ApiDefinition("jasmine-customized-api")
        .setPredicateItems(new HashSet<ApiPredicateItem>() {{
            add(new ApiPathPredicateItem().setPattern("/auth/**"));
            add(new ApiPathPredicateItem().setPattern("/log/**")
                .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
        }});
    definitions.add(apiDefinition);
    GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}

上述代码主要是将/auth/**/log/**进行统一分组,并提供一个name=jasmine-customized-api,然后在初始化网关限流规则时,针对该name设置限流规则。同时,我们可以通过setMatchStrategy来设置不同path下的限流参数策略。

// 初始化限流规则
private void initGatewayRules() {
    Set<GatewayFlowRule> rules = new HashSet<>();
    GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("jasmine-customized-api");
    gatewayFlowRule.setCount(1D);
    gatewayFlowRule.setIntervalSec(5L);
    gatewayFlowRule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
    rules.add(gatewayFlowRule);
    GatewayRuleManager.loadRules(rules);
}

initCustomizedApis()方法和initGatewayRules()方法一样,在初始化的时候调用。

@PostConstruct
public void doInit() {
    initCustomizedApis();
    initGatewayRules();
}

这样,我们在浏览器中访问http://127.0.0.1:9010/auth/hellohttp://127.0.0.1:9010/log/hello,多次刷新,都会触发限流规则,然后看到的返回结果如下所示:

{"msg":"访问人数过多!","code":500}

网关流控实现原理

网关流控实现原理.png

图片引自官网。


如果觉得文章写得还凑合,还麻烦你动动小手帮忙点个赞呗。你的支持是我更文的动力。目前在写的系列有下面几个系统:

  • MyBatis源码解读
  • 设计模式
  • 一起学Three.js
  • Spring Boot
  • Spring Cloud

都是一些平时工作中用到的知识点的个人总结和学习笔记。