架構師必備:限流方案選型(使用篇)
大家好,我是Java烘焙師。為了避免突增流量引起服務雪崩,需要對接口、存儲資源做限流保護,根據系統負載情況設置合適的限流值。下面結合筆者的經驗和思考,對主要限流方案的選型做一下總結,本篇先看如何使用,下一篇再看背后的原理。
下面介紹幾種常見限流方案的使用方法、優缺點:
- 單機限流:Guava RateLimiter
- 同時支持單機限流、集群限流:Sentinel
- 分布式限流:Redisson RateLimiter
1. 單機限流:Guava RateLimiter
使用
官網API文檔:https://guava.dev/releases/snapshot-jre/api/docs/com/google/common/util/concurrent/RateLimiter.html
Guava是Google開源的Java類庫,提供了很多實用的工具,包括集合、本地緩存、并發編程工具等。Guava RateLimiter就是其中的單機限流工具,核心概念如下:
- 每秒允許通過的最大permits:
- 當請求獲取1個permit時,就相當于是qps限流
- 當請求獲取多個permits時,代表該請求需要消耗多少資源,比如想限定更新DB的SQL qps為1000,則需獲取1000 permits
- 支持預熱:在預熱期內逐步支持到最大permits,適用于需要預熱緩存資源的場景
- 允許突發流量:每次請求是否被限流,不取決于該次請求permits的多少,而是取決于前一次請求,前一次請求的permits越多,則后續請求需等待的時間越長
- 比如有一個空閑的RateLimiter,第一次請求無論是獲取1 permit、還是10000 permits,都能立刻成功,但是會計算出下一次permits可用的時間。那么第一次請求是10000 permits時,第二次請求即使只需10 permits也可能要長時間等待
如下分別是阻塞、非阻塞方式,使用Guava RateLimiter的示例。
// 創建一個每秒允許10個permits的限流器
RateLimiter rateLimiter = RateLimiter.create(10.0);
// 1. 阻塞的方式
long startTime = System.currentTimeMillis();
// 獲取5 permits;如果充足,則獲取成功,否則阻塞等待
rateLimiter.aquire(5);
long endTime = System.currentTimeMillis();
// 打印阻塞等待的時間
System.out.println("Processing business logic. Waiting time ms: " + (endTime - startTime));
// 2. 非阻塞的方式
// 嘗試獲取1 permit
if (rateLimiter.tryAcquire()) {
// 如果能獲取到令牌,則繼續處理業務邏輯
System.out.println("Processing business logic...");
} else {
// 否則快速失敗,返回友好提示或執行降級邏輯
System.out.println("Too many requests.");
}
優點
- 性能極高:純計算,無網絡開銷
- 實現簡單
缺點
- 無法跨實例協同,從整體層面控制限流
2. 同時支持單機限流、集群限流:Sentinel
使用
官網文檔:https://sentinelguard.io/zh-cn/docs/introduction.html
Sentinel是阿里開源的流量治理組件,在國內使用較廣泛。核心概念如下:
- 資源:可以是接口(調入、調出均可),也可以是任何一段代碼邏輯。可通過sentinel注解、或者sentinel API包裝成受保護的資源
- 規則:圍繞資源的實時狀態設定的規則,包括流量控制規則、熔斷降級規則以及系統保護規則,所有規則都可以動態實時調整。
- 常見的流量控制規則:
- 限流閾值類型:支持QPS、線程數模式,默認是按QPS來限流
- 流控效果:即超過限流閾值時的行為,支持直接拒絕、排隊等待、慢啟動模式,默認是直接拒絕
- 常見的熔斷降級規則:
- 熔斷策略:即在什么情況下熔斷降級,支持慢調用比例、異常比例、異常數策略,默認是慢調用比例
- 熔斷閾值:慢調用比例模式下為慢調用的RT平均響應時間,異常比例/異常數模式下為對應的閾值
- 常見的流量控制規則:
- 集群限流:是為了解決流量在集群內各實例之間不均勻、導致觸發單機限流,而達不到總QPS預期的問題。集群限流有2種模式:單機均攤、全局閾值,為了避免token server成為瓶頸,比較典型的是單機均攤。
- 單機均攤:先設置一個集群限流閾值,然后再根據機器實例數均攤到單機上,即
單機限流值=集群限流值/機器實例數 - 全局閾值:集群流控中共有兩種身份
- Token Client:集群流控客戶端,用于向所屬 Token Server 通信請求 token。集群限流服務端會返回給客戶端結果,決定是否限流
- Token Server:即集群流控服務端,處理來自 Token Client 的請求,根據配置的集群規則判斷是否應該發放 token(是否允許通過)
- 單機均攤:先設置一個集群限流閾值,然后再根據機器實例數均攤到單機上,即
如下分別是通過sentinel注解、sentinel API方式的示例。
- sentinel注解一般加在受保護的接口、訪問存儲資源的方法上
- sentinel API更加靈活,可包住受保護的業務代碼片段;更進一步,結合業務入參,可實現熱點參數限流
// 1. sentinel注解:在sentinel控制臺上可針對定義的資源名配置限流規則
@Service
@SentinelResource(value = "protectedMethod")
public String protectedMethod(Request request) {
}
// 2. sentinel API
// 2.1 拋異常方式定義資源
try (Entry entry = SphU.entry("protectedResource1")) {
// 被保護的邏輯
System.out.println("protected resource 1");
} catch (BlockException ex) {
// 處理被流控的邏輯
System.out.println("blocked!");
}
// 2.2 返回布爾值方式定義資源
if (SphO.entry("protectedResource2")) {
// 務必保證finally會被執行
try {
// 被保護的業務邏輯
} finally {
SphO.exit();
}
} else {
// 資源訪問阻止,被限流或被降級
// 進行相應的處理操作
}
// 2.3 熱點參數限流
try (Entry entry = SphU.entry("hotspotResource", EntryType.IN, 1, paramA, paramB)) {
// 易出現熱點參數的業務邏輯
} catch (BlockException ex) {
// 熱點參數限流
} finally {
if (entry != null) {
entry.exit(1, paramA, paramB);
}
}
優點
- 易于與Java框架集成,提高應用開發效率:如Spring Cloud、Dubbo等
- 功能強大:在控制臺頁面,可動態配置限流規則、熔斷降級規則
缺點
- sentinel的集群限流,在單機均攤模式下最終會換算為單機限流,當集群限流值較低、機器實例數較多時,計算出的單機限流值可能不準,無法精準控制總的集群限流
- 例如:有100臺機器,按30 qps來限流;則計算出的單機均攤qps不足1、按1處理,這樣總的集群限流就變成了100 qps,大于預期的30 qps
3. 分布式限流:Redisson RateLimiter
使用
官網API文檔:https://redisson.pro/docs/data-and-services/objects/#ratelimiter
Redisson是一個高性能的Redis客戶端框架,提供了基于redis實現的分布式工具,如分布式集合、分布式鎖、布隆過濾器等,借助Redisson RateLimiter可以實現分布式限流。核心概念如下:
- 允許在一段時間間隔內,最多有n個permits通過
- 通過redis lua腳本實現限流邏輯、以及原子操作
如下是使用Redisson RateLimiter的示例。
// 使用Redisson的分布式限流器
RRateLimiter limiter = redisson.getRateLimiter("myLimiter");
// 限定每60秒100個permits
limiter.trySetRate(RateType.OVERALL, 100, 60, RateIntervalUnit.SECONDS);
// 阻塞式獲取5 permits
limiter.acquire(5);
// 非阻塞式獲取1 permit
if (limiter.tryAcquire(1)) {
// 獲取到permit,執行業務邏輯
} else {
// 被限流
}
優點
- 基于redis實現了分布式限流,能較精準地控制低qps場景的限流
- 與特定框架/限流組件無關
缺點
- 引入了外部依賴,有網絡開銷,系統穩定性易受影響
其它分布式限流方案
筆者還了解到有一個redis模塊 redis-cell 可做分布式限流。不過有以下問題,不是很建議使用:
- 需要額外運維部署,成本較高
- 項目由個人維護,目前已基本停滯
結論
- Java應用開發,一般情況下,可引入Sentinel做接口、存儲資源的限流控制
- 單機任務,如手動執行或定期執行的task,可引入Guava RateLimiter做簡易的限流控制
- 低qps場景的限流、或不想接入特定框架/限流組件的服務,可引入Redisson RateLimiter來實現較精準的分布式限流控制

浙公網安備 33010602011771號