限流在分布式系統(tǒng)中是一個(gè)經(jīng)常被提到的話題辉词,如果當(dāng)前系統(tǒng)的能力喉脖,不足以承受那么大的訪問量的時(shí)候况鸣,那么我們就要阻止外來請(qǐng)求對(duì)系統(tǒng)繼續(xù)施壓
實(shí)現(xiàn)簡(jiǎn)單限流
首先我們來看一個(gè)常見的簡(jiǎn)單限流策略何乎,系統(tǒng)要限制每個(gè)用戶在一定時(shí)間內(nèi)的某個(gè)行為只能操作N次昔搂,如何是用redis的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)這個(gè)限流的功能呢玲销。
解決方案
這個(gè)限流需求中存在一個(gè)滑動(dòng)時(shí)間窗口,想想 zset 數(shù)據(jù)結(jié)構(gòu)的 score 值摘符,是不是可以通過 score 來圈出這個(gè)時(shí)間窗口來贤斜。而且我們只需要保留這個(gè)時(shí)間窗口,窗口之外的數(shù)據(jù)都可以砍掉逛裤。那這個(gè) zset 的 value 填什么比較合適呢瘩绒?它只需要保證唯一性即可,用 uuid 會(huì)比較浪費(fèi)空間带族,那就改用毫秒時(shí)間戳吧锁荔。
如圖所示,用一個(gè) zset 結(jié)構(gòu)記錄用戶的行為歷史蝙砌,每一個(gè)行為都會(huì)作為 zset 中的一個(gè) key 保存下來阳堕。同一個(gè)用戶同一種行為用一個(gè) zset 記錄。
為節(jié)省內(nèi)存择克,我們只需要保留時(shí)間窗口內(nèi)的行為記錄恬总,同時(shí)如果用戶是冷用戶,滑動(dòng)時(shí)間窗口內(nèi)的行為是空記錄肚邢,那么這個(gè) zset 就可以從內(nèi)存中移除壹堰,不再占用空間。
通過統(tǒng)計(jì)滑動(dòng)窗口內(nèi)的行為數(shù)量與閾值 max_count 進(jìn)行比較就可以得出當(dāng)前的行為是否允許骡湖。用代碼表示如下:
public class SimpleRateLimiter {
private Jedis jedis;
public SimpleRateLimiter(Jedis jedis) {
this.jedis = jedis;
}
public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
String key = String.format("hist:%s:%s", userId, actionKey);
long nowTs = System.currentTimeMillis();
Pipeline pipe = jedis.pipelined();
pipe.multi();
pipe.zadd(key, nowTs, "" + nowTs);
pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
Response<Long> count = pipe.zcard(key);
pipe.expire(key, period + 1);
pipe.exec();
pipe.close();
return count.get() <= maxCount;
}
public static void main(String[] args) {
Jedis jedis = new Jedis();
SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
for(int i=0;i<20;i++) {
System.out.println(limiter.isActionAllowed("laoqian", "reply", 60, 5));
}
}
}
這段代碼還是略顯復(fù)雜贱纠,需要讀者花一定的時(shí)間好好啃。它的整體思路就是:每一個(gè)行為到來時(shí)响蕴,都維護(hù)一次時(shí)間窗口并巍。將時(shí)間窗口外的記錄全部清理掉,只保留窗口內(nèi)的記錄换途。zset 集合中只有 score 值非常重要懊渡,value 值沒有特別的意義刽射,只需要保證它是唯一的就可以了。
因?yàn)檫@幾個(gè)連續(xù)的 Redis 操作都是針對(duì)同一個(gè) key 的剃执,使用 pipeline 可以顯著提升 Redis 存取效率誓禁。但這種方案也有缺點(diǎn),因?yàn)樗涗洉r(shí)間窗口內(nèi)所有的行為記錄肾档,如果這個(gè)量很大摹恰,比如限定 60s 內(nèi)操作不得超過 100w 次這樣的參數(shù),它是不適合做這樣的限流的怒见,因?yàn)闀?huì)消耗大量的存儲(chǔ)空間俗慈。