Spring Cloud Gateway 閘道器限流

語言: CN / TW / HK

一、背景

在我們平時開發過程中,一般一個請求都是需要經過多個微服務的,**比如:**請求從A服務流過B服務,如果A服務請求過快,導致B服務響應慢,那麼必然會導致系統出現問題。因為,我們就需要有限流操作。

二、實現功能

  1. 提供自定義的限流key生成,需要實現KeyResolver介面。

  2. 提供預設的限流演算法,實現實現RateLimiter介面。

  3. 當限流的key為空時,直接不限流,放行,由引數spring.cloud.gateway.routes[x].filters[x].args[x].deny-empty-key 來控制

  4. 限流時返回客戶端的相應碼有 spring.cloud.gateway.routes[x].filters[x].args[x].status-code 來控制,需要寫這個 org.springframework.http.HttpStatus類的列舉值。

  5. RequestRateLimiter 只能使用name || args這種方式來配置,不能使用簡寫的方式來配置。 錯誤的配置方式

  6. RequestRateLimiter過濾器的redis-rate-limiter引數是在RedisRateLimiterCONFIGURATION_PROPERTY_NAME屬性配置的。構造方法中用到了。

三、閘道器層限流

限流的key 生成規則,預設是 PrincipalNameKeyResolver來實現 限流演算法,預設是 RedisRateLimiter來實現,是令牌桶演算法。

1、使用預設的redis來限流

Spring Cloud Gateway中預設提供了 RequestRateLimiter 過濾器來實現限流操作。

1、引入jar包

<dependencies>
    <dependency>
        <groupid>com.alibaba.cloud</groupid>
        <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
    </dependency>
    <dependency>
        <groupid>org.springframework.cloud</groupid>
        <artifactid>spring-cloud-starter-gateway</artifactid>
    </dependency>
    <dependency>
        <groupid>org.springframework.cloud</groupid>
        <artifactid>spring-cloud-starter-loadbalancer</artifactid>
    </dependency>
    <dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-data-redis-reactive</artifactid>
    </dependency>
    <dependency>
        <groupid>org.projectlombok</groupid>
        <artifactid>lombok</artifactid>
    </dependency>
</dependencies>

2、編寫配置檔案

spring:
  application:
    name: gateway-9205
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8847
    gateway:
      routes:
        - id: user-provider-9206
          uri: lb://user-provider-9206
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user(?<segment>/?.*), $\{segment}
            - name: RequestRateLimiter
              args:
                # 如果返回的key是空的話,則不進行限流
                deny-empty-key: false
                # 每秒產生多少個令牌
                redis-rate-limiter.replenishRate: 1
                # 1秒內最大的令牌,即在1s內可以允許的突發流程,設定為0,表示阻止所有的請求
                redis-rate-limiter.burstCapacity: 1
                # 每次請求申請幾個令牌
                redis-rate-limiter.requestedTokens: 1
  redis:
    host: 192.168.7.1
    database: 12
    port: 6379
    password: 123456

server:
  port: 9205
debug: true

配置的定義

3、閘道器正常響應

閘道器正常響應

4、閘道器限流響應

閘道器限流響應

2、自定義限流演算法和限流key

1、自定義限流key

編寫一個類實現 KeyResolver 介面即可。

package com.huan.study.gateway;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Optional;

/**
 * 限流的key獲取
 *
 * @author huan.fu 2021/9/7 - 上午10:25
 */
@Slf4j
@Component
public class DefaultGatewayKeyResolver implements KeyResolver {

    @Override
    public Mono<string> resolve(ServerWebExchange exchange) {
        // 獲取當前路由
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();
        log.info("當前返回的uri:[{}]", uri);

        return Mono.just(Optional.ofNullable(route).map(Route::getId).orElse("") + "/" + uri);
    }
}

配置檔案中的寫法(部分)

spring:
  cloud:
    gateway:
      routes:
        - id: user-provider-9206
          filters:
            - name: RequestRateLimiter
              args:
                # 返回限流的key
                key-resolver: "#{@defaultGatewayKeyResolver}"
              

2、自定義限流演算法

編寫一個類實現 RateLimiter ,此處使用記憶體限流

package com.huan.study.gateway;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * @author huan.fu 2021/9/7 - 上午10:36
 */
@Component
@Slf4j
@Primary
public class DefaultGatewayRateLimiter extends AbstractRateLimiter<defaultgatewayratelimiter.config> {

    /**
     * 和配置檔案中的配置屬性相對應
     */
    private static final String CONFIGURATION_PROPERTY_NAME = "default-gateway-rate-limiter";

    private RateLimiter rateLimiter = RateLimiter.create(1);

    protected DefaultGatewayRateLimiter(ConfigurationService configurationService) {
        super(DefaultGatewayRateLimiter.Config.class, CONFIGURATION_PROPERTY_NAME, configurationService);
    }

    @Override
    public Mono<response> isAllowed(String routeId, String id) {
        log.info("閘道器預設的限流 routeId:[{}],id:[{}]", routeId, id);

        Config config = getConfig().get(routeId);

        return Mono.fromSupplier(() -&gt; {
            boolean acquire = rateLimiter.tryAcquire(config.requestedTokens);
            if (acquire) {
                return new Response(true, Maps.newHashMap());
            } else {
                return new Response(false, Maps.newHashMap());
            }
        });
    }

    @Getter
    @Setter
    @ToString
    public static class Config {
        /**
         * 每次請求多少個 token
         */
        private Integer requestedTokens;
    }
}

配置檔案中的寫法(部分)

spring:
  cloud:
    gateway:
      routes:
        - id: user-provider-9206
          filters:
            - name: RequestRateLimiter
              args:
                # 自定義限流規則
                rate-limiter: "#{@defaultGatewayRateLimiter}"

注意⚠️: 這個類需要加上 @Primary 註解。

3、配置檔案中的寫法

spring:
  application:
    name: gateway-9205
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8847
    gateway:
      routes:
        - id: user-provider-9206
          uri: lb://user-provider-9206
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user(?<segment>/?.*), $\{segment}
            - name: RequestRateLimiter
              args:
                # 自定義限流規則
                rate-limiter: "#{@defaultGatewayRateLimiter}"
                # 返回限流的key
                key-resolver: "#{@defaultGatewayKeyResolver}"
                # 如果返回的key是空的話,則不進行限流
                deny-empty-key: false
                # 限流後向客戶端返回的響應碼429,請求太多
                status-code: TOO_MANY_REQUESTS
                # 每次請求申請幾個令牌  default-gateway-rate-limiter 的值是在 defaultGatewayRateLimiter 中定義的。
                default-gateway-rate-limiter.requestedTokens: 1
server:
  port: 9205
debug: true

四、完成程式碼

https://gitee.com/huan1993/spring-cloud-alibaba-parent/tree/master/gateway-redis-limiter

五、參考文件

1、https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-redis-ratelimiter