最近學(xué)到的限流知識
前言
只有光頭才能變強。
文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y
之前在學(xué)習(xí)的時候也接觸不到高并發(fā)/大流量這種東西,所以限流當(dāng)然是沒接觸過的了。在看公司項目的時候,發(fā)現(xiàn)有用到限流(RateLimiter),順帶了解一波。
一、限流基礎(chǔ)知識介紹
為啥要限流,相信就不用我多說了。
比如,我周末去飯店吃飯,但是人太多了,我只能去前臺拿個號,等號碼到我的時候才能進飯店吃飯。如果飯店沒有限流怎么辦?一到飯點,人都往里沖,而飯店又處理不了這么多人流,很容易就出事故(飯店塞滿了人,無路可走。飯店的工作人員崩潰了,處理不過來)
回到代碼世界上也是一樣的,服務(wù)器能處理的請求數(shù)有限,如果請求量特別大,我們需要做限流(要么就讓請求等待,要么就把請求給扔了)
限流在代碼世界上,限流有兩種比較常見的算法:
令牌桶算法
漏桶算法
1.1 什么是漏桶算法
比如,現(xiàn)在我有一個桶子,綠色那塊是我能裝水的容量,如果超過我能裝下的容量,再往桶子里邊倒水,就會溢出來(限流):
桶子我們目前可以知道的是:
桶子的容量是固定的(是圖上綠色那塊)
超出了桶子的容量就會溢出(要么等待,要么直接丟棄)
OK,現(xiàn)在我們在桶子里挖個洞,讓水可以從洞子里邊流出來:
挖了個洞,水從洞口流出來桶子的洞口的大小是固定的,所以水從洞口流出來的速率也是固定的。
所以總結(jié)下來算法所需的參數(shù)就兩個:
桶子的容量
漏水的速率
漏桶算法有兩種實現(xiàn):
不允許突發(fā)流量的情況:如果進水的速率大于出水的速率,直接舍棄掉多余的水。比如,我的桶子容量能裝100L,但我的桶子出水速率是10L/s。此時,如果現(xiàn)在有100L/s的水進來,我只讓10L的水進到桶子,其余的都限流。(限定了請求的速度)
允許一定的突發(fā)流量情況:我的桶子能裝100L,如果現(xiàn)在我的桶子是空的,那么這100L的水都能瞬間進我的桶子。我以10L/s的速率將這些水流出,如果還有100L的水進來,只能限流了。
經(jīng)過上面的分析我們就知道:
漏桶算法可以平滑網(wǎng)絡(luò)上的突發(fā)流量(因為漏水的速率是固定的)
1.2 什么是令牌桶算法
現(xiàn)在我有另外一個桶子,這個桶子不用來裝水,用來裝令牌:
桶子裝著令牌令牌會一定的速率扔進桶子里邊,比如我1秒扔10個令牌進桶子:
以一定的速率扔令牌進桶子桶子能裝令牌的個數(shù)有上限的,比如我的桶子最多只能裝1000個令牌。
每個請求進來,就會去桶子拿一個令牌
比如這秒我有1001個請求,我就去桶子里邊拿1001個令牌,此時可能會出現(xiàn)兩種情況:
桶子里邊沒有1001個令牌,只有1000個,那沒拿到令牌的請求只能被阻塞了(等待)
桶子里邊有1001個令牌,所有請求都可以執(zhí)行。

令牌桶算法支持網(wǎng)絡(luò)上的突發(fā)流量
漏桶和令牌桶的區(qū)別:從上面的例子估計大家也能看出來了,漏桶只能以固定的速率去處理請求,而令牌桶可以以桶子最大的令牌數(shù)去處理請求
二、RateLimiter使用
RateLimiter是Guava的一個限流組件,我這邊的系統(tǒng)就有用到這個限流組件,使用起來十分方便。
引入pom依賴:
<dependency>
????<groupId>com.google.guavagroupId>
????<artifactId>guavaartifactId>
????<version>20.0version>
dependency>
RateLimiter它是基于令牌桶算法的,API非常簡單,看以下的Demo:
public?static?void?main(String[]?args)?{
????????//線程池
????????ExecutorService?exec?=?Executors.newCachedThreadPool();
????????//速率是每秒只有3個許可
????????final?RateLimiter?rateLimiter?=?RateLimiter.create(3.0);
????????for?(int?i?=?0;?i?100;?i++)?{
????????????final?int?no?=?i;
????????????Runnable?runnable?=?new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????try?{
????????????????????????//獲取許可
????????????????????????rateLimiter.acquire();
????????????????????????System.out.println("Accessing:?"?+?no?+?",time:"
????????????????????????????????+?new?SimpleDateFormat("yy-MM-dd?HH:mm:ss").format(new?Date()));
????????????????????}?catch?(Exception?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????}
????????????};
????????????//執(zhí)行線程
????????????exec.execute(runnable);
????????}
????????//退出線程池
????????exec.shutdown();
????}
我們可以從結(jié)果看出,每秒只能執(zhí)行三個:
每秒執(zhí)行三個三、分布式限流
RateLimiter是一個單機的限流組件,如果是分布式應(yīng)用的話,該怎么做?
可以使用Redis+Lua的方式來實現(xiàn),大致的lua腳本代碼如下:
local?key?=?"rate.limit:"?..?KEYS[1]?--限流KEY
local?limit?=?tonumber(ARGV[1])????????--限流大小
local?current?=?tonumber(redis.call('get',?key)?or?"0")
if?current?+?1?>?limit?then?--如果超出限流大小
??return?0
else??--請求數(shù)+1,并設(shè)置1秒過期
??redis.call("INCRBY",?key,"1")
???redis.call("expire",?key,"1")
???return?current?+?1
end
Java代碼如下:
public?static?boolean?accquire()?throws?IOException,?URISyntaxException?{
????Jedis?jedis?=?new?Jedis("127.0.0.1");
????File?luaFile?=?new?File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath()?+?"limit.lua");
????String?luaScript?=?FileUtils.readFileToString(luaFile);
????String?key?=?"ip:"?+?System.currentTimeMillis()/1000;?//?當(dāng)前秒
????String?limit?=?"5";?//?最大限制
????List?keys?=?new?ArrayList();
????keys.add(key);
????List?args?=?new?ArrayList();
????args.add(limit);
????Long?result?=?(Long)(jedis.eval(luaScript,?keys,?args));?//?執(zhí)行l(wèi)ua腳本,傳入?yún)?shù)
????return?result?==?1;
}
解釋:
Java代碼傳入key和最大的限制limit參數(shù)進lua腳本
執(zhí)行l(wèi)ua腳本(lua腳本判斷當(dāng)前key是否超過了最大限制limit)
如果超過,則返回0(限流)
如果沒超過,返回1(程序繼續(xù)執(zhí)行)
參考來源:
https://segmentfault.com/a/1190000016552464
更多資料參考:
https://segmentfault.com/a/1190000016042927
http://wuwenliang.net/2018/10/27/%E8%87%AA%E5%B7%B1%E5%86%99%E5%88%86%E5%B8%83%E5%BC%8F%E9%99%90%E6%B5%81%E7%BB%84%E4%BB%B6-%E5%9F%BA%E4%BA%8ERedis%E7%9A%84RateLimter/
公眾號文章導(dǎo)航:兩年嘔心瀝血的文章!
長按掃碼可關(guān)注獲取?
在看和分享對我非常重要!
