自定義限速功能實踐——Map版本

說起 限速 ,想必各位不會陌生。通常在一個服務程序當中,限速指的是對同一類請求進行速率的限制,用來防止服務端某些資源被過度消耗,從而保障服務的穩定性。

限速的好處有以下幾點:

  1. 保護系統穩定性: 限速可以避免系統因過多請求而過載,導致性能下降甚至崩潰。通過限制請求速率,可以平滑地處理請求,保持系統的穩定運行狀態。
  2. 防止濫用和惡意攻擊: 限速可以有效地防止惡意用戶或自動化工具對系統進行濫用、DoS(拒絕服務)攻擊或暴力破解等行爲。通過限制請求速率,可以降低系統遭受攻擊的風險。
  3. 控制資源消耗: 一些服務或資源可能具有有限的容量或成本,限速可以幫助控制資源的消耗,確保資源被合理分配和利用。例如,限速可以避免數據庫或存儲系統被過度查詢,保護數據庫服務器的穩定性和性能。
  4. 提高服務質量: 通過限速可以減少請求的排隊和等待時間,提高系統對正常用戶的響應速度和服務質量。合理的限速策略可以平衡不同用戶和請求之間的競爭,使系統能夠更公平地分配資源。

通常在業務服務研發當中,我們會藉助成熟的框架來實現限流功能,例如下面所列舉的:

  1. Guava RateLimiter: Guava是Google開發的Java核心庫,其中包含了一個名爲RateLimiter的限流工具類。它基於令牌桶算法實現了簡單的限流功能,可以輕鬆地控制代碼的執行速率。
  2. Resilience4j: Resilience4j是一個用於構建彈性和容錯性應用的Java庫,其中包含了限流器(Rate Limiter)功能。它提供了多種限流算法和配置選項,可以靈活地應用於各種場景。
  3. Sentinel: Sentinel是阿里巴巴開源的流量控制框架,提供了流量控制、熔斷降級、系統負載保護等功能。它支持基於QPS、線程數、併發數等多種限流策略,並提供了實時監控和動態配置功能。
  4. Hystrix: Hystrix是Netflix開源的容錯框架,提供了限流、熔斷、降級等功能。雖然Hystrix已經進入維護模式,但仍然被許多項目廣泛使用。
  5. Bucket4j: Bucket4j是一個基於令牌桶算法的Java限流庫,具有簡單易用和高性能的特點。它支持在內存、Redis、Hazelcast等存儲後端進行限流。

雖然這些框架的功能都非常強大,但是在簡單場景當中,我們並不需要非常複雜的功能,只是對接口進行簡單限流,不涉及負載問題、也不存在分佈式需求。所以我打算繼續發揮能親自動手的就先試試的精神,自己實現一個限速的功能。

思路

配置管理:使用了一個Map<String, LimitConfig>來存儲每個限流key對應的限流配置。這些配置包括了最大次數和限流時間窗口持續時間。提供了添加限流配置的相關方法,可以爲每個限流key設置不同的最大次數和時間窗口。在是否被限流判斷方法中,會檢查配置中是否包含指定的限流key,如果不包含則添加默認配置,以確保每個限流key都有相應的配置。

限流狀態管理:使用了三個Map<String, Integer>來分別記錄每個限流key的最後一次請求時間、請求次數以及用於同步的鎖對象。判斷是否限流的方法裏面,會根據當前時間與最後一次請求時間的間隔以及請求次數來判斷是否需要限流。如果超過了限流時間窗口,則重新計數請求次數;如果請求次數超過了最大次數,則需要限流。

線程安全性:使用了ReentrantLock來保證對限流配置和狀態的線程安全訪問。例如,在添加配置項方法中使用了一個全局的寫鎖,以確保添加限流配置時的線程安全性。在判斷是否限流的方法中,對於每一個配置項也增加一個 ReentrantLock 保障修改操作的線程安全。同時在統計單個接口請求次數的類也用上了 java.util.concurrent.atomic.AtomicInteger

代碼

根據粉絲建議,我加了一些註釋,方便理解和使用。

  
import com.funtester.frame.SourceCode  
  
import java.util.concurrent.TimeUnit  
import java.util.concurrent.atomic.AtomicInteger  
import java.util.concurrent.locks.ReentrantLock  
  
/**  
 * 限流工具,支持N/M限流  
 */  
class RateLimit {  
  
    /**  
     * 總限流配置  
     */  
    Map<String, LimitConfig> config = [:]  
  
    /**  
     * 最後一次請求時間  
     */  
    Map<String, Integer> lastTime = [:]  
  
    /**  
     * 請求次數  
     */  
    Map<String, AtomicInteger> requestTimes = [:]  
  
    /**  
     * 所有key的鎖  
     */  
    Map<String, ReentrantLock> allLock = [:]  
  
    /**  
     * 寫鎖  
     */  
    ReentrantLock writeLock = new ReentrantLock()  
  
    /**  
     * 是否限流  
     * @param key 限流key  
     * @return  
     */  
    boolean isLimit(String key) {  
        if (!config.containsKey(key)) {  
            addConfig(key, 2, 2) //默認配置  
            return isLimit(key) //遞歸,初始化配置  
        }  
        def mark = SourceCode.getMark()  
        if (mark - lastTime[key] >= config[key].duration) {//進入下一個限流週期  
            if (allLock[key].tryLock(1, TimeUnit.SECONDS)) {  
                if (mark - lastTime[key] >= config[key].duration) {  
                    lastTime[key] = mark  
                    requestTimes[key] = new AtomicInteger(1)  
                    allLock[key].unlock()  
                    return false  
                } else {  
                    return true  
                }  
            }  
        }  
        if (requestTimes[key].get() >= config[key].maxTimes) //超過最大次數  
            return true  
        requestTimes[key].getAndIncrement() //增加次數  
        return false  
    }  
  
    /**  
     * 添加限流配置  
     * @param key 限流key  
     * @param maxTimes 最大次數  
     * @param duration 限流時間,單位秒  
     * @return  
     */  
    def addConfig(String key, int maxTimes, int duration) {  
        if (writeLock.tryLock(1, TimeUnit.SECONDS)) {  
            try {  
                if (!config.containsKey(key)) {  
                    config[key] = new LimitConfig(maxTimes: maxTimes, duration: duration)  
                    allLock[key] = new ReentrantLock()  
                    lastTime[key] = SourceCode.getMark()  
                    requestTimes[key] = new AtomicInteger(0)  
                }  
            } catch (e) {  
  
            } finally {  
                writeLock.unlock()  
            }  
        }  
    }  
  
    /**  
     * 限流配置  
     */  
    static class LimitConfig {  
  
        /**  
         * 最大次數  
         */  
        int maxTimes  
  
        /**  
         * 限流時間計算持續時間,單位秒  
         */  
        int duration  
  
    }  
}

測試

測試的腳本如下:

import com.funtester.httpclient.FunHttp  
import com.funtester.utils.RateLimit  
  
class Routine extends FunHttp {  
  
    static void main(String[] args) {  
        def limit = new RateLimit()  
        limit.addConfig("test", 1, 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

可以看到按照2/2s的默認配置生效了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章