限流器過濾器

RateLimiter 過濾器使用 Bucket4j 來確定當前請求是否允許繼續處理。如果請求不被允許,(預設情況下)將返回狀態碼 HTTP 429 - Too Many Requests

在閱讀本文件之前,請先回顧 Bucket4j 概念

Bucket4j 使用的演算法是 令牌桶演算法

該過濾器接受一個 keyResolver 引數和其他 Bucket4j 配置引數。鍵解析器是一個 java.util.Function<ServerRequest, String>。這允許使用者從請求中提取任何資訊,以便在配置的 Bucket4j 分散式 機制中用作鍵。一個常見的鍵可以是根據 ServerRequest 獲取的 Principal

預設情況下,如果鍵解析器找不到鍵,請求將被拒絕並返回 FORBIDDEN 狀態碼。

目前,配置鍵解析器唯一的方法是透過 Java DSL,而不是透過外部屬性。

Bucket4j 分散式配置

一個型別為 io.github.bucket4j.distributed.proxy.AsyncProxyManager 的 bean。為此,請使用 ProxyManager.asAsync() 方法。

RateLimiterConfiguration.java
import com.github.benmanes.caffeine.cache.Caffeine;
import io.github.bucket4j.caffeine.CaffeineProxyManager;

@Configuration
class RateLimiterConfiguration {

	@Bean
	public AsyncProxyManager<String> caffeineProxyManager() {
		Caffeine<String, RemoteBucketState> builder = (Caffeine) Caffeine.newBuilder().maximumSize(100);
		return new CaffeineProxyManager<>(builder, Duration.ofMinutes(1)).asAsync();
	}
}

上述配置使用 Caffeine(一個本地記憶體快取,適用於測試)來設定一個 AsyncProxyManager

配置令牌桶

預設情況下,令牌桶使用配置的 capacityperiod 進行配置。Capacity 是指令牌桶擁有的令牌數量。Period 是一個 java.util.Duration,它定義了令牌桶中可用令牌再生所需的時間。

其他配置項包括請求被拒絕時返回的 statusCode。預設值為 429,即 TOO_MANY_REQUESTS。tokens 項定義了每個請求使用的令牌數量,預設為 1。headerName 項是包含剩餘令牌數量的響應頭的名稱,預設值為 X-RateLimit-Remainingtimeout 選項定義了分散式令牌桶返回結果的 Duration,預設未設定。

以下是配置帶有限流功能的路由的示例

RouteConfiguration.java
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
import static org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.rateLimit;
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;

@Configuration
class RouteConfiguration {

    @Bean
    public RouterFunction<ServerResponse> gatewayRouterFunctionsRateLimited() {
        return route("rate_limited_route")
            .GET("/api/**", http())
            .before(uri("https://example.org"))
            .filter(rateLimit(c -> c.setCapacity(100)
                    .setPeriod(Duration.ofMinutes(1))
                    .setKeyResolver(request -> request.servletRequest().getUserPrincipal().getName())))
            .build();
    }
}

這配置了每分鐘 100 個令牌的令牌桶容量來進行限流。鍵解析器從 Servlet 請求中獲取主體名稱。