/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.factory;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
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.HasRouteId;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpStatus;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
/**
* User Request Rate Limiter filter. See https://stripe.com/blog/rate-limiters and
* https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34.
*/
@ConfigurationProperties("spring.cloud.gateway.filter.request-rate-limiter")
public class RequestRateLimiterGatewayFilterFactory
extends AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {
/**
* Key-Resolver key.
*/
public static final String KEY_RESOLVER_KEY = "keyResolver";
private static final String EMPTY_KEY = "____EMPTY_KEY__";
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
/**
* Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
*/
private boolean denyEmptyKey = true;
/** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
public RequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
super(Config.class);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
public KeyResolver getDefaultKeyResolver() {
return defaultKeyResolver;
}
public RateLimiter getDefaultRateLimiter() {
return defaultRateLimiter;
}
public boolean isDenyEmptyKey() {
return denyEmptyKey;
}
public void setDenyEmptyKey(boolean denyEmptyKey) {
this.denyEmptyKey = denyEmptyKey;
}
public String getEmptyKeyStatusCode() {
return emptyKeyStatusCode;
}
public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
this.emptyKeyStatusCode = emptyKeyStatusCode;
}
@SuppressWarnings("unchecked")
@Override
public GatewayFilter apply(Config config) {
KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter);
boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
HttpStatusHolder emptyKeyStatus = HttpStatusHolder
.parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> {
if (EMPTY_KEY.equals(key)) {
if (denyEmpty) {
setResponseStatus(exchange, emptyKeyStatus);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
String routeId = config.getRouteId();
if (routeId == null) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
routeId = route.getId();
}
return limiter.isAllowed(routeId, key).flatMap(response -> {
for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
}
if (response.isAllowed()) {
return chain.filter(exchange);
}
setResponseStatus(exchange, config.getStatusCode());
return exchange.getResponse().setComplete();
});
});
}
private <T> T getOrDefault(T configValue, T defaultValue) {
return (configValue != null) ? configValue : defaultValue;
}
public static class Config implements HasRouteId {
private KeyResolver keyResolver;
private RateLimiter rateLimiter;
private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;
private Boolean denyEmptyKey;
private String emptyKeyStatus;
private String routeId;
public KeyResolver getKeyResolver() {
return keyResolver;
}
public Config setKeyResolver(KeyResolver keyResolver) {
this.keyResolver = keyResolver;
return this;
}
public RateLimiter getRateLimiter() {
return rateLimiter;
}
public Config setRateLimiter(RateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
return this;
}
public HttpStatus getStatusCode() {
return statusCode;
}
public Config setStatusCode(HttpStatus statusCode) {
this.statusCode = statusCode;
return this;
}
public Boolean getDenyEmptyKey() {
return denyEmptyKey;
}
public Config setDenyEmptyKey(Boolean denyEmptyKey) {
this.denyEmptyKey = denyEmptyKey;
return this;
}
public String getEmptyKeyStatus() {
return emptyKeyStatus;
}
public Config setEmptyKeyStatus(String emptyKeyStatus) {
this.emptyKeyStatus = emptyKeyStatus;
return this;
}
@Override
public void setRouteId(String routeId) {
this.routeId = routeId;
}
@Override
public String getRouteId() {
return this.routeId;
}
}
}
这个RequestRateLimiterGatewayFilterFactory没有name属性
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.factory;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.HasRouteId;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.reset;
/**
* @author Ryan Baxter
*/
public abstract class SpringCloudCircuitBreakerFilterFactory
extends AbstractGatewayFilterFactory<SpringCloudCircuitBreakerFilterFactory.Config> {
/** CircuitBreaker component name. */
public static final String NAME = "CircuitBreaker";
private ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory;
private ReactiveCircuitBreaker cb;
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public SpringCloudCircuitBreakerFilterFactory(ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory,
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
super(Config.class);
this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory;
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
}
private DispatcherHandler getDispatcherHandler() {
if (dispatcherHandler == null) {
dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
}
return dispatcherHandler;
}
@Override
public List<String> shortcutFieldOrder() {
return singletonList(NAME_KEY);
}
@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 -> {
if (statuses.contains(exchange.getResponse().getStatusCode())) {
HttpStatus status = exchange.getResponse().getStatusCode();
throw new CircuitBreakerStatusCodeException(status);
}
}), t -> {
if (config.getFallbackUri() == null) {
return Mono.error(t);
}
exchange.getResponse().setStatusCode(null);
reset(exchange);
// TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
// TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
.uri(config.getFallbackUri()).scheme(null).build(encoded).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
addExceptionDetails(t, exchange);
// Reset the exchange
reset(exchange);
ServerHttpRequest request = exchange.getRequest().mutate().uri(requestUrl).build();
return getDispatcherHandler().handle(exchange.mutate().request(request).build());
}).onErrorResume(t -> handleErrorWithoutFallback(t, config.isResumeWithoutError()));
}
@Override
public String toString() {
return filterToStringCreator(SpringCloudCircuitBreakerFilterFactory.this)
.append("name", config.getName()).append("fallback", config.fallbackUri).toString();
}
};
}
protected abstract Mono<Void> handleErrorWithoutFallback(Throwable t, boolean resumeWithoutError);
private void addExceptionDetails(Throwable t, ServerWebExchange exchange) {
ofNullable(t).ifPresent(
exception -> exchange.getAttributes().put(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR, exception));
}
@Override
public String name() {
return NAME;
}
public static class Config implements HasRouteId {
private String name;
private URI fallbackUri;
private String routeId;
private Set<String> statusCodes = new HashSet<>();
private boolean resumeWithoutError = false;
@Override
public void setRouteId(String routeId) {
this.routeId = routeId;
}
public String getRouteId() {
return routeId;
}
public URI getFallbackUri() {
return fallbackUri;
}
public Config setFallbackUri(URI fallbackUri) {
this.fallbackUri = fallbackUri;
return this;
}
public Config setFallbackUri(String fallbackUri) {
return setFallbackUri(URI.create(fallbackUri));
}
public String getName() {
return name;
}
public Config setName(String name) {
this.name = name;
return this;
}
public String getId() {
if (StringUtils.isEmpty(name) && !StringUtils.isEmpty(routeId)) {
return routeId;
}
return name;
}
public Set<String> getStatusCodes() {
return statusCodes;
}
public Config setStatusCodes(Set<String> statusCodes) {
this.statusCodes = statusCodes;
return this;
}
public Config addStatusCode(String statusCode) {
this.statusCodes.add(statusCode);
return this;
}
public boolean isResumeWithoutError() {
return resumeWithoutError;
}
public void setResumeWithoutError(boolean resumeWithoutError) {
this.resumeWithoutError = resumeWithoutError;
}
}
public class CircuitBreakerStatusCodeException extends HttpStatusCodeException {
public CircuitBreakerStatusCodeException(HttpStatus statusCode) {
super(statusCode);
}
}
}
这个SpringCloudCircuitBreakerFilterFactory类有name属性。
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.route;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.event.FilterArgsEvent;
import org.springframework.cloud.gateway.event.PredicateArgsEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.cloud.gateway.support.HasRouteId;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.web.server.ServerWebExchange;
/**
* {@link RouteLocator} that loads routes from a {@link RouteDefinitionLocator}.
*
* @author Spencer Gibb
*/
public class RouteDefinitionRouteLocator implements RouteLocator {
/**
* Default filters name.
*/
public static final String DEFAULT_FILTERS = "defaultFilters";
protected final Log logger = LogFactory.getLog(getClass());
private final RouteDefinitionLocator routeDefinitionLocator;
private final ConfigurationService configurationService;
private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
private final GatewayProperties gatewayProperties;
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;
}
private void initFactories(List<RoutePredicateFactory> predicates) {
predicates.forEach(factory -> {
String key = factory.name();
if (this.predicates.containsKey(key)) {
this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: "
+ this.predicates.get(key) + ". It will be overwritten.");
}
this.predicates.put(key, factory);
if (logger.isInfoEnabled()) {
logger.info("Loaded RoutePredicateFactory [" + key + "]");
}
});
}
@Override
public Flux<Route> getRoutes() {
Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute);
if (!gatewayProperties.isFailOnRouteDefinitionError()) {
// instead of letting error bubble up, continue
routes = routes.onErrorContinue((error, obj) -> {
if (logger.isWarnEnabled()) {
logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
+ " will be ignored. Definition has invalid configs, " + error.getMessage());
}
});
}
return routes.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
private Route convertToRoute(RouteDefinition routeDefinition) {
AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
return Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters).build();
}
@SuppressWarnings("unchecked")
List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
for (int i = 0; i < filterDefinitions.size(); i++) {
FilterDefinition definition = filterDefinitions.get(i);
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
if (factory == null) {
throw new IllegalArgumentException(
"Unable to find GatewayFilterFactory with name " + definition.getName());
}
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to "
+ definition.getName());
}
// @formatter:off
Object configuration = this.configurationService.with(factory)
.name(definition.getName())
.properties(definition.getArgs())
.eventFunction((bound, properties) -> new FilterArgsEvent(
// TODO: why explicit cast needed or java compile fails
RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
.bind();
// @formatter:on
// some filters require routeId
// TODO: is there a better place to apply this?
if (configuration instanceof HasRouteId) {
HasRouteId hasRouteId = (HasRouteId) configuration;
hasRouteId.setRouteId(id);
}
GatewayFilter gatewayFilter = factory.apply(configuration);
if (gatewayFilter instanceof Ordered) {
ordered.add(gatewayFilter);
}
else {
ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
}
}
return ordered;
}
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
List<GatewayFilter> filters = new ArrayList<>();
// TODO: support option to apply defaults after route specific filters?
if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(routeDefinition.getId(),
new ArrayList<>(this.gatewayProperties.getDefaultFilters())));
}
if (!routeDefinition.getFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(routeDefinition.getId(), new ArrayList<>(routeDefinition.getFilters())));
}
AnnotationAwareOrderComparator.sort(filters);
return filters;
}
private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
List<PredicateDefinition> predicates = routeDefinition.getPredicates();
if (predicates == null || predicates.isEmpty()) {
// this is a very rare case, but possible, just match all
return AsyncPredicate.from(exchange -> true);
}
AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));
for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {
AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);
predicate = predicate.and(found);
}
return predicate;
}
@SuppressWarnings("unchecked")
private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
if (factory == null) {
throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
}
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to "
+ predicate.getName());
}
// @formatter:off
Object config = this.configurationService.with(factory)
.name(predicate.getName())
.properties(predicate.getArgs())
.eventFunction((bound, properties) -> new PredicateArgsEvent(
RouteDefinitionRouteLocator.this, route.getId(), properties))
.bind();
// @formatter:on
return factory.applyAsync(config);
}
}
这个RouteDefinitionRouteLocator类的这个方法:loadGatewayFilters,就会抛异常:
throw new IllegalArgumentException( "Unable to find GatewayFilterFactory with name " + definition.getName());
List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) { ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size()); for (int i = 0; i < filterDefinitions.size(); i++) { FilterDefinition definition = filterDefinitions.get(i); GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName()); if (factory == null) { throw new IllegalArgumentException( "Unable to find GatewayFilterFactory with name " + definition.getName()); } if (logger.isDebugEnabled()) { logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to " + definition.getName()); } // @formatter:off Object configuration = this.configurationService.with(factory) .name(definition.getName()) .properties(definition.getArgs()) .eventFunction((bound, properties) -> new FilterArgsEvent( // TODO: why explicit cast needed or java compile fails RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties)) .bind(); // @formatter:on // some filters require routeId // TODO: is there a better place to apply this? if (configuration instanceof HasRouteId) { HasRouteId hasRouteId = (HasRouteId) configuration; hasRouteId.setRouteId(id); } GatewayFilter gatewayFilter = factory.apply(configuration); if (gatewayFilter instanceof Ordered) { ordered.add(gatewayFilter); } else { ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1)); } } return ordered; }
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;
}
这个RouteDefinitionRouteLocator初始化,无法把这个RequestRateLimiterGatewayFilterFactory,加到map中,就会出现空值。
这个配置应该没写错
- name: RequestRateLimiter args: # 每秒允许处理的请求数量# 令牌桶每秒填充平均速率 redis-rate-limiter.replenishRate: 1 # 每秒最大处理的请求数量# 令牌桶总容量 redis-rate-limiter.burstCapacity: 3 # 限流策略,对应策略的Bean key-resolver: "#{@ipKeyResolver}"
redis配置:
redis: # Redis数据库索引(默认为0) database: 0 # Redis服务器地址 host: 127.0.0.1 # Redis服务器连接端口 port: 6379 # Redis服务器连接密码(默认为空) password: 123456 client-type: lettuce lettuce: pool: # 连接池最大连接数(使用负值表示没有限制) max-active: 200 # 连接池中的最大空闲连接 max-idle: 20 # 连接池中的最小空闲连接 min-idle: 10 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms # 连接超时时间(毫秒)默认是2000ms timeout: 2000ms
maven依赖也加了,版本:<spring-cloud.version>2021.0.1</spring-cloud.version>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- <exclusions>--> <!-- <exclusion>--> <!-- <groupId>io.lettuce</groupId>--> <!-- <artifactId>lettuce-core</artifactId>--> <!-- </exclusion>--> <!-- </exclusions>--> </dependency>
出现这种异常,应该spring-cloud-2021.0.1的bug吧。限流控制类自己一个比较靠谱。
不知道有人碰到过没有。