在高併發的系統中,每每須要在系統中作限流,一方面是爲了防止大量的請求使服務器過載,致使服務不可用,另外一方面是爲了防止網絡***。java
通常開發高併發系統常見的限流有:限制總併發數(好比數據庫鏈接池、線程池)、限制瞬時併發數(如 nginx 的 limit_conn 模塊,用來限制瞬時併發鏈接數)、限制時間窗口內的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模塊,限制每秒的平均速率);其餘還有如限制遠程接口調用速率、限制 MQ 的消費速率。另外還能夠根據網絡鏈接數、網絡流量、CPU 或內存負載等來限流。react
簡單的作法是維護一個單位時間內的 計數器,每次請求計數器加1,當單位時間內計數器累加到大於設定的閾值,則以後的請求都被拒絕,直到單位時間已通過去,再將 計數器 重置爲零。此方式有個弊端:若是在單位時間1s內容許100個請求,在10ms已經經過了100個請求,那後面的990ms,只能眼巴巴的把請求拒絕,咱們把這種現象稱爲「突刺現象」。nginx
經常使用的更平滑的限流算法有兩種:漏桶算法 和 令牌桶算法。下面介紹下兩者。程序員
漏桶算法思路很簡單,水(請求)先進入到漏桶裏,漏桶以必定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),而後就拒絕請求,能夠看出漏桶算法能強行限制數據的傳輸速率。redis
可見這裏有兩個變量,一個是桶的大小,支持流量突發增多時能夠存多少的水(burst),另外一個是水桶漏洞的大小(rate)。由於漏桶的漏出速率是固定的參數,因此,即便網絡中不存在資源衝突(沒有發生擁塞),漏桶算法也不能使流突發(burst)到端口速率。所以,漏桶算法對於存在突發特性的流量來講缺少效率。算法
令牌桶算法 和漏桶算法 效果同樣但方向相反的算法,更加容易理解。隨着時間流逝,系統會按恆定 1/QPS 時間間隔(若是 QPS=100,則間隔是 10ms)往桶裏加入 Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),若是桶已經滿了就再也不加了。新請求來臨時,會各自拿走一個 Token,若是沒有 Token 可拿了就阻塞或者拒絕服務。spring
令牌桶的另一個好處是能夠方便的改變速度。一旦須要提升速率,則按需提升放入桶中的令牌的速率。通常會定時(好比 100 毫秒)往桶中增長必定數量的令牌,有些變種算法則實時的計算應該增長的令牌的數量。數據庫
在 Spring Cloud Gateway 上實現限流是個不錯的選擇,只須要編寫一個過濾器就能夠了。有了前邊過濾器的基礎,寫起來很輕鬆。api
Spring Cloud Gateway 已經內置了一個RequestRateLimiterGatewayFilterFactory,咱們能夠直接使用。服務器
目前RequestRateLimiterGatewayFilterFactory的實現依賴於 Redis,因此咱們還要引入spring-boot-starter-data-redis-reactive。
<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>
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的限流過濾器,該過濾器須要配置三個參數:
獲取請求用戶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()); }
歡迎關注個人公衆號《程序員果果》,關注有驚喜~~