天天看點

Spring Cloud Gateway 之 限流

文章首發于公衆号《程式員果果》

位址:

https://mp.weixin.qq.com/s/yGNSsk5vatqTmAfD7-KcHg

簡介

在高并發的系統中,往往需要在系統中做限流,一方面是為了防止大量的請求使伺服器過載,導緻服務不可用,另一方面是為了防止網絡攻擊。

一般開發高并發系統常見的限流有:限制總并發數(比如資料庫連接配接池、線程池)、限制瞬時并發數(如 nginx 的 limit_conn 子產品,用來限制瞬時并發連接配接數)、限制時間視窗内的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 子產品,限制每秒的平均速率);其他還有如限制遠端接口調用速率、限制 MQ 的消費速率。另外還可以根據網絡連接配接數、網絡流量、CPU 或記憶體負載等來限流。

限流算法

計數器

簡單的做法是維護一個機關時間内的 計數器,每次請求計數器加1,當機關時間内計數器累加到大于設定的門檻值,則之後的請求都被拒絕,直到機關時間已經過去,再将 計數器 重置為零。此方式有個弊端:如果在機關時間1s内允許100個請求,在10ms已經通過了100個請求,那後面的990ms,隻能眼巴巴的把請求拒絕,我們把這種現象稱為“突刺現象”。

常用的更平滑的限流算法有兩種:漏桶算法 和 令牌桶算法。下面介紹下二者。

漏桶算法

漏桶算法思路很簡單,水(請求)先進入到漏桶裡,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(通路頻率超過接口響應速率),然後就拒絕請求,可以看出漏桶算法能強行限制資料的傳輸速率。

Spring Cloud Gateway 之 限流

可見這裡有兩個變量,一個是桶的大小,支援流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。因為漏桶的漏出速率是固定的參數,是以,即使網絡中不存在資源沖突(沒有發生擁塞),漏桶算法也不能使流突發(burst)到端口速率。是以,漏桶算法對于存在突發特性的流量來說缺乏效率。

令牌桶算法

令牌桶算法 和漏桶算法 效果一樣但方向相反的算法,更加容易了解。随着時間流逝,系統會按恒定 1/QPS 時間間隔(如果 QPS=100,則間隔是 10ms)往桶裡加入 Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個 Token,如果沒有 Token 可拿了就阻塞或者拒絕服務。

Spring Cloud Gateway 之 限流

令牌桶的另外一個好處是可以友善的改變速度。一旦需要提高速率,則按需提高放入桶中的令牌的速率。一般會定時(比如 100 毫秒)往桶中增加一定數量的令牌,有些變種算法則實時的計算應該增加的令牌的數量。

限流實作

在 Spring Cloud Gateway 上實作限流是個不錯的選擇,隻需要編寫一個過濾器就可以了。有了前邊過濾器的基礎,寫起來很輕松。

Spring Cloud Gateway 已經内置了一個RequestRateLimiterGatewayFilterFactory,我們可以直接使用。

目前RequestRateLimiterGatewayFilterFactory的實作依賴于 Redis,是以我們還要引入spring-boot-starter-data-redis-reactive。

pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

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

application.yml

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
        - id: limit_route
          uri: http://httpbin.org:80/get
          predicates:
          - After=2019-02-26T00:00:00+08:00[Asia/Shanghai]
          filters:
          - name: RequestRateLimiter
            args:
              key-resolver: '#{@hostAddrKeyResolver}'
              redis-rate-limiter.replenishRate: 1
              redis-rate-limiter.burstCapacity: 3
  application:
    name: gateway-limiter
  redis:
    host: localhost
    port: 6379
    database: 0           

在上面的配置檔案,配置了 redis的資訊,并配置了RequestRateLimiter的限流過濾器,該過濾器需要配置三個參數:

  • burstCapacity:令牌桶總容量。
  • replenishRate:令牌桶每秒填充平均速率。
  • key-resolver:用于限流的鍵的解析器的 Bean 對象的名字。它使用 SpEL 表達式根據#{@beanName}從 Spring 容器中擷取 Bean 對象。

IP限流

擷取請求使用者ip作為限流key。

@Bean
public KeyResolver hostAddrKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}           

使用者限流

擷取請求使用者id作為限流key。

@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}           

接口限流

擷取請求位址的uri作為限流key。

@Bean
KeyResolver apiKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getPath().value());
}