从本文开始,笔者将总结 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/
目录
本文讲解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

新建 maven 聚合项目 cloud-learn
最外层父工程 cloud-learn 的 pom.xml
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
-
- <groupId>com.wsjzzcbqgroupId>
- <artifactId>cloud-learnartifactId>
- <version>1.0-SNAPSHOTversion>
- <modules>
- <module>gateway-learnmodule>
- <module>consumer-learnmodule>
- modules>
- <packaging>pompackaging>
-
- <repositories>
- <repository>
- <id>naxus-aliyunid>
- <name>naxus-aliyunname>
- <url>https://maven.aliyun.com/repository/publicurl>
- <releases>
- <enabled>trueenabled>
- releases>
- <snapshots>
- <enabled>falseenabled>
- snapshots>
- repository>
- repositories>
-
- <parent>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-parentartifactId>
- <version>2.6.5version>
- <relativePath/>
- parent>
-
- <properties>
- <spring-cloud.version>2021.0.1spring-cloud.version>
- <spring-cloud-alibaba.version>2021.0.1.0spring-cloud-alibaba.version>
- <alibaba-nacos-discovery.veriosn>2021.1alibaba-nacos-discovery.veriosn>
- <alibaba-nacos-config.version>2021.1alibaba-nacos-config.version>
- <spring-cloud-starter-bootstrap.version>3.1.1spring-cloud-starter-bootstrap.version>
- properties>
-
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-dependenciesartifactId>
- <version>${spring-cloud.version}version>
- <type>pomtype>
- <scope>importscope>
- dependency>
-
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-alibaba-dependenciesartifactId>
- <version>${spring-cloud-alibaba.version}version>
- <type>pomtype>
- <scope>importscope>
- dependency>
-
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
- <version>${alibaba-nacos-discovery.veriosn}version>
- dependency>
-
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
- <version>${alibaba-nacos-config.version}version>
- dependency>
-
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-bootstrapartifactId>
- <version>${spring-cloud-starter-bootstrap.version}version>
- dependency>
-
- <dependency>
- <groupId>com.alibaba.fastjson2groupId>
- <artifactId>fastjson2artifactId>
- <version>2.0.40version>
- dependency>
- dependencies>
- dependencyManagement>
-
- <dependencies>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- dependency>
- dependencies>
-
-
- project>
然后创建2个子工程 consumer-learn 和 gateway-learn
gateway-learn 中配置路由转发到 consumer-learn
consumer-learn 工程 pom.xml
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>cloud-learnartifactId>
- <groupId>com.wsjzzcbqgroupId>
- <version>1.0-SNAPSHOTversion>
- parent>
- <modelVersion>4.0.0modelVersion>
-
- <artifactId>consumer-learnartifactId>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
-
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-loadbalancerartifactId>
- dependency>
-
- dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-maven-pluginartifactId>
- plugin>
- plugins>
- build>
- project>
consumer-learn 工程 启动类
- package com.wsjzzcbq;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.openfeign.EnableFeignClients;
-
- /**
- * ConsumerApplication
- *
- * @author wsjz
- * @date 2023/09/17
- */
- @EnableFeignClients
- @SpringBootApplication
- public class ConsumerApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(ConsumerApplication.class, args);
- }
- }
consumer-learn 工程 配置文件
- spring.application.name=consumer-learn
- server.port=8081
- spring.cloud.nacos.discovery.username=nacos
- spring.cloud.nacos.discovery.password=nacos
- spring.cloud.nacos.discovery.server-addr=192.168.31.152:8848
- spring.cloud.nacos.discovery.namespace=public
- logging.level.com.alibaba.cloudlearnconsumer.feign.ProducerService=DEBUG
consumer-learn 工程 controller
- package com.wsjzzcbq.controller;
-
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- /**
- * ConsumerController
- *
- * @author wsjz
- * @date 2023/09/17
- */
- @RestController
- public class ConsumerController {
-
- @RequestMapping("/name")
- public String user() {
- return "宝剑锋从磨砺出,梅花香自苦寒来";
- }
- }
gateway-learn
gateway-learn 工程 pom.xml
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>cloud-learnartifactId>
- <groupId>com.wsjzzcbqgroupId>
- <version>1.0-SNAPSHOTversion>
- parent>
- <modelVersion>4.0.0modelVersion>
-
- <artifactId>gateway-learnartifactId>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-gatewayartifactId>
- dependency>
-
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
- dependency>
-
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-loadbalancerartifactId>
- dependency>
-
-
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
- dependency>
-
- <dependency>
- <groupId>com.alibaba.fastjson2groupId>
- <artifactId>fastjson2artifactId>
- dependency>
- dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-maven-pluginartifactId>
- plugin>
- plugins>
- build>
-
- project>
gateway-learn 工程 启动类
- package com.wsjzzcbq;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- /**
- * GatewayApplication
- *
- * @author wsjz
- * @date 2023/09/17
- */
- @SpringBootApplication
- public class GatewayApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(GatewayApplication.class, args);
- }
- }
限流需要实现 KeyResolver 接口的 resolve 方法
在 resolve 方法中返回限流的维度,如请求路径、ip地址、请求参数等
笔者这里限流维度是请求路径
- package com.wsjzzcbq.limit;
-
- import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
- import org.springframework.stereotype.Component;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- /**
- * ApiKeyResolver
- *
- * @author wsjz
- * @date 2023/09/17
- */
- @Component
- public class ApiKeyResolver implements KeyResolver {
- @Override
- public Mono
resolve(ServerWebExchange exchange) { - System.out.println("API限流: " + exchange.getRequest().getPath().value());
- return Mono.just(exchange.getRequest().getPath().value());
- }
- }
gateway-learn 工程 配置文件
- server:
- port: 9000
- spring:
- application:
- name: gateway-learn
-
- redis:
- host: 192.168.31.152
- password: 123456
- timeout: 5000
- database: 0
- cloud:
- nacos:
- discovery:
- server-addr: http://192.168.31.152:8848
- gateway:
- routes:
- - id: consumer-learn
- uri: lb://consumer-learn
- predicates:
- - Path=/cloudlearn/consumer/**
- filters:
- - name: RequestRateLimiter
- args:
- key-resolver: "#{@apiKeyResolver}"
- redis-rate-limiter.replenishRate: 1 #生成令牌速率:个/秒
- redis-rate-limiter.burstCapacity: 2 #令牌桶容量
- redis-rate-limiter.requestedTokens: 1 #每次消费的Token数量,默认是 1
- - StripPrefix=2
-
-
-
配置说明:
RequestRateLimiter 是 gateway 提供的限流过滤器
#{@apiKeyResolver} 是笔者实现的 ApiKeyResolver
redis-rate-limiter.replenishRate 生成令牌的速率每秒几个
redis-rate-limiter.burstCapacity 令牌桶容量
redis-rate-limiter.requestedTokens 每次消费的令牌数量
当请求到网关以 /cloudlearn/consumer/ 为开头前缀时,会路由到 consumer-learn 服务上
创建完成的项目结构

分别启动 consumer-learn 和 gateway-learn
登录 Nacos 控制台查看

启动成功后,可在Naocs 控制台查看注册服务信息
浏览器访问测试限流:http://localhost:9000/cloudlearn/consumer/name
运行效果

可以看到当1秒钟内请求超过2次时会被限流
上面代码实现了限流,但限流触发后返回的是429,不利于前端处理,这里我们可以在默认的限流过滤器基础上进行改进,自定义限流时的返回
新建 NewRequestRateLimiterGatewayFilterFactory 类
继承默认的 RequestRateLimiterGatewayFilterFactory
- package com.wsjzzcbq.filter;
-
- import com.alibaba.fastjson2.JSONObject;
- import org.springframework.cloud.gateway.filter.GatewayFilter;
- import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
- import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
- import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
- import org.springframework.cloud.gateway.route.Route;
- import org.springframework.cloud.gateway.support.HttpStatusHolder;
- import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
- import org.springframework.core.io.buffer.DataBuffer;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.stereotype.Component;
- import reactor.core.publisher.Mono;
-
- import java.nio.charset.StandardCharsets;
- import java.util.Iterator;
- import java.util.Map;
-
- /**
- * NewRequestRateLimiterGatewayFilterFactory
- *
- * @author wsjz
- * @date 2023/09/17
- */
- @Component
- public class NewRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {
-
- private final RateLimiter defaultRateLimiter;
-
- private final KeyResolver defaultKeyResolver;
-
- private boolean denyEmptyKey = true;
-
- private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
-
- public NewRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
- super(defaultRateLimiter, defaultKeyResolver);
- this.defaultRateLimiter = defaultRateLimiter;
- this.defaultKeyResolver = defaultKeyResolver;
- }
-
- @Override
- public GatewayFilter apply(Config config) {
- System.out.println("过滤限流");
- KeyResolver resolver = (KeyResolver)this.getOrDefault(config.getKeyResolver(), this.defaultKeyResolver);
- RateLimiter
- boolean denyEmpty = (Boolean)this.getOrDefault(config.getDenyEmptyKey(), this.denyEmptyKey);
- HttpStatusHolder emptyKeyStatus = HttpStatusHolder.parse((String)this.getOrDefault(config.getEmptyKeyStatus(), this.emptyKeyStatusCode));
- return (exchange, chain) -> {
- return resolver.resolve(exchange).defaultIfEmpty("____EMPTY_KEY__").flatMap((key) -> {
- if ("____EMPTY_KEY__".equals(key)) {
- if (denyEmpty) {
- ServerWebExchangeUtils.setResponseStatus(exchange, emptyKeyStatus);
- return exchange.getResponse().setComplete();
- } else {
- return chain.filter(exchange);
- }
- } else {
- String routeId = config.getRouteId();
- if (routeId == null) {
- Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
- routeId = route.getId();
- }
-
- return limiter.isAllowed(routeId, key).flatMap((response) -> {
- Iterator var4 = response.getHeaders().entrySet().iterator();
-
- while(var4.hasNext()) {
- Map.Entry
header = (Map.Entry)var4.next(); - exchange.getResponse().getHeaders().add((String)header.getKey(), (String)header.getValue());
- }
-
- if (response.isAllowed()) {
- return chain.filter(exchange);
- } else {
- ServerHttpResponse httpResponse = exchange.getResponse();
- httpResponse.getHeaders().set("Content-Type", "application/json");
- JSONObject json = new JSONObject();
- json.put("code", 0);
- json.put("msg", "当前请求人数较多,请稍后再访问");
- DataBuffer dataBuffer = httpResponse.bufferFactory().wrap(json.toJSONString().getBytes(StandardCharsets.UTF_8));
- return httpResponse.writeWith(Mono.just(dataBuffer));
- }
- });
- }
- });
- };
- }
-
- private
T getOrDefault(T configValue, T defaultValue) { - return configValue != null ? configValue : defaultValue;
- }
- }
修改配置文件
配置我们自定义的限流过滤器
- server:
- port: 9000
- spring:
- application:
- name: gateway-learn
-
- redis:
- host: 192.168.31.152
- password: 123456
- timeout: 5000
- database: 0
- cloud:
- nacos:
- discovery:
- server-addr: http://192.168.31.152:8848
- gateway:
- routes:
- - id: consumer-learn
- uri: lb://consumer-learn
- predicates:
- - Path=/cloudlearn/consumer/**
- filters:
- - name: NewRequestRateLimiter
- args:
- key-resolver: "#{@apiKeyResolver}"
- redis-rate-limiter.replenishRate: 1 #生成令牌速率:个/秒
- redis-rate-limiter.burstCapacity: 2 #令牌桶容量
- redis-rate-limiter.requestedTokens: 1 #每次消费的Token数量
- - StripPrefix=2
-
-
-
重新启动 gateway-learn
请求测试

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