自定義限速功能實踐——Caffeine

FunTester發表於2024-03-25

之前使用了 JDK 自帶的 Map 實現了自定義限速的簡單需求。在當時的實現當中,有一個被隱藏的小設計,就是如果是用使用非同步執行緒,用來根據配置給請求次數資料重置。如此這樣,校驗方法會非常簡單方便。

對於普通 Java 專案來說,如果使用非同步執行緒處理,除了 deamon 程序以外,其他實現的確有點麻煩。即使 deamon 執行緒也很難做到完全的實用性,所以才使用了上篇文章的實現方案。

之前提到過一個非常有趣的高效能本地快取 Caffeine 剛好能解決這個問題,可以透過快取過期或者定時重新整理功能來實現定時重新整理的需求。這裡我選擇了定時重新整理功能,這種選擇會限制限流配置的種類,無法進行 2/3s , 10/2s 配置,我最終選擇 TPS 進行配置,全部使用 1s 為限制週期。

程式碼

主要思路如下:

  1. 資料結構選擇:使用了兩種資料結構來實現限流功能:使用了一個 Map 來儲存每個請求的限流配置,以請求的識別符號作為鍵,以該請求的每秒事務數(TPS)作為值;使用了 Caffeine 快取來儲存每個請求的計數器,其中鍵為請求的識別符號,值為一個原子整數,用於記錄請求的處理數量。
  2. 限流判斷邏輯:具體邏輯如下:首先從快取中獲取對應請求的計數器;判斷當前計數器的值是否大於等於該請求的配置的每秒事務數(TPS);如果超過了配置的值,則表示需要限流,返回 true。否則,遞增計數器並返回 false,表示不需要限流。
  3. 動態配置:動態新增請求的限流配置,將請求的識別符號和對應的每秒事務數(TPS)新增到配置中,實現了動態配置的功能。
  4. 使用 Caffeine 快取:使用了 Caffeine 快取來儲存請求的計數器,可以配置快取的過期時間(1 秒),當快取過期時會自動重新整理。這樣可以確保計數器在一定時間內有效,避免長時間未使用的請求佔用記憶體。

程式碼如下:


import com.github.benmanes.caffeine.cache.Caffeine  
import com.github.benmanes.caffeine.cache.LoadingCache  

import java.util.concurrent.TimeUnit  
import java.util.concurrent.atomic.AtomicInteger  
/**  
 * 限流工具,基於Caffeine實現,支援動態配置,根據TPS限流  
 */  
class TpsLimit {  

    Map<String, Integer> qpsConfig = [:]  

    LoadingCache<Object, AtomicInteger> build = Caffeine.newBuilder().refreshAfterWrite(1, TimeUnit.SECONDS).build((key) -> {  
        return new AtomicInteger()  
    })  

    /**  
     * 是否限流  
     * @param key  
     * @return  
     */  
    boolean isLimit(String key) {  
        AtomicInteger atomicInteger = build.get(key)  
        if (atomicInteger.get() >= qpsConfig.get(key, 1)) {  
            return true  
        }  
        atomicInteger.incrementAndGet()  
        return false  
    }  

    /**  
     * 新增限流配置  
     * @param key  
     * @param qps  
     * @return  
     */  
    def addConfig(String key, Integer qps) {  
        qpsConfig.put(key, qps)  
    }  

}

測試

測試指令碼如下,與前一篇文章大同小異:

import com.funtester.httpclient.FunHttp  
import com.funtester.utils.TpsLimit  

class Routine extends FunHttp {  

    static void main(String[] args) {  
        def limit = new TpsLimit()  
        limit.addConfig("test", 1)  
        1000.times {  
            sleep(0.1)  
            fun {  
                def limit1 = limit.isLimit("t4est")  
                if (!limit1) {  
                    output("未限流")  
                }  
            }  
        }    }  
}

控制檯輸出:

22:19:20:545 F-1  未限流
22:19:20:644 F-2  未限流
22:19:22:094 F-8  未限流
22:19:22:195 F-1  未限流
22:19:24:048 F-3  未限流
22:19:24:150 F-4  未限流
22:19:25 uptime:6 s
22:19:25 finished: 49 task

可以看出,按照預設配置 1 TPS 的配置實現。

  • 2021 年原創合集
  • 2022 年原創合集
  • 2023 年原創合集
  • 介面功能測試專題
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI 自動化
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章