欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redisson分布式限流的實(shí)現(xiàn)原理解析

 更新時(shí)間:2023年02月12日 09:59:17   作者:xindoo  
這篇文章主要為大家介紹了Redisson分布式限流的實(shí)現(xiàn)原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

我們目前在工作中遇到一個(gè)性能問題,我們有個(gè)定時(shí)任務(wù)需要處理大量的數(shù)據(jù),為了提升吞吐量,所以部署了很多臺(tái)機(jī)器,但這個(gè)任務(wù)在運(yùn)行前需要從別的服務(wù)那拉取大量的數(shù)據(jù),隨著數(shù)據(jù)量的增大,如果同時(shí)多臺(tái)機(jī)器并發(fā)拉取數(shù)據(jù),會(huì)對下游服務(wù)產(chǎn)生非常大的壓力。之前已經(jīng)增加了單機(jī)限流,但無法解決問題,因?yàn)檫@個(gè)數(shù)據(jù)任務(wù)運(yùn)行中只有不到10%的時(shí)間拉取數(shù)據(jù),如果單機(jī)限流限制太狠,雖然集群總的請求量控制住了,但任務(wù)吞吐量又降下來。如果限流閾值太高,多機(jī)并發(fā)的時(shí)候,還是有可能壓垮下游。 所以目前唯一可行的解決方案就是分布式限流。

我目前是選擇直接使用Redisson庫中的RRateLimiter實(shí)現(xiàn)了分布式限流,關(guān)于Redission可能很多人都有所耳聞,它其實(shí)是在Redis能力上構(gòu)建的開發(fā)庫,除了支持Redis的基礎(chǔ)操作外,還封裝了布隆過濾器、分布式鎖、限流器……等工具。今天要說的RRateLimiter及時(shí)其實(shí)現(xiàn)的限流器。接下來本文將詳細(xì)介紹下RRateLimiter的具體使用方式、實(shí)現(xiàn)原理還有一些注意事項(xiàng),最后簡單談?wù)勎覍Ψ植际较蘖鞯讓釉淼睦斫狻?/p>

RRateLimiter使用

RRateLimiter的使用方式異常的簡單,參數(shù)也不多。只要?jiǎng)?chuàng)建出RedissonClient,就可以從client中獲取到RRateLimiter對象,直接看代碼示例。

RedissonClient redissonClient = Redisson.create();
RRateLimiter rateLimiter = redissonClient.getRateLimiter("xindoo.limiter");
rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.HOURS); 

rateLimiter.trySetRate就是設(shè)置限流參數(shù),RateType有兩種,OVERALL是全局限流 ,PER_CLIENT是單Client限流(可以認(rèn)為就是單機(jī)限流),這里我們只討論全局模式。而后面三個(gè)參數(shù)的作用就是設(shè)置在多長時(shí)間窗口內(nèi)(rateInterval+IntervalUnit),許可總量不超過多少(rate),上面代碼中我設(shè)置的值就是1小時(shí)內(nèi)總許可數(shù)不超過100個(gè)。然后調(diào)用rateLimiter的tryAcquire()或者acquire()方法即可獲取許可。

rateLimiter.acquire(1); // 申請1份許可,直到成功
boolean res = rateLimiter.tryAcquire(1, 5, TimeUnit.SECONDS); // 申請1份許可,如果5s內(nèi)未申請到就放棄

使用起來還是很簡單的嘛,以上代碼中的兩種方式都是同步調(diào)用,但Redisson還同樣提供了異步方法acquireAsync()和tryAcquireAsync(),使用其返回的RFuture就可以異步獲取許可。

RRateLimiter的實(shí)現(xiàn)

接下來我們順著tryAcquire()方法來看下它的實(shí)現(xiàn)方式,在RedissonRateLimiter類中,我們可以看到最底層的tryAcquireAsync()方法。

    private <T> RFuture<T> tryAcquireAsync(RedisCommand<T> command, Long value) {
        byte[] random = new byte[8];
        ThreadLocalRandom.current().nextBytes(random);
        return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
                "——————————————————————————————————————"
                + "這里是一大段lua代碼"
                + "____________________________________",
                Arrays.asList(getRawName(), getValueName(), getClientValueName(), getPermitsName(), getClientPermitsName()),
                value, System.currentTimeMillis(), random);
    }

映入眼簾的就是一大段lua代碼,其實(shí)這段Lua代碼就是限流實(shí)現(xiàn)的核心,我把這段lua代碼摘出來,并加了一些注釋,我們來詳細(xì)看下。

local rate = redis.call("hget", KEYS[1], "rate")  # 100 
local interval = redis.call("hget", KEYS[1], "interval")  # 3600000
local type = redis.call("hget", KEYS[1], "type")  # 0
assert(rate ~= false and interval ~= false and type ~= false, "RateLimiter is not initialized")
local valueName = KEYS[2]      # {xindoo.limiter}:value 用來存儲(chǔ)剩余許可數(shù)量
local permitsName = KEYS[4]    # {xindoo.limiter}:permits 記錄了所有許可發(fā)出的時(shí)間戳  
# 如果是單實(shí)例模式,name信息后面就需要拼接上clientId來區(qū)分出來了
if type == "1" then
    valueName = KEYS[3]        # {xindoo.limiter}:value:b474c7d5-862c-4be2-9656-f4011c269d54
    permitsName = KEYS[5]      # {xindoo.limiter}:permits:b474c7d5-862c-4be2-9656-f4011c269d54
end
# 對參數(shù)校驗(yàn) 
assert(tonumber(rate) >= tonumber(ARGV[1]), "Requested permits amount could not exceed defined rate")
# 獲取當(dāng)前還有多少許可 
local currentValue = redis.call("get", valueName)   
local res
# 如果有記錄當(dāng)前還剩余多少許可 
if currentValue ~= false then
    # 回收已過期的許可數(shù)量
    local expiredValues = redis.call("zrangebyscore", permitsName, 0, tonumber(ARGV[2]) - interval)
    local released = 0
    for i, v in ipairs(expiredValues) do
        local random, permits = struct.unpack("Bc0I", v)
        released = released + permits
    end
    # 清理已過期的許可記錄
    if released > 0 then
        redis.call("zremrangebyscore", permitsName, 0, tonumber(ARGV[2]) - interval)
        if tonumber(currentValue) + released > tonumber(rate) then
            currentValue = tonumber(rate) - redis.call("zcard", permitsName)
        else
            currentValue = tonumber(currentValue) + released
        end
        redis.call("set", valueName, currentValue)
    end
    # ARGV  permit  timestamp  random, random是一個(gè)隨機(jī)的8字節(jié)
    # 如果剩余許可不夠,需要在res中返回下個(gè)許可需要等待多長時(shí)間 
    if tonumber(currentValue) < tonumber(ARGV[1]) then
        local firstValue = redis.call("zrange", permitsName, 0, 0, "withscores")
        res = 3 + interval - (tonumber(ARGV[2]) - tonumber(firstValue[2]))
    else
        redis.call("zadd", permitsName, ARGV[2], struct.pack("Bc0I", string.len(ARGV[3]), ARGV[3], ARGV[1]))
        # 減小可用許可量 
        redis.call("decrby", valueName, ARGV[1])
        res = nil
    end
else # 反之,記錄到還有多少許可,說明是初次使用或者之前已記錄的信息已經(jīng)過期了,就將配置rate寫進(jìn)去,并減少許可數(shù) 
    redis.call("set", valueName, rate)
    redis.call("zadd", permitsName, ARGV[2], struct.pack("Bc0I", string.len(ARGV[3]), ARGV[3], ARGV[1]))
    redis.call("decrby", valueName, ARGV[1])
    res = nil
end
local ttl = redis.call("pttl", KEYS[1])
# 重置
if ttl > 0 then
    redis.call("pexpire", valueName, ttl)
    redis.call("pexpire", permitsName, ttl)
end
return res

即便是加了注釋,相信你還是很難一下子看懂這段代碼的,接下來我就以其在Redis中的數(shù)據(jù)存儲(chǔ)形式,然輔以流程圖讓大家徹底了解其實(shí)現(xiàn)實(shí)現(xiàn)原理。

首先用RRateLimiter有個(gè)name,在我代碼中就是xindoo.limiter,用這個(gè)作為KEY你就可以在Redis中找到一個(gè)map,里面存儲(chǔ)了limiter的工作模式(type)、可數(shù)量(rate)、時(shí)間窗口大小(interval),這些都是在limiter創(chuàng)建時(shí)寫入到的redis中的,在上面的lua代碼中也使用到了。

其次還倆很重要的key,valueName和permitsName,其中在我的代碼實(shí)現(xiàn)中valueName是{xindoo.limiter}:value ,它存儲(chǔ)的是當(dāng)前可用的許可數(shù)量。我代碼中permitsName的具體值是{xindoo.limiter}:permits,它是一個(gè)zset,其中存儲(chǔ)了當(dāng)前所有的許可授權(quán)記錄(含有許可授權(quán)時(shí)間戳),其中SCORE直接使用了時(shí)間戳,而VALUE中包含了8字節(jié)的隨機(jī)值和許可的數(shù)量,如下圖:

{xindoo.limiter}:permits這個(gè)zset中存儲(chǔ)了所有的歷史授權(quán)記錄,直到了這些信息,相信你也就理解了RRateLimiter的實(shí)現(xiàn)原理,我們還是將上面的那大段Lua代碼的流程圖繪制出來,整個(gè)執(zhí)行的流程會(huì)更直觀。

看到這大家應(yīng)該能理解這段Lua代碼的邏輯了,可以看到Redis用了多個(gè)字段來存儲(chǔ)限流的信息,也有各種各樣的操作,那Redis是如何保證在分布式下這些限流信息數(shù)據(jù)的一致性的?

答案是不需要保證,在這個(gè)場景下,信息天然就是一致性的。原因是Redis的單進(jìn)程數(shù)據(jù)處理模型,在同一個(gè)Key下,所有的eval請求都是串行的,所有不需要考慮數(shù)據(jù)并發(fā)操作的問題。在這里,Redisson也使用了HashTag,保證所有的限流信息都存儲(chǔ)在同一個(gè)Redis實(shí)例上。

RRateLimiter使用時(shí)注意事項(xiàng)

了解了RRateLimiter的底層原理,再結(jié)合Redis自身的特性,我想到了RRateLimiter使用的幾個(gè)局限點(diǎn)(問題點(diǎn))。

RRateLimiter是非公平限流器

這個(gè)是我查閱資料得知,并且在自己代碼實(shí)踐的過程中也得到了驗(yàn)證,具體表現(xiàn)就是如果多個(gè)實(shí)例(機(jī)器)取競爭這些許可,很可能某些實(shí)例會(huì)獲取到大部分,而另外一些實(shí)例可憐巴巴僅獲取到少量的許可,也就是說容易出現(xiàn)旱的旱死 澇的澇死的情況。在使用過程中,你就必須考慮你能否接受這種情況,如果不能接受就得考慮用某些方式盡可能讓其變公平。

Rate不要設(shè)置太大

從RRateLimiter的實(shí)現(xiàn)原理你也看出了,它采用的是滑動(dòng)窗口的模式來限流的,而且記錄了所有的許可授權(quán)信息,所以如果你設(shè)置的Rate值過大,在Redis中存儲(chǔ)的信息(permitsName對應(yīng)的zset)也就越多,每次執(zhí)行那段lua腳本的性能也就越差,這對Redis實(shí)例也是一種壓力。個(gè)人建議如果你是想設(shè)置較大的限流閾值,傾向于小Rate+小時(shí)間窗口的方式,而且這種設(shè)置方式請求也會(huì)更均勻一些。

限流的上限取決于Redis單實(shí)例的性能

從原理上看,RRateLimiter在Redis上所存儲(chǔ)的信息都必須在一個(gè)Redis實(shí)例上,所以它的限流QPS的上限就是Redis單實(shí)例的上限,比如你Redis實(shí)例就是1w QPS,你想用RRateLimiter實(shí)現(xiàn)一個(gè)2w QPS的限流器,必然實(shí)現(xiàn)不了。 那有沒有突破Redis單實(shí)例性能上限的方式?單限流器肯定是實(shí)現(xiàn)不了的,我們可以拆分多個(gè)限流器,比如我搞10個(gè)限流器,名詞用不一樣的,然后每臺(tái)機(jī)器隨機(jī)使用一個(gè)限流器限流,實(shí)際的流量不就被分散到不同的限流器上了嗎,總的限流上線不也就上來了。

分布式限流的本質(zhì)

分布式限流的本質(zhì)實(shí)際上就是協(xié)同,協(xié)同的本質(zhì)就是信息交換,信息交換最重要的的就是信息的準(zhǔn)確性和一致性。 更簡單粗暴理解,分布式限流的本質(zhì)原理其實(shí)還是分布式數(shù)據(jù)一致性的原理,而限流只是數(shù)據(jù)結(jié)果的一種決策。所以只要以任何方式能讓信息同步,且保證信息的正確性就可以實(shí)現(xiàn)一個(gè)分布式限流器了,這就是我理解的本質(zhì)思路。

其實(shí)從上面的RRateLimiter的實(shí)現(xiàn)原理也可以看出來,它不就是存儲(chǔ)了一些信息嗎! 那我不用Redis,而是使用mysql行不行。實(shí)際肯定是可以的,只要將的上面Lua代碼中的所有操作都放到一個(gè)事務(wù)里,且事務(wù)的級(jí)別改成串行化,依舊能實(shí)現(xiàn)RRateLimiter同樣的功能。如果你具備Mysql相關(guān)知識(shí)的話,肯定也能基于Mysql將RRateLimiter的API封裝出來,但是封裝出來的限流器,其限流的上限就取決于Mysql實(shí)例的性能上限。

最近c(diǎn)hatGPT比較火,我也問了下它對分布式限流本質(zhì)原理的理解,下面是它的回答,大家覺得怎么樣?

分布式限流的本質(zhì)原理是通過在分布式系統(tǒng)中共享限流狀態(tài)來限制系統(tǒng)中單位時(shí)間內(nèi)的請求數(shù)量,從而避免系統(tǒng)因流量過大而崩潰。

這是通過使用一些共享的存儲(chǔ)組件,如數(shù)據(jù)庫,緩存,分布式鎖等來實(shí)現(xiàn)的。在每次請求時(shí),系統(tǒng)會(huì)檢查當(dāng)前的請求數(shù)量是否超過了預(yù)先設(shè)定的限制,如果超過了限制,請求就會(huì)被拒絕;如果未超過限制,請求就會(huì)被允許。

通過使用分布式限流技術(shù),系統(tǒng)可以在高并發(fā)情況下保持穩(wěn)定的性能,并避免因流量過大而導(dǎo)致的系統(tǒng)崩潰。

以上就是Redisson分布式限流的實(shí)現(xiàn)原理解析的詳細(xì)內(nèi)容,更多關(guān)于Redisson分布式限流的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Redis實(shí)現(xiàn)訂單自動(dòng)過期功能的示例代碼

    Redis實(shí)現(xiàn)訂單自動(dòng)過期功能的示例代碼

    這篇文章主要介紹了Redis實(shí)現(xiàn)訂單自動(dòng)過期功能的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • redis客戶端實(shí)現(xiàn)高可用讀寫分離的方式詳解

    redis客戶端實(shí)現(xiàn)高可用讀寫分離的方式詳解

    基于sentienl 獲取和動(dòng)態(tài)感知 master、slaves節(jié)點(diǎn)信息的變化,我們的讀寫分離客戶端就能具備高可用+動(dòng)態(tài)擴(kuò)容感知能力了,接下來通過本文給大家分享redis客戶端實(shí)現(xiàn)高可用讀寫分離的方式,感興趣的朋友一起看看吧
    2021-07-07
  • 淺談redis的過期時(shí)間設(shè)置和過期刪除機(jī)制

    淺談redis的過期時(shí)間設(shè)置和過期刪除機(jī)制

    本文主要介紹了redis的過期時(shí)間設(shè)置和過期刪除機(jī)制,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Redis主從/哨兵機(jī)制原理分析

    Redis主從/哨兵機(jī)制原理分析

    本文介紹了Redis的主從復(fù)制和哨兵機(jī)制,主從復(fù)制實(shí)現(xiàn)了數(shù)據(jù)的熱備份和負(fù)載均衡,而哨兵機(jī)制可以監(jiān)控Redis集群,實(shí)現(xiàn)自動(dòng)故障轉(zhuǎn)移,哨兵機(jī)制通過監(jiān)控、下線、選舉和故障轉(zhuǎn)移等步驟,確保Redis集群的高可用性
    2025-01-01
  • Redis排序命令Sort深入解析

    Redis排序命令Sort深入解析

    這篇文章主要為大家介紹了Redis排序命令Sort深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • redis lua腳本實(shí)戰(zhàn)秒殺和減庫存的實(shí)現(xiàn)

    redis lua腳本實(shí)戰(zhàn)秒殺和減庫存的實(shí)現(xiàn)

    本文主要是學(xué)習(xí)一下redis lua腳本的編寫,以及在redisson這個(gè)redis客戶端中是怎樣使用的,實(shí)戰(zhàn)一下秒殺場景redis減庫存lua腳本的編寫,并偽真實(shí)環(huán)境壓測查看效果。感興趣的可以了解一下
    2021-11-11
  • redis分布式鎖與zk分布式鎖的對比分析

    redis分布式鎖與zk分布式鎖的對比分析

    這篇文章主要介紹了redis分布式鎖與zk分布式鎖的對比分析,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Redis源碼解析sds字符串實(shí)現(xiàn)示例

    Redis源碼解析sds字符串實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了Redis源碼解析sds字符串實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • springmvc集成使用redis過程

    springmvc集成使用redis過程

    這篇文章主要介紹了springmvc集成使用redis過程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Redis key鍵的具體使用

    Redis key鍵的具體使用

    Redis 是一種鍵值(key-value)型的緩存型數(shù)據(jù)庫,它將數(shù)據(jù)全部以鍵值對的形式存儲(chǔ)在內(nèi)存中,本文就來介紹一下key鍵的具體使用,感興趣的可以了解一下
    2024-02-02

最新評論