目录

  • 路由维度
  • 自定义API维度


  • 自1.6.0版本开始,Sentinel提供了Spring Cloud Gateway的适配模块,能针对路由(route)和自定义API分组两个维度进行限流。

路由维度

  • 路由维度指配置文件中的路由条目,资源名是对应的routeId,相比自定义API维度这是一个较为粗粒度的限流。
    下面就来实现网关路由维度的限流。
  • 1.首先导入Sentinel组件为Spring Cloud Gateway提供的适配模块依赖包,在项目pom.xml文件dependencies标签内添加如下代码。
<!--Sentinel组件为Spring Cloud Gateway提供的适配模块依赖包-->
<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
  • 2.新增配置类SentinelRouteConfiguration,实例化SentinelGatewayFilter和SentinelGatewayBlockExceptionHandler对象,初始化限流规则,自定义限流后的界面显示,具体代码如下所示。
@Configuration // 标记为配置类
public class SentinelRouteConfiguration { // 路由维度限流配置类
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public SentinelRouteConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, // 构造函数
                                      ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
    @PostConstruct
    public void initGatewayRules() { //初始化限流规则
        Set<GatewayFlowRule> rules = new HashSet<>();
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("user_route");// 资源名称,对应routeId的值 此处限流用户服务
        gatewayFlowRule.setCount(1); // 限流阀值
        gatewayFlowRule.setIntervalSec(1); // 统计时间窗口(单位:秒),默认是1秒
        rules.add(gatewayFlowRule);
        GatewayRuleManager.loadRules(rules); // 载入规则
    }
    @PostConstruct
    public void initBlockHandlers() {  // 自定义限流后的界面
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map<String, String> result = new HashMap<>(); //  限流提示
                result.put("code", "0");
                result.put("message", "您已被限流");
                return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).
                        body(BodyInserters.fromObject(result));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // 配置限流异常处理器
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() { //初始化一个限流的过滤器
        return new SentinelGatewayFilter();
    }
}

注意:Spring Cloud Gateway限流是通过Filter实现的,主要是注入SentinelGatewayFilter实例和SentinelGatewayBlockExceptionHandler实例。

  • application.yml文件依然维持最开始配置的user_route和shop_route路由。然后使用curl命令,快速调用用户服务和商品服务,每个服务都调用2次,结果如下所示。
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/user/findById?id=1
ZhangSan
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/user/findById?id=1
{"code":"0","message":"您已被限流"}
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/shop/findById?id=1
这是苹果
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/shop/findById?id=1
这是苹果
  • 快速调用/user/findById接口2次,发现第2次被限流了;快速调用/shop/findById接口2次,结果都正常,只针对user_route路由的接口生效,验证了代码的配置。

自定义API维度

  • 通过上面一种限流方式可以看出,灵活性还不够。自定义API维度可以利用Sentinel提供的API自定义分组来限流。相比路由维度这是一种更为细粒度的方式。
  • 下面来看实现自定义API维度的具体步骤。
  • 1.导入Sentinel组件为Spring Cloud Gateway提供的适配模块依赖包,前面已经导入过了,这里就不再赘述了。直接进入下一个环节,新增自定义API维度的配置类SentinelApiGroupConfiguration。
  • 依然是实例化SentinelGatewayFilter和SentinelGatewayBlockExceptionHandler对象,初始化限流规则,定义API分组,自定义限流后的界面显示,具体代码如下所示。
@Configuration // 标记为配置类
public class SentinelApiGroupConfiguration { // API分组维度限流配置类
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public SentinelApiGroupConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, // 构造函数
                                      ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
    @PostConstruct
    public void initGatewayRules() {// 初始化限流规则
        Set<GatewayFlowRule> rules = new HashSet<>();
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("user_api");
        gatewayFlowRule.setCount(1); // 限流阀值
        gatewayFlowRule.setIntervalSec(1); // 统计时间窗口(单位:秒),默认是1秒
        rules.add(gatewayFlowRule);
        GatewayRuleManager.loadRules(rules); // 载入规则
    }
    @PostConstruct
    public void initCustomizedApis() { //  自定义API分组
        Set<ApiDefinition> apiDefinitions = new HashSet<>();
        ApiDefinition apiDefinition = new ApiDefinition("user_api") // user_api是api分组名称
                .setPredicateItems(new HashSet<ApiPredicateItem>() { {
                add(new ApiPathPredicateItem().setPattern("/user/group/**") // 匹配路径
                  .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); // 匹配策略,匹配前缀
            }});
        apiDefinitions.add(apiDefinition);
        GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions); // 载入API分组定义
    }
    @PostConstruct
    public void initBlockHandlers() {  // 自定义限流后的界面
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map<String, String> result = new HashMap<>(); //  限流提示
                result.put("code", "0");
                result.put("message", "您已被限流");
                return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).
                        body(BodyInserters.fromObject(result));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // 配置限流异常处理器
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() { //初始化一个限流的过滤器
        return new SentinelGatewayFilter();
    }
}

注意:匹配路径不仅可以使用通配符,如/user/group/**,也可以固定某个地址,如/user/group/findById,如果是固定的地址,也无需再使用setMatchStrategy方法了。

  • 2.为了看到API分组更清晰的效果,新增/user/group/findById接口,代码如下所示。
@RequestMapping("/user/group/findById")
public String groupFindById(@RequestParam("id") Integer id){ // 根据id查询用户信息的方法 -- 为了测试API分组新增的方法
	return userInfo.getOrDefault(id,null);
}
  • 现在可以使用curl命令调用/user/group/findById和/user/findById接口,分别快速调用2次结果如下所示。
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/user/group/findById?id=1
ZhangSan
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/user/group/findById?id=1
{"code":"0","message":"您已被限流"}
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/user/findById?id=1
ZhangSan
D:\software\curl-7.71.1-win64-mingw\bin>curl http://localhost:8083/user/findById?id=1
ZhangSan
  • /user/group/findById接口地址是符合API分组匹配规则的,在调用第2次时被限流了。/user/findById接口地址不符合API分组匹配规则,所以快速调用时没有被限流。