概述
在微服務、API 化、云原生大行其道的今天,服務治理不可或缺,而服務治理中限流幾乎是必不可少的手段;微服務化往往伴隨著分布式的架構,那么僅僅單機限流是不夠的,還需要分布式的限流,
那么問題就來了:分布式限流中,往往會出現「限流不均衡」或「限流誤差」的情況,這是為什么呢?
限流
國慶假期,限流這個詞在新聞中應該能頻繁聽到,就是「景區限流」,這里以無錫的兩個景點為例:
??示例:
- 無錫蠡園:最大承載量調整至 20000 人;瞬時最大承載量調整至 4000 人;
- 無錫東林書院:書院接待日最大承載量即時降至 1500 人,瞬時承載量降至 300 人,
在計算機網路中,限流就是用于控制網路介面控制器發送或接收請求的速率[1],由此延伸為:限制到達系統的并發請求數,以此來保障系統的穩定性(特別是在微服務、API 化、云原生系統上),
常見的限流演算法
- 固定視窗計數器
- 滑動視窗計數器
- 漏桶
- 令牌桶
單機限流和分布式限流
本質上單機限流和分布式限流的區別就在于「承載量」存放的位置,
單機限流直接在單臺服務器上實作,而在微服務、API 化、云原生系統上,應用和服務是集群部署的,因此需要集群內的多個實體協同作業,以提供集群范圍的限流,這就是分布式限流,
??為什么分布式限流會出現不均衡的情況?
比如上面提到的滑動視窗的演算法,可以將計數器存放至 Redis 這樣的 KV 資料庫中,
例如滑動視窗的每個請求的時間記錄可以利用 Redis 的 zset 存盤,利用 ZREMRANGEBYSCORE 洗掉時間視窗之外的資料,再用 ZCARD 計數,
示例代碼[2]如下:
package com.lizba.redis.limit;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
/**
* <p>
* Limiting current by sliding window algorithm through zset
* </p>
*
* @Author: Liziba
* @Date: 2021/9/6 18:11
*/
public class SimpleSlidingWindowByZSet {
private Jedis jedis;
public SimpleSlidingWindowByZSet(Jedis jedis) {
this.jedis = jedis;
}
/**
* Judging whether an action is allowed
*
* @param userId User id
* @param actionKey Behavior key
* @param period Current Limiting Cycle
* @param maxCount Maximum number of requests (sliding window size)
* @return
*/
public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
String key = this.key(userId, actionKey);
long ts = System.currentTimeMillis();
Pipeline pipe = jedis.pipelined();
pipe.multi();
pipe.zadd(key, ts, String.valueOf(ts));
// Remove data other than sliding windows
pipe.zremrangeByScore(key, 0, ts - (period * 1000));
Response<Long> count = pipe.zcard(key);
// Set the expiration time of the behavior, and if the data is cold, zset will be deleted to save memory space
pipe.expire(key, period);
pipe.exec();
pipe.close();
return count.get() <= maxCount;
}
/**
* Current limiting key
*
* @param userId
* @param actionKey
* @return
*/
public String key(String userId, String actionKey) {
return String.format("limit:%s:%s", userId, actionKey);
}
}
像令牌桶也可以將令牌數量放到 Redis 中,
??答案一:批量導致的誤差
不過以上的方式相當于每一個請求都需要去 Redis 判斷一下能不能通過,在性能上有一定的損耗,所以針對大并發系統,有個優化點就是 「批量」,例如每次取令牌不是一個一取,而是取一批,不夠了再去取一批,這樣可以減少對 Redis 的請求,
但是,批量獲取就會導致一定范圍內的限流誤差,比如 a 實體此刻取了 100 個,等下一秒再用,那下一秒集群總承載量就有可能超過閾值,
這是一種原因,
??答案二:負載均衡負載不均
分布式限流還有一種做法是「平分」,比如之前單機限流 100,現在集群部署了 5 個實體,那就讓每臺繼續限流 100,即在總的入口做總的流量限制,比如 500,然后每個實體再自己實作限流,
這種情況下,假設總的入口放入了 500 請求,這些請求需要通過負載均衡演算法(如:輪詢、最小連接數、最小連接時間等)以及會話保持策略(如:源地址保持、cookie 保持或特定引數的 hash),分到每臺的請求就可能是不均衡的,比如 a 實體有 70 個,b 實體有 130 個,那么 a 實體的 70 個會通過,而 b 實體的 130 個可能只有 100 個會通過,這時就出現了「限流不均衡」或「限流偏差」的情況,
這是第二種原因,
總結
由于本人經驗所限,本文只列出了我目前能想到的 2 個答案給大家參考,歡迎各位交流補充,
真實的業務場景是很復雜的,具體到一個工程,限流需要考慮的條件和資源有很多,我們要做的就是通過估算、壓測、試運行、調整、再生產驗證再調整來逼近理想情況,
三人行, 必有我師; 知識共享, 天下為公. 本文由東風微鳴技術博客 EWhisper.cn 撰寫.
Rate limiting - Wikipedia ??
Redis zset for sliding window current limiting (programmer.group) ??
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/540153.html
標籤:其他
