1. 程式人生 > >限流、消峰的三種辦法

限流、消峰的三種辦法

鏈接 png ret unit 做了 pan mit append vertex

互聯網服務賴以生存的根本是流量, 產品和運營會經常通過各種方式來為應用倒流,比如淘寶的雙十一等,如何讓系統在處理高並發的同時還是保證自身系統的穩定,通常在最短時間內提高並發的做法就是加機器,但是如果機器不夠怎麽辦?那就需要做業務降級或系統限流。

流量控制中用的比較多的三個算法就是令牌桶、漏桶、計數器。

1.令牌桶限流(TokenBucket)
令牌桶算法的基本過程如下:

  1. 每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增加一個令牌。
  2. 桶中最多存放 b 個令牌,如果桶滿了,新放入的令牌會被丟棄。
  3. 當一個 n 字節的數據包到達時,消耗 n 個令牌,然後發送該數據包。
  4. 如果桶中可用令牌小於 n,則該數據包將被緩存或丟棄。
    技術分享

2.漏桶限流(LeakBucket)
漏桶算法強制一個常量的輸出速率而不管輸入數據流的突發性,當輸入空閑時,該算法不執行任何動作.就像用一個底部開了個洞的漏桶接水一樣,水進入到漏桶裏,桶裏的水通過下面的孔以固定的速率流出,當水流入速度過大會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率.如下圖所示:

技術分享

漏桶和令牌桶比較
“漏桶算法”能夠強行限制數據的傳輸速率,而“令牌桶算法”在能夠限制數據的平均傳輸數據外,還允許某種程度的突發傳輸。在“令牌桶算法”中,只要令牌桶中存在令牌,那麽就允許突發地傳輸數據直到達到用戶配置的上限,因此它適合於具有突發特性的流量。

計數器限流
有時我們還會使用計數器來進行限流,主要用來限制一定時間內的總並發數。計數器限流方法可以通過緩存實現計數器,假如以秒為單位進行限流,過期時間為1秒,每次請求計數加1,超過每秒允許的最大請求數請求數閥值將被丟棄。

RateLimiter
我們可以使用 Guava 的 RateLimiter 來實現基於令牌桶的流量控制。RateLimiter 令牌桶算法的單桶實現,RateLimiter 對簡單的令牌桶算法做了一些工程上的優化,具體的實現是 SmoothBursty。需要註意的是,RateLimiter 的另一個實現 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。

SmoothBursty 有一個可以放 N 個時間窗口產生的令牌的桶,系統空閑的時候令牌就一直攢著,最好情況下可以扛 N 倍於限流值的高峰而不影響後續請求,就像三峽大壩一樣能扛千年一遇的洪水.

4.redis

local key = KEYS[1] --限流KEY(一秒一個)
local limit = tonumber(ARGV[1])        --限流大小
local current = tonumber(redis.call(get, key) or "0")
if current + 1 > limit then --如果超出限流大小
    redis.call("INCRBY", key,"1") -- 如果不需要統計真是訪問量可以不加這行
    return 0
else  --請求數+1,並設置2秒過期
    redis.call("INCRBY", key,"1")
    if tonumber(ARGV[2]) > -1 then
        redis.call("expire", key,tonumber(ARGV[2])) --時間窗口最大時間後銷毀鍵
    end
    return 1
end

lua腳本返回值比較奇怪,用java客戶端接受返回值,只能使用Long,沒有去深究。這個腳本只需要傳入key(url+時間戳/預設時間窗口大小),便可以實現限流。

java調用lua腳本

/**
 * Created by xujingfeng on 2017/3/13.
 * <p>
 * 基於redis lua腳本的線程安全的計數器限流方案
 * </p>
 */
public class RedisRateLimiter {

    /**
     * 限流訪問的url
     */
    private String url;

    /**
     * 單位時間的大小,最大值為 Long.MAX_VALUE - 1,以秒為單位
     */
    final Long timeUnit;

    /**
     * 單位時間窗口內允許的訪問次數
     */
    final Integer limit;

    /**
     * 需要傳入一個lua script,莫名其妙redisTemplate返回值永遠是個Long
     */
    private RedisScript<Long> redisScript;

    private RedisTemplate redisTemplate;

    /**
     * 配置鍵是否會過期,
     * true:可以用來做接口流量統計,用定時器去刪除
     * false:過期自動刪除,時間窗口過小的話會導致鍵過多
     */
    private boolean isDurable = false;

    public void setRedisScript(RedisScript<Long> redisScript) {
        this.redisScript = redisScript;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public boolean isDurable() {
        return isDurable;
    }

    public void setDurable(boolean durable) {
        isDurable = durable;
    }

    public RedisRateLimiter(Integer limit, Long timeUnit) {
        this.timeUnit = timeUnit;
        Assert.isTrue(timeUnit < Long.MAX_VALUE - 1);
        this.limit = limit;
    }

    public RedisRateLimiter(Integer limit, Long timeUnit, boolean isDurable) {
        this(limit, timeUnit);
        this.isDurable = isDurable;
    }

    public boolean acquire() {
        return this.acquire(this.url);
    }

    public boolean acquire(String url) {
        StringBuffer key = new StringBuffer();
        key.append("rateLimiter").append(":")
                .append(url).append(":")
                .append(System.currentTimeMillis() / 1000 / timeUnit);
        Integer expire = limit + 1;
        String convertExpire = isDurable ? "-1" : expire.toString();
        return redisTemplate.execute(redisScript, Arrays.asList(key.toString()), limit.toString(), convertExpire).equals(1l);
    }

}

5.spring mvc

技術分享

技術分享

技術分享



作者:Lewe
鏈接:http://www.jianshu.com/p/7170edcd9239
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。

限流、消峰的三種辦法