前言
- 在Redis–Zset的語法和使用場景舉例(朋友圈點贊,排行榜)一文中,提及了redis數(shù)據(jù)結(jié)構(gòu)zset的指令語法和一些使用場景,今天我們使用zset來實現(xiàn)滑動窗口限流,詳見下文。
什么是滑動窗口
-
滑動窗口是一種流量控制策略,用于控制一定時間內(nèi)請求的訪問數(shù)量。
-
其原理是:將時間劃分成規(guī)定的時間片段,每個片段有固定的時間間隔,如1s,1min,1h,然后定義一個時間窗口,比如5s,5min等,該窗口會隨著時間向右移動。此外還需要計數(shù)器計算窗口內(nèi)的請求數(shù)。當窗口移動時,會把已經(jīng)走過的時間片段的請求數(shù)刪掉。每當請求進入系統(tǒng)時,會檢查計數(shù)器中的請求數(shù)是否已經(jīng)滿了,如果計數(shù)未滿,則請求允許被執(zhí)行;否則執(zhí)行相應的拒絕方法。
-
滑動窗口在時間內(nèi)平滑地控制流量,而非簡單地固定請求數(shù)與速率,可以更加靈活地突發(fā)流量和峰值流量。
zset實現(xiàn)滑動窗口
-
在redis中可以使用zset實現(xiàn)滑動窗口作為限流方案,假如接口A每一分鐘只能訪問100次,那么我們可以將這個需要限流的接口名作為key,value采用zset數(shù)據(jù)結(jié)構(gòu),zset的score設(shè)置為當前請求的時間戳,zset的member只需要保證唯一性即可。
-
涉及到的zset指令
向zset添加數(shù)據(jù):zadd key score member
刪除zset某個score范圍內(nèi)的數(shù)據(jù): zremrangebyscore key min max
統(tǒng)計zset中數(shù)據(jù)的數(shù)量:zcard key -
代碼實現(xiàn):在代碼中定義滑動窗口大小為"windowSize",收到請求后,在redis生成zset,用zremrangebyscore刪除score小于當前時間戳減去"windowSize"的數(shù)據(jù),使用zcard查詢當前zset中的數(shù)據(jù)量,即請求量判斷是否超出限制值,若超出則不加入zset。
public class RedisRateLimiter { private Jedis jedis; private String key; //窗口大小 private int windowsize; //限制訪問的請求數(shù) private Integer limitValue; public RedisRateLimiter(Jedis jedis, String key, int windowsize, Integer limitValue) { this.jedis = jedis; this.key = key; this.windowsize = windowsize; this.limitValue = limitValue; } public boolean allowVisit() { //獲取當前時間戳 long nowTimeStamp = System.currentTimeMillis(); //窗口開始時間為當前時間戳減去60s long windowStartTime = nowTimeStamp - windowsize * 1000; //刪除score小于窗口開始時間的數(shù)據(jù) jedis.zremrangeByScore(key, "-inf", String.valueOf(windowStartTime)); if (jedis.zcard(key) < limitValue) { jedis.zadd(key, nowTimeStamp, String.valueOf(nowTimeStamp)); return true; } //超過limieValue 返回false return false; } /** * 上面的方法可以改寫為使用lua腳本,以避免高并發(fā)情況下的原子性問題 */ public boolean allowVIsitUseLua() { //獲取當前時間戳 long nowTimeStamp = System.currentTimeMillis(); String luaScript = """ local window_start_time = ARGV[1] -ARGV[3]*1000 redis.call('ZREMRANGEBYSCORE',KEYS[1],'-inf',window_start_time) local now_request = redis.call('ZCARD',KEYS[1]) if now_request < tonumber(ARGV[2]) then redis.call('ZADD',KEYS[1],ARGV[1],ARGV[1]) return 1 else return 0 end """; Object result = jedis.eval(luaScript, 1, key, String.valueOf(nowTimeStamp), String.valueOf(limitValue), String.valueOf(windowsize)); return (long) result == 1; } public static void main(String[] args) throws InterruptedException { Jedis jedis = new Jedis("127.0.0.1"); String key = "interfaceA"; jedis.del(key); RedisRateLimiter interfaceA = new RedisRateLimiter(jedis, key, 60, 10); //調(diào)用20次接口觀察結(jié)果 for (int i = 0; i < 20; i++) { System.out.println("當前時間:"+ DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss:SSS").format(LocalDateTime.now())+ "接口訪問情況: "+(interfaceA.allowVIsitUseLua()?"成功":"失敗")); Thread.sleep(1000); } } }
-
測試結(jié)果:我們在mian方法中,調(diào)用20次接口A,設(shè)置滑動窗口為60秒內(nèi)只可以訪問10次,觀察接口A的訪問情況:
-
觀察運行結(jié)果,因為60秒內(nèi)該接口只能調(diào)用10次,所以調(diào)用20次接口A,只有前10次成功了,與我們的期望相同。到此我們通過zset實現(xiàn)了滑動窗口限流的功能。文章來源:http://www.zghlxwxcb.cn/news/detail-804360.html
小結(jié)
本文通過Redis的有序集合Zset實現(xiàn)了滑動窗口限流的功能。然而這個方案也存在著缺點,因為zset要記錄滑動窗口內(nèi)的所有接口記錄,當我們的要求是某接口在60秒內(nèi)只能訪問100萬次,那么我們就可能得存入100萬條記錄,這種情況下,采用這種方案會消耗很大的存儲空間,明顯不適用。文章來源地址http://www.zghlxwxcb.cn/news/detail-804360.html
附錄
- 在window系統(tǒng)快速使用Redis服務(wù),只需要下載該壓縮包 redis壓縮包:redis.7z,解壓后,找到redis-server.exe即可啟動redis服務(wù)。
到了這里,關(guān)于Redis--Zset使用場景舉例(滑動窗口實現(xiàn)限流)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!