您好,登錄后才能下訂單哦!
在高并發的系統中,往往需要在系統中做限流,一方面是為了防止大量的請求使服務器過載,導致服務不可用,另一方面是為了防止網絡***。
一般開發高并發系統常見的限流有:限制總并發數(比如數據庫連接池、線程池)、限制瞬時并發數(如 nginx 的 limit_conn 模塊,用來限制瞬時并發連接數)、限制時間窗口內的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模塊,限制每秒的平均速率);其他還有如限制遠程接口調用速率、限制 MQ 的消費速率。另外還可以根據網絡連接數、網絡流量、CPU 或內存負載等來限流。
簡單的做法是維護一個單位時間內的 計數器,每次請求計數器加1,當單位時間內計數器累加到大于設定的閾值,則之后的請求都被拒絕,直到單位時間已經過去,再將 計數器 重置為零。此方式有個弊端:如果在單位時間1s內允許100個請求,在10ms已經通過了100個請求,那后面的990ms,只能眼巴巴的把請求拒絕,我們把這種現象稱為“突刺現象”。
常用的更平滑的限流算法有兩種:漏桶算法 和 令牌桶算法。下面介紹下二者。
漏桶算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),然后就拒絕請求,可以看出漏桶算法能強行限制數據的傳輸速率。
可見這里有兩個變量,一個是桶的大小,支持流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。因為漏桶的漏出速率是固定的參數,所以,即使網絡中不存在資源沖突(沒有發生擁塞),漏桶算法也不能使流突發(burst)到端口速率。因此,漏桶算法對于存在突發特性的流量來說缺乏效率。
令牌桶算法 和漏桶算法 效果一樣但方向相反的算法,更加容易理解。隨著時間流逝,系統會按恒定 1/QPS 時間間隔(如果 QPS=100,則間隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個 Token,如果沒有 Token 可拿了就阻塞或者拒絕服務。
令牌桶的另外一個好處是可以方便的改變速度。一旦需要提高速率,則按需提高放入桶中的令牌的速率。一般會定時(比如 100 毫秒)往桶中增加一定數量的令牌,有些變種算法則實時的計算應該增加的令牌的數量。
在 Spring Cloud Gateway 上實現限流是個不錯的選擇,只需要編寫一個過濾器就可以了。有了前邊過濾器的基礎,寫起來很輕松。
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());
}
歡迎關注我的公眾號《程序員果果》,關注有驚喜~~
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。