文章目录
- 限流(RequestRateLimiter)
- 熔断器(CircuitBreaker)
- 路径操作(PrefixPath、StripPrefix、RewritePath、SetPath)
- [AddRequestHeade、AddRequestHeadersIfNotPresent 、SetRequestHeader](#AddRequestHeade、AddRequestHeadersIfNotPresent 、SetRequestHeader)
- AddResponseHeader、SetResponseHeader、RewriteResponseHeader
- RequestSize
- AddRequestParameter
限流(RequestRateLimiter)
核心配置
yaml
spring:
cloud:
gateway:
routes:
- id: normal-http
uri: http://localhost:8089
predicates:
- Path=/api/normal/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
RequestRateLimiter有2个重要的参数:
- RateLimiter:具体限流逻辑
- KeyResolver:key的解析器,就是根据什么限流,例如:ip、服务名、用户命、参数...
默认使用的RateLimiter是RedisRateLimiter,使用的KeyResolver是PrincipalNameKeyResolver
所以,需要添加依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
PrincipalNameKeyResolver获取的是Principal信息,通常我们是没有到,所以需要我们自定义KeyResolver。
因为没有其他的KeyResolver才会注册PrincipalNameKeyResolver,所以我们直接创建一个新的KeyResolver就可以。
自定义KeyResolver
通过ip来做限流:
java
@Bean
KeyResolver ipKeyResolver() {
return exchange -> {
InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
if (remoteAddress == null) {
return Mono.just("unknown");
}
InetAddress address = remoteAddress.getAddress();
return Mono.just(address.getHostAddress());
};
}
配置rateLimiter和keyResolver
rateLimiter和keyResolver都可以配置:
yaml
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: http://localhost:8089
filters:
- name: RequestRateLimiter
args:
rate-limiter: "#{@myRateLimiter}"
key-resolver: "#{@userKeyResolver}"
多个RateLimiter、KeyResolve注意冲突问题。
java
@Bean
@ConditionalOnBean({ RateLimiter.class, KeyResolver.class })
@ConditionalOnEnabledFilter
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter,
KeyResolver resolver) {
return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}
RedisRateLimiter限流实现说明与测试
RedisRateLimiter使用的是令牌桶算法,我们先来看一下RedisRateLimiter的参数:
- redis-rate-limiter.replenishRate: 每秒生成令牌数量
- redis-rate-limiter.burstCapacity: 令牌桶的大小
- redis-rate-limiter.requestedTokens: 每次请求消耗token数量
Spring Gateway的RedisRateLimiter使用的是lua脚本实现,有兴趣可以看看request_rate_limiter.lua脚本。

我们用Apache的ab来请求接口看看:
sh
ab -t 180 -c 10 http://127.0.0.1:8000/api/normal/ok
想了解ab的可以看看:Apache Bench(ab)常用命令与结果解读
Redis的信息:

请求结果:

很多时候,我们需要更灵活的限流控制,例如:每天、每周、每月、每年都请求量限制,怎么做呢?
只需要RateLimiter接口,把RequestRateLimiter过滤器的rate-limiter配置为自己实现的RateLimiter即可。
熔断器(CircuitBreaker)
CircuitBreakerGatewayFilterFactory去哪儿了
很多对Spring Gateway熟悉的朋友,肯定会直接找CircuitBreakerGatewayFilterFactory,发现找不到。
通过找GatewayFilterFactory的实现类,找到一个包含CircuitBreaker的SpringCloudCircuitBreakerResilience4JFilterFactory类。
没错,它就是我们的CircuitBreaker。
那为什么,它不一样呢?
我们在核心概念那一篇已经详细介绍了原理,知道GatewayFilterFactory是由RouteDefinitionRouteLocator处理。
java
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories,
GatewayProperties gatewayProperties, ConfigurationService configurationService) {
this.routeDefinitionLocator = routeDefinitionLocator;
this.configurationService = configurationService;
initFactories(predicates);
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
this.gatewayProperties = gatewayProperties;
}
我们可以看到是通过对name方法获取名字:
java
// 存放的是GatewayFilterFactory.name()
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
// RouteDefinitionRouteLocator.loadGatewayFilters
// 使用的时候是通过 FilterDefinition的name查找
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
通常都name方法实现使用的是normalizeFilterFactoryName,就是过滤器本身的名字去掉GatewayFilterFactory:
java
default String name() {
return NameUtils.normalizeFilterFactoryName(getClass());
}
public static String normalizeFilterFactoryName(Class<? extends GatewayFilterFactory> clazz) {
return removeGarbage(clazz.getSimpleName().replace(GatewayFilterFactory.class.getSimpleName(), ""));
}
而SpringCloudCircuitBreakerResilience4JFilterFactory的name方法继承它的父类SpringCloudCircuitBreakerFilterFactory,直接返回来CircuitBreaker。
java
public abstract class SpringCloudCircuitBreakerFilterFactory
extends AbstractGatewayFilterFactory<SpringCloudCircuitBreakerFilterFactory.Config> {
public static final String NAME = "CircuitBreaker";
@Override
public String name() {
return NAME;
}
}
为什么要这么做呢?
因为SpringCloudCircuitBreakerResilience4J实在太长了,在配置中写这么长的名字实在不方便。
SpringCloudCircuitBreakerResilience4J原理
其实就是把Filter调用链包装在ReactiveCircuitBreaker中执行。
java
@Override
public GatewayFilter apply(Config config) {
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId());
Set<HttpStatus> statuses = config.getStatusCodes().stream().map(HttpStatusHolder::parse)
.filter(statusHolder -> statusHolder.getHttpStatus() != null).map(HttpStatusHolder::getHttpStatus)
.collect(Collectors.toSet());
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return cb.run(chain.filter(exchange).doOnSuccess(v -> {
// 省略成功处理
}), t -> {
// 省略异常处理
return handle(getDispatcherHandler(), exchange.mutate().request(request).build());
}).onErrorResume(t -> handleErrorWithoutFallback(t, config.isResumeWithoutError()));
}
};
}
我们知道Resilience4j都有不同的实例,不同实例的配置不一样,那么获取的是那一个实例,使用的是什么配置呢?
如果配置了name就使用name,没有name就是要routeId,如果通过他们没有找到就使用默认值。
注意;默认的ReactiveResilience4JCircuitBreaker不仅包含熔断器(CircuitBreaker),还包含超时器(TimeLimiter)
熟悉自动配置的朋友都知道找AutoConfiguration类:
org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JAutoConfiguration
它会创建:ReactiveResilience4JCircuitBreakerFactory
至于它依赖的注册中心CircuitBreakerRegistry,则要费点功夫找,因为它在resilience4j.springboot包中:
io.github.resilience4j.springboot3.circuitbreaker.autoconfigure.AbstractCircuitBreakerConfigurationOnMissingBean
怎么找到的呢?
开启actuator,引入actuator依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置暴露beans
yaml
management:
endpoints:
web:
exposure:
include: gateway,beans,conditions
endpoint:
gateway:
enabled: true
通过类似http://localhost:8000/actuator/beans链接查看bean来源信息。

其他的就是Resilience4j的东西了,不熟悉Resilience4j的朋友可以看看这篇:resilience4j核心概念、配置参数与基本使用
那么问题来了,我们该怎么自定义配置呢?
自定义熔断配置与使用
不推荐直接创建ReactiveResilience4JCircuitBreakerFactory,我们只需要专注于我们需要的配置就可以。
也不需要直接创建CircuitBreaker,因为SpringCloudCircuitBreakerFilterFactory使用的是org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory#create(java.lang.String, java.lang.String)创建的ReactiveResilience4JCircuitBreaker,它使用的是配置类,而不是实例类。
java
@Override
public ReactiveCircuitBreaker create(String id, String groupName) {
Resilience4JConfigBuilder.Resilience4JCircuitBreakerConfiguration defaultConfig = getConfigurations()
.computeIfAbsent(id, defaultConfiguration);
CircuitBreakerConfig circuitBreakerConfig = this.circuitBreakerRegistry.getConfiguration(id)
.orElseGet(() -> this.circuitBreakerRegistry.getConfiguration(groupName)
.orElseGet(defaultConfig::getCircuitBreakerConfig));
TimeLimiterConfig timeLimiterConfig = this.timeLimiterRegistry.getConfiguration(id)
.orElseGet(() -> this.timeLimiterRegistry.getConfiguration(groupName)
.orElseGet(defaultConfig::getTimeLimiterConfig));
Resilience4JConfigBuilder.Resilience4JCircuitBreakerConfiguration config = new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(circuitBreakerConfig).timeLimiterConfig(timeLimiterConfig).build();
return new ReactiveResilience4JCircuitBreaker(id, groupName, config, circuitBreakerRegistry,
timeLimiterRegistry, Optional.ofNullable(circuitBreakerCustomizers.get(id)), isDisableTimeLimiter());
}
我们可以看到它包含熔断配置CircuitBreakerConfig和TimeLimiterConfig,没有就使用默认值。
接下来问题就简单了,我们需要配置熔断器,添加一个熔断器配置类就可以了。
java
@Bean
public CircuitBreakerConfig countBasedCircuitBreakerConfig(CircuitBreakerRegistry circuitBreakerRegistry) {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
.slidingWindowSize(10)
.minimumNumberOfCalls(20)
.failureRateThreshold(25f)
.slowCallRateThreshold(30f)
.slowCallDurationThreshold(Duration.ofMillis(500))
.waitDurationInOpenState(Duration.ofSeconds(10))
.maxWaitDurationInHalfOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(10)
.recordException(throwable ->
throwable instanceof RuntimeException &&
!(throwable instanceof IllegalArgumentException)
).build();
circuitBreakerRegistry.addConfiguration("custom", config);
return config;
}
注意我们需要把配置类添加到注册中心CircuitBreakerRegistry,不然找不到,添加到时候都key,我们配置中要使用,这里是custom。
配置文件中,我们可以如下配置:
yaml
spring:
cloud:
gateway:
routes:
- id: circuitbreaker_route
uri: http://localhost:8089
filters:
- CircuitBreaker=custom
CircuitBreaker=custom,前面是熔断器的名字CircuitBreaker
后面是配置的名字custom,这个名字可以随便填,没找到就会使用默认值,但是要使用我们自定义配置,就一定要和我们自定义的配置key对应上。
上面是简写方式,还有非简写方式:
yaml
spring:
cloud:
gateway:
routes:
- id: circuitbreaker_route
uri: lb://backing-service:8088
predicates:
- Path=/consumingServiceEndpoint
filters:
- name: CircuitBreaker
args:
name: custom
fallbackUri: forward:/inCaseOfFailureUseThis
我们在Spring Gateway核心概念、流程及原理中介绍了,简写方式是通过构造函数转换,非简写就能直接映射到name和args上。
java
public class FilterDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}
然后参数map args又会转换为对应的Config类:
java
public abstract class SpringCloudCircuitBreakerFilterFactory
extends AbstractGatewayFilterFactory<SpringCloudCircuitBreakerFilterFactory.Config> {
public static class Config implements HasRouteId {
private String name;
private URI fallbackUri;
private String routeId;
public String getId() {
if (!StringUtils.hasText(name) && StringUtils.hasText(routeId)) {
return routeId;
}
return name;
}
}
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId());
}
除了配置类,当然可以通过配置文件来配置:
yaml
resilience4j:
circuitbreaker:
configs:
xx:
registerHealthIndicator: true
failureRateThreshold: 25 # 失败率阈值
waitDurationInOpenState: 2000 # 断路器半开状态持续时间(ms)
permittedNumberOfCallsInHalfOpenState: 5 # 半开状态允许的调用次数
slidingWindowSize: 10
slidingWindowType: TIME_BASED
instances:
backendA:
baseConfig: xx
backendB:
failureRateThreshold: 33
注意:SpringCloudCircuitBreakerFilterFactory使用的不是实例instances,而是configs
所以,对应的是configs下的配置,如果想要覆盖默认配置,只需要在configs下添加一个default配置即可。
路径操作(PrefixPath、StripPrefix、RewritePath、SetPath)
- PrefixPath:添加路径前缀
- StripPrefix:删除路径前缀层数,例如到网关的请求是/user/query,我们转到user服务,StripPrefix=1就可以把user部分去掉
- SetPath:设置路径
- RewritePath:路径重新,支持Java表达式,类似Nginx的rewrite
yaml
spring:
cloud:
gateway:
routes:
- id: rewrite_path_route
uri: http://localhost:7777
predicates:
- Path=/test/**
filters:
- RewritePath=/test/?(?<segment>.*), /hello/$\{segment}
- id: set_path_route
uri: http://localhost:7777
predicates:
- Path=/test/{segment}
filters:
- SetPath=/hello/{segment}
- id: prefix_path_route
uri: http://localhost:7777
filters:
- PrefixPath=/mypath
- id: strip_prefix_route
uri: http://localhost:7777
predicates:
- Path=/level1/level2/act/**
filters:
- StripPrefix=2
在 y a m l 中没有特殊含义, 在yaml中没有特殊含义, 在yaml中没有特殊含义,{segment}在Java正则表达式中是命名分组,但是Spring会也会把它识别为占位符,yaml转义也使用,所以使用${segment}
RedirectTo
重定向是直接返回来3xx和Locaction,客户端自己做重定向。
yaml
spring:
cloud:
gateway:
routes:
- id: redirect_path_route
uri: http://localhost:8089
predicates:
- Path=/old/**
filters:
- RedirectTo=302, http://127.0.0.1:8088/feign/ok

AddRequestHeade、AddRequestHeadersIfNotPresent 、SetRequestHeader
- AddRequestHeader:没有添加,有则跳过
- SetRequestHeader:没有添加,有则替换
- AddRequestHeadersIfNotPresent:配置多个,逗号分割
yaml
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
predicates:
- Path=/red/{segment}
filters:
- AddRequestHeader=X-Request-Red, Blue-{segment}
- id: setrequestheader_route
uri: https://example.org
predicates:
- Host: {segment}.myhost.org
filters:
- SetRequestHeader=foo, bar-{segment}
- id: add_request_headers_route
uri: https://example.org
filters:
- AddRequestHeadersIfNotPresent=X-Request-Color-1:blue,X-Request-Color-2:green
都支持从path、host匹配变量
AddResponseHeader、SetResponseHeader、RewriteResponseHeader
- AddResponseHeader:没有添加,有则跳过
- SetResponseHeader:没有添加,有则替换
- RewriteResponseHeader:重写响应头,支持Java正则表达式,更加灵活
yaml
spring:
cloud:
gateway:
routes:
- id: rewriteresponseheader_route
uri: https://example.org
filters:
- RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=***
Retry
重试过滤器,非常实用,一些重要的业务可以添加上。
- retries:重试次数,默认3
- methods:什么方法会才重试,org.springframework.http.HttpMethod
- statuses:什么状态码才需要重试,org.springframework.http.HttpStatus
- series:什么状态码系列才重试,默认5xx,org.springframework.http.HttpStatus.Series
- exceptions:什么异常才重试,默认IOException、TimeoutException
- backoff:重试时间间隔算法,指数退避,就是TCP使用的那个,参数可配置,默认是没有使用这个
yaml
spring:
cloud:
gateway:
routes:
- id: retry_test
uri: http://localhost:8080/flakey
predicates:
- Host=*.retry.com
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
RequestSize
RequestSize用于控制请求大小,类型是DataSize,所以DataUnit都可以使用KB、MB、GB...
不满足条件返回413错误(Payload Too Large)
yaml
spring:
cloud:
gateway:
routes:
- id: request_size_route
uri: http://localhost:8080
predicates:
- Path=/ai/parse
filters:
- name: RequestSize
args:
maxSize: 30MB
AddRequestParameter
简单实用,但是不够灵活,因为我们参数往往都是根据服务的动态添加到,它的很大的作用是为我们实现自己添加参数的Filter的参考模板。
yaml
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
predicates:
- Host: {segment}.myhost.org
filters:
- AddRequestParameter=foo, bar-{segment}