API 速率限制與 Spring Cloud Gateway

工程 | Haytham Mohamed | 2021 年 4 月 5 日 | ...

保護 API 和服務端點免受有害影響,例如阻斷服務、級聯故障或資源過度使用,是迫切需要的架構考量之一。速率限制是一種控制 API 或服務消耗速率的技術。在分散式系統中,沒有比集中配置和管理消費者與 API 互動速率更好的選擇。只有在定義速率範圍內的請求才能到達 API。任何超出此範圍的請求都會引發 HTTP “請求過多” 錯誤。

link to rate limit image

Spring Cloud Gateway (SCG) 是一個簡單輕量的組件,但它是一種管理 API 消耗速率限制的有效方法。在本部落格中,我將說明如何透過配置方法輕鬆實現這一點。如下圖所示,此示範包含前端和後端服務,中間有一個 Spring Cloud Gateway 服務。

link to rate limit image

架構中無需包含任何程式碼即可加入 SCG。您只需要在一個基礎的 Spring Boot 應用程式中加入 Spring Boot Cloud 相依性 org.springframework.cloud:spring-cloud-starter-gateway,並設定適當的配置設定即可開始使用。

SCG 從前端服務接收的請求可以根據配置的路由定義路由到後端服務。路由定義配置指定閘道如何將請求路由到後端端點。路由配置通常根據可以從 HTTP 請求中提取的資訊(例如路徑和標頭)定義條件。

例如,下面的程式碼片段列出了一個 YAML 段落,用於配置請求應路由到後端服務的條件。它顯示當閘道被 “/backend” 路徑命中時,請求應以後端服務為目標。在配置中,路由被賦予一個識別符號和後端服務 URI。

spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: https://127.0.0.1:8081
          predicates:
            - Path=/backend

RequestRateLimiter 是 SCG 提供的眾多閘道篩選器之一。此實作決定是否允許請求繼續進行,或是否已超出其限制。此實作讓您可以(選擇性地)插入一個金鑰,以管理對不同服務的請求數量限制。雖然可以自訂實作如何解析金鑰,但閘道提供了一個使用使用者 Principal 名稱的金鑰。需要一個安全的閘道來解析使用者的 principal 名稱,但您可以選擇實作 KeyResolver 介面,以從 ServerWebExchange 解析不同的金鑰。您可以使用 SPEL #{@customKeyResolver} 表達式在配置中指向自訂的 KeyResolver bean(例如,名為 customKeyResolver)。以下清單顯示了 KeyResolver 介面

public interface KeyResolver {
    Mono<String> resolve(ServerWebExchange exchange);
}

如果沒有解析出金鑰,閘道將拒絕請求。若要讓閘道接受遺失的已解析金鑰,您可以設定以下屬性

spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key=false

您還可以指定當閘道無法判斷金鑰時應報告的狀態碼,透過設定以下屬性

spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code=

考慮一個藍圖架構,其中閘道透過使用 Redis 控制 API 消耗限制。提供的 Redis 實作使用 Token Bucket 演算法。若要使用它,您需要在閘道應用程式中包含 spring-boot-starter-data-redis Spring Boot starter 相依性。基本上,token bucket 演算法使用平衡 token 作為一種手段,來維護累計的使用預算。此演算法假設 token 將以一定速率添加到 bucket 中,而對 API 的呼叫會消耗這些 token。一次 API 調用可能執行許多操作以組成回應,以便滿足請求(考慮基於 GraphQL 的 API)。在這種情況下,此演算法有助於辨識一次調用可能比一個 token 花費 API 更多。

link to rate limit image

提供的 Redis 實作讓您可以定義使用者在特定時間段內可以進行呼叫的請求速率。它還可以適應零星的需求,同時受到定義的消耗速率的約束。例如,配置可以透過設定 redis-rate-limiter.replenishRate=500 屬性來定義每秒 500 個請求的補充速率,以及透過設定 redis-rate-limiter.burstCapacity=1000 屬性來定義每秒 1000 個請求的爆發容量。這樣做會將消耗限制為每秒 500 個請求。如果請求數量爆發,則只允許 1,000 個請求。但是,由於 1,000 個請求是 2 秒的配額,因此閘道在接下來的一秒內不會路由請求。此配置還讓您可以透過設定屬性 redis-rate-limiter.requestedTokens 屬性來定義請求將花費多少 token。通常,它被設定為 1。

若要使用具有請求限制功能的閘道,需要使用 RequestRateLimiter 閘道篩選器進行配置。此配置可以指定參數,以定義補充速率、爆發容量以及請求花費的 token 數量。下面的範例說明如何使用這些參數配置閘道

spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: https://127.0.0.1:8081
          predicates:
            - Path=/backend
          filters:
          - name: RequestRateLimiter
            args:
              redis-rate-limiter.replenishRate: 500
              redis-rate-limiter.burstCapacity: 1000
              redis-rate-limiter.requestedTokens: 1

Spring Cloud Gateway 提供了彈性,讓您可以定義自己的自訂速率限制器實作。它提供了一個 RateLimiter 介面來實作和定義一個 bean。可以使用 SPEL 表達式來配置速率限制器 bean,就像自訂金鑰解析器的情況一樣。例如,您可以定義一個名為 customRateLimiter 的自訂速率限制器 bean 和一個名為 customKeyResolver 的自訂金鑰解析器,並像這樣配置路由

@Bean
public KeyResolver customKeyResolver {
	return exchange -> ....  // returns a Mono of String
}
spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: https://127.0.0.1:8081
          predicates:
            - Path=/backend
          filters:
          - name: RequestRateLimiter
            args:
              rate-limiter: "#{customRateLimiter}"
              key-resolver: "#{customKeyResolver}"

GitHub 上提供了說明範例 GitHub

取得 Spring 電子報

隨時關注 Spring 電子報

訂閱

搶先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看全部