Stories

Detail Return Return

架構師必備:限流方案選型(使用篇) - Stories Detail

大家好,我是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來實現較精準的分佈式限流控制
user avatar coderdd Avatar leguandeludeng Avatar nullwy Avatar airenaodejiangniurou_elzfx0 Avatar ucrx2py9 Avatar kaiwudb Avatar
Favorites 6 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.