1. 程式人生 > 其它 >Gateway 閘道器限流

Gateway 閘道器限流

系統進行高併發處理時 ,往往需要進行限流處理,防止因流量過大導致服務不可用,也可防止網路攻擊。

常見的限流演算法:

1.計數器演算法:

一般我們會限制一秒鐘的能夠通過的請求數,比如限流qps為100,演算法的實現思路就是從第一個請求進來開始計時,在接下去的1s內,每來一個請求,就把計數加1,如果累加的數字達到了100,那麼後續的請求就會被全部拒絕。等到1s結束後,把計數恢復成0,重新開始計數。存在弊端:如果我在單位時間1s內的前10ms,已經通過了100個請求,那後面的990ms 請求全部拒絕掉。

2.漏桶演算法:

類似漏斗,當請求進來時,相當於水倒入漏斗,然後從下端小口慢慢勻速的流出。不管上面流量多大,下面流出的速度始終保持不變。弊端:無法應對突發流量衝擊。

3.令牌桶演算法:

主要有生產令牌和消費令牌組成。

生產令牌:固定容量的令牌桶,按固定的速率(N/s)往桶中放入令牌,桶滿時不再放入;
消費令牌:每個請求需要從桶中拿取令牌,當消費速率低於生產速率時,直至桶中令牌滿而觸發限流,此時請求可以放入緩衝佇列或直接拒絕。
令牌桶演算法有一個很關鍵的問題,就是桶容量的設定,這個引數可以讓令牌桶演算法具備處理突發流量的能力。假如將桶容量設定為 100,生成令牌的速度為每秒 10 個,那麼在系統空閒一段時間之後(桶中令牌一直沒有消費,慢慢的會被裝滿),突然來了 50 個請求,這時系統可以直接按每秒 50 個的速度處理,隨著桶中的令牌很快用完,處理速度又會慢慢降下來,和生成令牌速度趨於一致。這是令牌桶演算法和漏桶演算法最大的區別,漏桶演算法無論來了多少請求,只會按固定速度進行處理。

令牌桶具體程式碼實現方式:

1.引入jar包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

2.yml 配置:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
  cloud:
    gateway:
      discovery:
        locator:
          enabled: 
false lowerCaseServiceId: true routes: - id: store-goods-service predicates: - Path=/store-goods-service/** uri: lb://STORE-GOODS-SERVICE filters: - StripPrefix=1 - name: RedisRequestRateLimiter args: key-resolver: '#{@pathKeyResolver}' # 令牌桶每秒填充平均速率 redis-rate-limiter.replenishRate: 1 # 令牌桶的總容量 redis-rate-limiter.burstCapacity: 3

3.配置 key-resolver 的bean 物件  和yaml 中定義的名稱相同

@Configuration
public class KeyResolverConfiguration {

    /**
     * 基於請求路徑的限流
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getPath().toString()
        );
    }
}

這樣基本就完成限流了,通過瀏覽器模擬訪問發下 ,返回狀態碼429 

 為了更加有好的提示錯誤資訊 ,可以自定義類繼承RequestRateLimiterGatewayFilterFactory,自定義錯誤資訊:

@Slf4j
@Component
public class RedisRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {

    private final RateLimiter defaultRateLimiter;

    private final KeyResolver defaultKeyResolver;

    public RedisRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
        super(defaultRateLimiter, defaultKeyResolver);
        this.defaultRateLimiter = defaultRateLimiter;
        this.defaultKeyResolver = defaultKeyResolver;
    }

    @Override
    public GatewayFilter apply(Config config) {
        KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);
        RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);
        return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {
            String routeId = config.getRouteId();
            if (routeId == null) {
                Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                routeId = route.getId();
            }

            String finalRouteId = routeId;
            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);
                }

                log.info("已限流: {}", finalRouteId);
                ServerHttpResponse httpResponse = exchange.getResponse();
                httpResponse.setStatusCode(config.getStatusCode());
                if (!httpResponse.getHeaders().containsKey("Content-Type")) {
                    httpResponse.getHeaders().add("Content-Type", "application/json");
                }
                JSONObject json = new JSONObject();
                json.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
                json.put("message","當前人數較多,請點選重新整理試試");
                json.put("serverTimeMillis",System.currentTimeMillis());
                DataBuffer buffer = httpResponse.bufferFactory().wrap(json.toJSONString().getBytes(StandardCharsets.UTF_8));
                return httpResponse.writeWith(Mono.just(buffer));
            });
        });
    }

    private <T> T getOrDefault(T configValue, T defaultValue) {
        return (configValue != null) ? configValue : defaultValue;
    }
}