Spring Cloud Gateway 제한 작업 에 대한 자세 한 설명

높 은 병발 시스템 을 개발 할 때 세 개의 이기 가 시스템 을 보호 하 는 데 사용 된다.캐 시,강등 과 제한 이다.
API 게 이 트 웨 이 는 모든 요청 의 입구 로 서 요 구 량 이 많 습 니 다.저 희 는 동시 방문 요청 에 대해 속 도 를 제한 하여 시스템 의 가용성 을 보호 할 수 있 습 니 다.
자주 사용 하 는 제한 알고리즘,예 를 들 어 토 큰 통 알고리즘,누 출 통 알고리즘,카운터 알고리즘 등 이 있 습 니 다.
Zuul 에서 우 리 는 스스로 흐름 제한 기능 을 실현 할 수 있다.  스프링 클 라 우 드 게 이 트 웨 이의 등장 자체 가 줄 을 대체 하 는 데 쓰 인 다.
그것 을 대체 하려 면 반드시 강력 한 기능 이 있어 야 한다.성능 상의 장점 을 제외 하고 Spring Cloud Gateway 는 많은 새로운 기능 을 제공 했다.예 를 들 어 오늘 우리 가 말 하고 자 하 는 제한 작업 은 사용 하기에 매우 간단 하 다.오늘 우 리 는 Spring Cloud Gateway 에서 어떻게 제한 작업 을 하 는 지 배 울 것 이다.
현재 제한 흐름 은 Redis 기반 의 실현 을 제공 합 니 다.우 리 는 대응 하 는 의존 도 를 증가 해 야 합 니 다.

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
KeyResolver 를 통 해 흐름 을 제한 하 는 Key 를 지정 할 수 있 습 니 다.예 를 들 어 저 희 는 사용자 에 따라 흐름 을 제한 하고 IP 에 따라 흐름 을 제한 해 야 합 니 다.
IP 제한

@Bean
public KeyResolver ipKeyResolver() {
 return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
exchange 대상 을 통 해 요청 정 보 를 얻 을 수 있 습 니 다.여 기 는 HostName 을 사 용 했 습 니 다.사용자 에 따라 스 트림 을 제한 하려 면 현재 요청 한 사용자 ID 나 사용자 이름 을 가 져 오 면 됩 니 다.예 를 들 어:
사용자 제한 흐름
이러한 방식 으로 흐름 을 제한 합 니 다.요청 경로 에 userId 인 자 를 휴대 해 야 합 니 다.

@Bean
KeyResolver userKeyResolver() {
 return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
인터페이스 제한 흐름
요청 주소 의 uri 를 스 트림 키 로 가 져 옵 니 다.

@Bean
KeyResolver apiKeyResolver() {
 return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
그리고 흐름 제한 필터 정 보 를 설정 합 니 다:

server:
 port: 8084
spring:
 redis:
 host: 127.0.0.1
 port: 6379
 cloud:
 gateway:
  routes:
  - id: fsh-house
  uri: lb://fsh-house
  predicates:
  - Path=/house/**
  filters:
  - name: RequestRateLimiter
   args:
   redis-rate-limiter.replenishRate: 10
   redis-rate-limiter.burstCapacity: 20
   key-resolver: "#{@ipKeyResolver}"
  • filter 이름 은 RequestRateLimiter
  • 여야 합 니 다.
  • redis-rate-limiter.replenish Rate:사용자 가 1 초 에 몇 개의 요청 을 처리 할 수 있 도록 합 니 다
  • redis-rate-limiter.burstCapacity:토 큰 통 의 용량,1 초 안에 완료 할 수 있 는 최대 요청 수
  • key-resolver:SpEL 을 사용 하여 이름 에 따라 bean
  • 을 참조 합 니 다.
    인터페이스 에 접근 하여 테스트 할 수 있 습 니 다.이 때 Redis 에 해당 하 는 데이터 가 있 습 니 다.
    127.0.0.1:6379> keys *
    1) "request_rate_limiter.{localhost}.timestamp"
    2) "request_rate_limiter.{localhost}.tokens"
    대괄호 안에 우리 의 흐름 제한 키 가 있 습 니 다.여 기 는 IP 이 고 로 컬 은 localhost 입 니 다.
  • timestamp:현재 시간의 초 수 를 저장 합 니 다.즉,System.currentTimeMillis()/1000 또는 Instant.now().getEpochSecond()
  • tokens:현재 이 초 에 대응 하 는 사용 가능 한 토 큰 수량
  • 을 저장 합 니 다.
    Spring Cloud Gateway 가 현재 제공 하 는 제한 흐름 은 상대 적 으로 간단 하 다.실제 에서 우리 의 제한 흐름 전략 은 여러 가지 상황 이 있 을 것 이다.예 를 들 어:
  • 인터페이스 마다 흐름 제한 수량 이 다 르 기 때문에 설정 센터 의 동적 조정
  • 을 통 해
  • 초과 한 데이터 가 거부 되면 호출 자 에 게 고정된 형식 으로 돌아 갈 수 있 습 니 다
  • 특정한 서 비 스 를 전체적으로 제한 합 니 다.(이것 은 Spring Cloud Gateway 로 어떻게 실현 하 는 지 생각해 보 세 요.사실은 간단 합 니 다)
  • ……
  • 물론 우 리 는 다시 Redis Rate Limiter 를 통 해 자신의 흐름 제한 전략 을 실현 할 수 있 습 니 다.이것 은 우리 가 나중에 다시 소개 하 겠 습 니 다.
    스 트림 소스 코드
    
    // routeId      fsh-house,id     key,   localhost。
    public Mono<Response> isAllowed(String routeId, String id) {
     //    RedisRateLimiter      
     if (!this.initialized.get()) {
      throw new IllegalStateException("RedisRateLimiter is not initialized");
     }
     //   routeId       
     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 {
      //   key   (request_rate_limiter.{localhost}.timestamp,request_rate_limiter.{localhost}.tokens)
      List<String> keys = getKeys(id);
    
    
      // The arguments to the LUA script. time() returns unixtime in seconds.
      List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
        Instant.now().getEpochSecond() + "", "1");
      // allowed, tokens_left = redis.eval(SCRIPT, keys, args)
      //   LUA  
      Flux<List<Long>> 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<Long>(), (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) {
      log.error("Error determining if user allowed from redis", e);
     }
     return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
    }
    
    
    LUA 스 크 립 트:
    
    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])
    local requested = tonumber(ARGV[4])
    
    local fill_time = capacity/rate
    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))
    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 }
    
    
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기