SpringCloud gateway 제한 소스 코드 분석
9996 단어 자바
Spring Cloud Gateway 제한 흐름 은 내 장 된 RequestRate Limiter GateWay FilterFactory 공장 을 통 해 이 루어 진다.
물론 공식 적 으로 는 우리 의 일부 업무 수 요 를 만족 시 킬 수 없 기 때문에 스 트림 필 터 를 사용자 정의 할 수 있 습 니 다.
\ # \ # yml 은 다음 과 같이 설정 하면 이 경로 에 차단 기 를 추가 할 수 있 습 니 다.
spring:
cloud:
gateway:
routes:
- id: test_route
uri: localhost
predicates:
- Path=/host/address
filters:
- name: RequestRateLimiter
args:
## , 。 。
redis-rate-limiter.replenishRate: 1
## 。 。 。
redis-rate-limiter.burstCapacity: 3
## KeyResolver key
key-resolver: "#{@hostAddrKeyResolver}"
\ # \ # RequestRateLimiterGateWayFilterFactory 코드:
//AbstractGatewayFilterFactory GatewayFilterFactory ,
//AbstractGatewayFilterFactory apply
public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory {
public static final String KEY_RESOLVER_KEY = "keyResolver";
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
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;
}
@SuppressWarnings("unchecked")
@Override
public GatewayFilter apply(Config config) {
//yml hostAddrKeyResolver
KeyResolver resolver = (config.keyResolver == null) ? defaultKeyResolver : config.keyResolver;
// , RedisRateLimiter
RateLimiter
분석:
1. KeyResolver 를 불 러 오고 설정 파일 에서 불 러 옵 니 다. hostAddrKeyResolver 를 설 정 했 습 니 다. 즉, host 주소 에 따라 흐름 을 제한 합 니 다.비어 있 으 면 기본 PrincipalNameKeyResolver 사용 하기
2. RateLimiter 를 불 러 옵 니 다. 기본적으로 RedisRateLimiter 를 사용 합 니 다.
3. Redis Rate Limiter 의 isAllowed 방법 을 실행 하여 response 를 얻 습 니 다. isAllowed 가 true 이면 차단 을 통 해 429 (isAllowed 방법 은 다음 설명 을 구체 적 으로 실현 합 니 다) 로 돌아 갑 니 다.
## HostAddrKeyResolver:
@Slf4j
public class HostAddrKeyResolver implements KeyResolver {
@Override
public Mono resolve(ServerWebExchange exchange) {
log.info("HostAddrKeyResolver ");
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
시작 클래스 에 bean 주입
@Bean
public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}
## RedisRateLimiter:
@Override
@SuppressWarnings("unchecked")
public Mono isAllowed(String routeId, String id) {
//
if (!this.initialized.get()) {
throw new IllegalStateException("RedisRateLimiter is not initialized");
}
//
Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);
if (routeConfig == null) {
throw new IllegalArgumentException("No Configuration found for route " + routeId);
}
//
int replenishRate = routeConfig.getReplenishRate();
//
int burstCapacity = routeConfig.getBurstCapacity();
try {
// redis key, lua
List keys = getKeys(id);
// , lua
List scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
Instant.now().getEpochSecond() + "", "1");
Flux> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
// .log("redisratelimiter", Level.FINER);
return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
.reduce(new ArrayList(), (longs, l) -> {
longs.addAll(l);
return longs;
}) .map(results -> {
boolean allowed = results.get(0) == 1L;
Long tokensLeft = results.get(1);
Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
if (log.isDebugEnabled()) {
log.debug("response: " + response);
}
return response;
});
}
catch (Exception e) {
/*
* We don't want a hard dependency on Redis to allow traffic. Make sure to set
* an alert so you know if this is happening too much. Stripe's observed
* failure rate is 0.01%.
*/
log.error("Error determining if user allowed from redis", e);
}
return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
}
@NotNull
public HashMap getHeaders(Config config, Long tokensLeft) {
HashMap headers = new HashMap<>();
headers.put(this.remainingHeader, tokensLeft.toString());
headers.put(this.replenishRateHeader, String.valueOf(config.getReplenishRate()));
headers.put(this.burstCapacityHeader, String.valueOf(config.getBurstCapacity()));
return headers;
}
static List getKeys(String id) {
// use `{}` around keys to use Redis Key hash tags
// this allows for using redis cluster
// Make a unique key per user.
String prefix = "request_rate_limiter.{" + id;
//
String tokenKey = prefix + "}.tokens";
//
String timestampKey = prefix + "}.timestamp";
return Arrays.asList(tokenKey, timestampKey);
}
분석:
1. 초기 화 여 부 를 판단 하고 설정 을 불 러 오 며 토 큰 충전 속도 와 토 큰 통 크기 를 가 져 옵 니 다.
2. 경로 id 에 따라 두 개의 redis 의 key 값 으로 조합 하여 lua 스 크 립 트 에 전송 합 니 다.
request_rate_limiter.{id}.tokens 영패 통 남 은 영패 수
request_rate_limiter.{id}.timestamp 토 큰 통 마지막 토 큰 시간 채 우기
3. 토 큰 충전 속도, 토 큰 통 크기, 현재 시간 (단위: 초), 토 큰 소모 (기본 값 1) 조합 을 lua 스 크 립 트 에 전송
4. lua 스 크 립 트 실행
5.flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L))) 이것 은 lua 스 크 립 트 를 실행 하 는 과정 에서 이상 한 처리 입 니 다. 이상 을 무시 하고 토 큰 을 되 돌려 줍 니 다.이렇게 하면 redis 와 결합 을 풀 수 있 고 강하 게 의존 하지 않 을 수 있 습 니 다.
이 실현 핵심 은 주로 lua 스 크 립 트 에 나타 나 는데 영패 통 알고리즘 을 사용 합 니 다.
spring - cloud - gateway - core 의 request 참조rate_limiter.lua
## redis key
local tokens_key = KEYS[1]
##
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)
##
local rate = tonumber(ARGV[1])
##
local capacity = tonumber(ARGV[2])
##
local now = tonumber(ARGV[3])
## , 1
local requested = tonumber(ARGV[4])
##
local fill_time = capacity/rate
## key
local ttl = math.floor(fill_time*2)
--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)
##
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)
##
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)
local delta = math.max(0, now-last_refreshed)
##
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
## allowed true
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
new_tokens = filled_tokens - requested
allowed_num = 1
end
--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed_num, new_tokens }
문제 가 있 으 면 지적 을 환영 합 니 다.
저자: 커 다란 열매 링크:https://blog.csdn.net/shuoshuo132 출처: CSDN 저작권 은 작가 의 소유 이 며, 어떠한 형식의 전재 도 출처 를 밝 혀 주 십시오.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.