• Spring Cloud Gateway 使用 Redis 限流使用教程


    从本文开始,笔者将总结 spring cloud 相关内容的教程

    版本选择

    为了适应 java8,笔者选择了下面的版本,后续会出 java17的以SpringBoot3.0.X为主的教程

    SpringBoot 版本 2.6.5

    SpringCloud 版本 2021.0.1

    SpringCloudAlibaba 版本 2021.0.1.0

    SpringCloudAlibaba github 版本说明截图

    SpringCloud 官网:https://spring.io/projects/spring-cloud

    Spring Cloud Alibaba 官网:https://sca.aliyun.com/zh-cn/

    目录

    1、环境准备

    2、项目创建

    3、测试限流

    4、改进限流返回

    5、项目代码


    1、环境准备

    本文讲解Spring Cloud Gateway 使用 Redis 限流,注册中心使用 Nacos

    Macos 官网:https://nacos.io/zh-cn/index.html

    Nacos 安装这里不做过多介绍,不了解的朋友可以参考

    Nacos 单机安装:https://blog.csdn.net/wsjzzcbq/article/details/123916233

    Nacos 集群安装:https://blog.csdn.net/wsjzzcbq/article/details/123956116

    笔者使用 docker 开启 nacos 和 redis

    Spring Cloud Alibaba  2021.0.1.0 版本对应的nacos版本是 1.4.2

    笔者使用的 Naocs 版本是 2.0.0-bugfix,redis 版本是 7.0.6

    2、项目创建

    新建 maven 聚合项目 cloud-learn

    最外层父工程 cloud-learn 的 pom.xml

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <modelVersion>4.0.0modelVersion>
    6. <groupId>com.wsjzzcbqgroupId>
    7. <artifactId>cloud-learnartifactId>
    8. <version>1.0-SNAPSHOTversion>
    9. <modules>
    10. <module>gateway-learnmodule>
    11. <module>consumer-learnmodule>
    12. modules>
    13. <packaging>pompackaging>
    14. <repositories>
    15. <repository>
    16. <id>naxus-aliyunid>
    17. <name>naxus-aliyunname>
    18. <url>https://maven.aliyun.com/repository/publicurl>
    19. <releases>
    20. <enabled>trueenabled>
    21. releases>
    22. <snapshots>
    23. <enabled>falseenabled>
    24. snapshots>
    25. repository>
    26. repositories>
    27. <parent>
    28. <groupId>org.springframework.bootgroupId>
    29. <artifactId>spring-boot-starter-parentartifactId>
    30. <version>2.6.5version>
    31. <relativePath/>
    32. parent>
    33. <properties>
    34. <spring-cloud.version>2021.0.1spring-cloud.version>
    35. <spring-cloud-alibaba.version>2021.0.1.0spring-cloud-alibaba.version>
    36. <alibaba-nacos-discovery.veriosn>2021.1alibaba-nacos-discovery.veriosn>
    37. <alibaba-nacos-config.version>2021.1alibaba-nacos-config.version>
    38. <spring-cloud-starter-bootstrap.version>3.1.1spring-cloud-starter-bootstrap.version>
    39. properties>
    40. <dependencyManagement>
    41. <dependencies>
    42. <dependency>
    43. <groupId>org.springframework.cloudgroupId>
    44. <artifactId>spring-cloud-dependenciesartifactId>
    45. <version>${spring-cloud.version}version>
    46. <type>pomtype>
    47. <scope>importscope>
    48. dependency>
    49. <dependency>
    50. <groupId>com.alibaba.cloudgroupId>
    51. <artifactId>spring-cloud-alibaba-dependenciesartifactId>
    52. <version>${spring-cloud-alibaba.version}version>
    53. <type>pomtype>
    54. <scope>importscope>
    55. dependency>
    56. <dependency>
    57. <groupId>com.alibaba.cloudgroupId>
    58. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    59. <version>${alibaba-nacos-discovery.veriosn}version>
    60. dependency>
    61. <dependency>
    62. <groupId>com.alibaba.cloudgroupId>
    63. <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    64. <version>${alibaba-nacos-config.version}version>
    65. dependency>
    66. <dependency>
    67. <groupId>org.springframework.cloudgroupId>
    68. <artifactId>spring-cloud-starter-bootstrapartifactId>
    69. <version>${spring-cloud-starter-bootstrap.version}version>
    70. dependency>
    71. <dependency>
    72. <groupId>com.alibaba.fastjson2groupId>
    73. <artifactId>fastjson2artifactId>
    74. <version>2.0.40version>
    75. dependency>
    76. dependencies>
    77. dependencyManagement>
    78. <dependencies>
    79. <dependency>
    80. <groupId>org.projectlombokgroupId>
    81. <artifactId>lombokartifactId>
    82. dependency>
    83. dependencies>
    84. project>

    然后创建2个子工程 consumer-learn 和 gateway-learn

    gateway-learn 中配置路由转发到 consumer-learn

    consumer-learn 工程 pom.xml

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>cloud-learnartifactId>
    7. <groupId>com.wsjzzcbqgroupId>
    8. <version>1.0-SNAPSHOTversion>
    9. parent>
    10. <modelVersion>4.0.0modelVersion>
    11. <artifactId>consumer-learnartifactId>
    12. <dependencies>
    13. <dependency>
    14. <groupId>org.springframework.bootgroupId>
    15. <artifactId>spring-boot-starter-webartifactId>
    16. dependency>
    17. <dependency>
    18. <groupId>com.alibaba.cloudgroupId>
    19. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    20. dependency>
    21. <dependency>
    22. <groupId>org.springframework.cloudgroupId>
    23. <artifactId>spring-cloud-starter-openfeignartifactId>
    24. dependency>
    25. <dependency>
    26. <groupId>org.springframework.cloudgroupId>
    27. <artifactId>spring-cloud-starter-loadbalancerartifactId>
    28. dependency>
    29. dependencies>
    30. <build>
    31. <plugins>
    32. <plugin>
    33. <groupId>org.springframework.bootgroupId>
    34. <artifactId>spring-boot-maven-pluginartifactId>
    35. plugin>
    36. plugins>
    37. build>
    38. project>

    consumer-learn 工程 启动类

    1. package com.wsjzzcbq;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.cloud.openfeign.EnableFeignClients;
    5. /**
    6. * ConsumerApplication
    7. *
    8. * @author wsjz
    9. * @date 2023/09/17
    10. */
    11. @EnableFeignClients
    12. @SpringBootApplication
    13. public class ConsumerApplication {
    14. public static void main(String[] args) {
    15. SpringApplication.run(ConsumerApplication.class, args);
    16. }
    17. }

    consumer-learn 工程 配置文件

    1. spring.application.name=consumer-learn
    2. server.port=8081
    3. spring.cloud.nacos.discovery.username=nacos
    4. spring.cloud.nacos.discovery.password=nacos
    5. spring.cloud.nacos.discovery.server-addr=192.168.31.152:8848
    6. spring.cloud.nacos.discovery.namespace=public
    7. logging.level.com.alibaba.cloudlearnconsumer.feign.ProducerService=DEBUG

    consumer-learn 工程 controller

    1. package com.wsjzzcbq.controller;
    2. import org.springframework.web.bind.annotation.RequestMapping;
    3. import org.springframework.web.bind.annotation.RestController;
    4. /**
    5. * ConsumerController
    6. *
    7. * @author wsjz
    8. * @date 2023/09/17
    9. */
    10. @RestController
    11. public class ConsumerController {
    12. @RequestMapping("/name")
    13. public String user() {
    14. return "宝剑锋从磨砺出,梅花香自苦寒来";
    15. }
    16. }

    gateway-learn

    gateway-learn 工程 pom.xml

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>cloud-learnartifactId>
    7. <groupId>com.wsjzzcbqgroupId>
    8. <version>1.0-SNAPSHOTversion>
    9. parent>
    10. <modelVersion>4.0.0modelVersion>
    11. <artifactId>gateway-learnartifactId>
    12. <dependencies>
    13. <dependency>
    14. <groupId>org.springframework.cloudgroupId>
    15. <artifactId>spring-cloud-starter-gatewayartifactId>
    16. dependency>
    17. <dependency>
    18. <groupId>com.alibaba.cloudgroupId>
    19. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    20. dependency>
    21. <dependency>
    22. <groupId>org.springframework.cloudgroupId>
    23. <artifactId>spring-cloud-starter-loadbalancerartifactId>
    24. dependency>
    25. <dependency>
    26. <groupId>org.springframework.bootgroupId>
    27. <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
    28. dependency>
    29. <dependency>
    30. <groupId>com.alibaba.fastjson2groupId>
    31. <artifactId>fastjson2artifactId>
    32. dependency>
    33. dependencies>
    34. <build>
    35. <plugins>
    36. <plugin>
    37. <groupId>org.springframework.bootgroupId>
    38. <artifactId>spring-boot-maven-pluginartifactId>
    39. plugin>
    40. plugins>
    41. build>
    42. project>

    gateway-learn 工程 启动类

    1. package com.wsjzzcbq;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. /**
    5. * GatewayApplication
    6. *
    7. * @author wsjz
    8. * @date 2023/09/17
    9. */
    10. @SpringBootApplication
    11. public class GatewayApplication {
    12. public static void main(String[] args) {
    13. SpringApplication.run(GatewayApplication.class, args);
    14. }
    15. }

    限流需要实现 KeyResolver 接口的 resolve 方法

    在 resolve 方法中返回限流的维度,如请求路径、ip地址、请求参数等

    笔者这里限流维度是请求路径

    1. package com.wsjzzcbq.limit;
    2. import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    3. import org.springframework.stereotype.Component;
    4. import org.springframework.web.server.ServerWebExchange;
    5. import reactor.core.publisher.Mono;
    6. /**
    7. * ApiKeyResolver
    8. *
    9. * @author wsjz
    10. * @date 2023/09/17
    11. */
    12. @Component
    13. public class ApiKeyResolver implements KeyResolver {
    14. @Override
    15. public Mono resolve(ServerWebExchange exchange) {
    16. System.out.println("API限流: " + exchange.getRequest().getPath().value());
    17. return Mono.just(exchange.getRequest().getPath().value());
    18. }
    19. }

    gateway-learn 工程 配置文件

    1. server:
    2. port: 9000
    3. spring:
    4. application:
    5. name: gateway-learn
    6. redis:
    7. host: 192.168.31.152
    8. password: 123456
    9. timeout: 5000
    10. database: 0
    11. cloud:
    12. nacos:
    13. discovery:
    14. server-addr: http://192.168.31.152:8848
    15. gateway:
    16. routes:
    17. - id: consumer-learn
    18. uri: lb://consumer-learn
    19. predicates:
    20. - Path=/cloudlearn/consumer/**
    21. filters:
    22. - name: RequestRateLimiter
    23. args:
    24. key-resolver: "#{@apiKeyResolver}"
    25. redis-rate-limiter.replenishRate: 1 #生成令牌速率:个/秒
    26. redis-rate-limiter.burstCapacity: 2 #令牌桶容量
    27. redis-rate-limiter.requestedTokens: 1 #每次消费的Token数量,默认是 1
    28. - StripPrefix=2

    配置说明:

    RequestRateLimiter 是 gateway 提供的限流过滤器

    #{@apiKeyResolver} 是笔者实现的 ApiKeyResolver

    redis-rate-limiter.replenishRate 生成令牌的速率每秒几个

    redis-rate-limiter.burstCapacity 令牌桶容量

    redis-rate-limiter.requestedTokens 每次消费的令牌数量

    当请求到网关以 /cloudlearn/consumer/  为开头前缀时,会路由到 consumer-learn 服务上

    创建完成的项目结构

    3、测试限流

    分别启动 consumer-learn 和 gateway-learn

    登录 Nacos 控制台查看

    启动成功后,可在Naocs 控制台查看注册服务信息

    浏览器访问测试限流:http://localhost:9000/cloudlearn/consumer/name

    运行效果

    可以看到当1秒钟内请求超过2次时会被限流

    4、改进限流返回

    上面代码实现了限流,但限流触发后返回的是429,不利于前端处理,这里我们可以在默认的限流过滤器基础上进行改进,自定义限流时的返回

    新建 NewRequestRateLimiterGatewayFilterFactory 类

    继承默认的 RequestRateLimiterGatewayFilterFactory

    1. package com.wsjzzcbq.filter;
    2. import com.alibaba.fastjson2.JSONObject;
    3. import org.springframework.cloud.gateway.filter.GatewayFilter;
    4. import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
    5. import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    6. import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
    7. import org.springframework.cloud.gateway.route.Route;
    8. import org.springframework.cloud.gateway.support.HttpStatusHolder;
    9. import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
    10. import org.springframework.core.io.buffer.DataBuffer;
    11. import org.springframework.http.HttpStatus;
    12. import org.springframework.http.server.reactive.ServerHttpResponse;
    13. import org.springframework.stereotype.Component;
    14. import reactor.core.publisher.Mono;
    15. import java.nio.charset.StandardCharsets;
    16. import java.util.Iterator;
    17. import java.util.Map;
    18. /**
    19. * NewRequestRateLimiterGatewayFilterFactory
    20. *
    21. * @author wsjz
    22. * @date 2023/09/17
    23. */
    24. @Component
    25. public class NewRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {
    26. private final RateLimiter defaultRateLimiter;
    27. private final KeyResolver defaultKeyResolver;
    28. private boolean denyEmptyKey = true;
    29. private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
    30. public NewRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
    31. super(defaultRateLimiter, defaultKeyResolver);
    32. this.defaultRateLimiter = defaultRateLimiter;
    33. this.defaultKeyResolver = defaultKeyResolver;
    34. }
    35. @Override
    36. public GatewayFilter apply(Config config) {
    37. System.out.println("过滤限流");
    38. KeyResolver resolver = (KeyResolver)this.getOrDefault(config.getKeyResolver(), this.defaultKeyResolver);
    39. RateLimiter limiter = (RateLimiter)this.getOrDefault(config.getRateLimiter(), this.defaultRateLimiter);
    40. boolean denyEmpty = (Boolean)this.getOrDefault(config.getDenyEmptyKey(), this.denyEmptyKey);
    41. HttpStatusHolder emptyKeyStatus = HttpStatusHolder.parse((String)this.getOrDefault(config.getEmptyKeyStatus(), this.emptyKeyStatusCode));
    42. return (exchange, chain) -> {
    43. return resolver.resolve(exchange).defaultIfEmpty("____EMPTY_KEY__").flatMap((key) -> {
    44. if ("____EMPTY_KEY__".equals(key)) {
    45. if (denyEmpty) {
    46. ServerWebExchangeUtils.setResponseStatus(exchange, emptyKeyStatus);
    47. return exchange.getResponse().setComplete();
    48. } else {
    49. return chain.filter(exchange);
    50. }
    51. } else {
    52. String routeId = config.getRouteId();
    53. if (routeId == null) {
    54. Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    55. routeId = route.getId();
    56. }
    57. return limiter.isAllowed(routeId, key).flatMap((response) -> {
    58. Iterator var4 = response.getHeaders().entrySet().iterator();
    59. while(var4.hasNext()) {
    60. Map.Entry header = (Map.Entry)var4.next();
    61. exchange.getResponse().getHeaders().add((String)header.getKey(), (String)header.getValue());
    62. }
    63. if (response.isAllowed()) {
    64. return chain.filter(exchange);
    65. } else {
    66. ServerHttpResponse httpResponse = exchange.getResponse();
    67. httpResponse.getHeaders().set("Content-Type", "application/json");
    68. JSONObject json = new JSONObject();
    69. json.put("code", 0);
    70. json.put("msg", "当前请求人数较多,请稍后再访问");
    71. DataBuffer dataBuffer = httpResponse.bufferFactory().wrap(json.toJSONString().getBytes(StandardCharsets.UTF_8));
    72. return httpResponse.writeWith(Mono.just(dataBuffer));
    73. }
    74. });
    75. }
    76. });
    77. };
    78. }
    79. private T getOrDefault(T configValue, T defaultValue) {
    80. return configValue != null ? configValue : defaultValue;
    81. }
    82. }
    83. 修改配置文件

      配置我们自定义的限流过滤器

      1. server:
      2. port: 9000
      3. spring:
      4. application:
      5. name: gateway-learn
      6. redis:
      7. host: 192.168.31.152
      8. password: 123456
      9. timeout: 5000
      10. database: 0
      11. cloud:
      12. nacos:
      13. discovery:
      14. server-addr: http://192.168.31.152:8848
      15. gateway:
      16. routes:
      17. - id: consumer-learn
      18. uri: lb://consumer-learn
      19. predicates:
      20. - Path=/cloudlearn/consumer/**
      21. filters:
      22. - name: NewRequestRateLimiter
      23. args:
      24. key-resolver: "#{@apiKeyResolver}"
      25. redis-rate-limiter.replenishRate: 1 #生成令牌速率:个/秒
      26. redis-rate-limiter.burstCapacity: 2 #令牌桶容量
      27. redis-rate-limiter.requestedTokens: 1 #每次消费的Token数量
      28. - StripPrefix=2

      重新启动 gateway-learn

      请求测试

      5、项目代码

      码云地址:https://gitee.com/wsjzzcbq/csdn-blog/tree/master/cloud-learn

      至此完

    84. 相关阅读:
      加密算法 — — 对token进行非对称加密【RSA】
      使用传统方式遍历集合过滤元素和使用流遍历集合过滤元素的区别
      刷题笔记17——KMP
      【回归预测-LSTM预测】基于灰狼算法优化LSTM实现数据回归预测附Matlab代码
      近半年内连获5家“巨头”投资,这家智能驾驶“黑马”受资本追捧
      GPT-4并非世界模型,LeCun双手赞同!ACL力证LLM无法模拟真实世界
      月报总结|Moonbeam 8月份大事一览
      1003 Emergency
      springboot+vue+elementUI 广场舞团高校舞蹈社团管理系统-#毕业设计
      软件测试流程分享
    85. 原文地址:https://blog.csdn.net/wsjzzcbq/article/details/133244158