利用Redis實(shí)現(xiàn)訪問次數(shù)限流的方法詳解
假設(shè)我們要做一個(gè)業(yè)務(wù)需求,這個(gè)需求就是限制用戶的訪問頻次。比如1分鐘內(nèi)只能訪問20次,10分鐘內(nèi)只能訪問200次。因?yàn)槭怯脩艟S度的場景,性能肯定是要首先考慮,那么適合這個(gè)場景的非Redis莫屬。
最簡單的實(shí)現(xiàn),莫過于只是用incr進(jìn)行計(jì)數(shù)操作,于是有了下面的代碼:
long count = redisTemplate.opsForValue().increment("user:1:60"); if (count > maxLimitCount) { throw new LimitException("訪問太頻繁"); } count = redisTemplate.opsForValue().increment("user:1:600"); if (count > maxLimitCount) { throw new LimitException("訪問太頻繁"); }
來,我們對(duì)上面這段代碼解讀一下。需求有2個(gè)時(shí)間維度的限制,所以這邊基于用戶和時(shí)間維度構(gòu)建了Redis的Key。然后對(duì)每個(gè)Key進(jìn)行計(jì)數(shù),計(jì)數(shù)后的結(jié)果用于跟限制的值進(jìn)行判斷,如果超出了限制的值就拋出異常。
假設(shè)限制的時(shí)間場景有10個(gè)呢?那上面的代碼是不是得寫10遍才可以。有人可能會(huì)說,這還不簡單嗎?循環(huán)呀,循環(huán)確實(shí)能夠解決這個(gè)問題。但是大家有沒有去思考,這是用戶維度的請(qǐng)求,如果每個(gè)請(qǐng)求里面都去操作10次Redis的話,這耗時(shí)至少也得10來毫秒吧。所以問題在這,并不是說這個(gè)邏輯實(shí)現(xiàn)的有問題。
那我們就改成批量的吧,用pipeline來批量執(zhí)行。
redisTemplate.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { connection.openPipeline(); connection.incr("user:1:60".getBytes()); connection.incr("user:1:600".getBytes()); onnection.closePipeline(); return null; } });
用pipeline也有一個(gè)問題,那就是拿不到返回值,也就只能增加,但是沒辦法判斷是否超過了限制的閥值。
所以需要在第一步先查詢下,用查到的值進(jìn)行判斷,這樣也就是只需要和Redis交互兩次就可以了。
上面的代碼在單節(jié)點(diǎn)下沒問題,但是如果在集群下,其實(shí)每個(gè)Key都可能分配到不同的節(jié)點(diǎn)上去,只不過是底層幫你屏蔽掉了細(xì)節(jié),并發(fā)執(zhí)行,拿到了所有結(jié)果后合并返回的。所以我們需要讓所有的Key都路由到一個(gè)節(jié)點(diǎn)上,本來就是用戶維度的,直接使用userId路由即可。
這個(gè)時(shí)候Redis的HashTag功能就排上用場了,將Key user:1:600改寫成user:{1}:600 。
雖然已經(jīng)優(yōu)化了,但是還是要發(fā)起兩次網(wǎng)絡(luò)請(qǐng)求才能完成這個(gè)邏輯,有沒有可能再進(jìn)一步優(yōu)化下呢?一次請(qǐng)求行不行。
這個(gè)時(shí)候要放大招了,Lua腳本走起,將所有邏輯都放入Lua腳本中,一次網(wǎng)絡(luò)交互即可完成。
local current current = redis.call("incr",KEYS[1]) if current == 1 then redis.call("expire",KEYS[1],1) end if current > ARGV[1] return 1 end return 0
上面腳本演示了如何對(duì)一個(gè)Key進(jìn)行處理,返回1表示限流,返回0表示通過。不過使用lua腳本的時(shí)候要注意,某些云服務(wù)的Redis會(huì)對(duì)腳本進(jìn)行校驗(yàn),像Redis的Key不能使用變量,必須用KEYS[下標(biāo)]的方式,所以這里操作多個(gè)Key還不能用循環(huán),代碼得寫多遍,這是一個(gè)惡心的點(diǎn)。
總結(jié)
到此這篇關(guān)于利用Redis實(shí)現(xiàn)訪問次數(shù)限流的文章就介紹到這了,更多相關(guān)Redis訪問次數(shù)限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis?異常?read?error?on?connection?的解決方案
這篇文章主要介紹了Redis異常read?error?on?connection的解決方案,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08redis計(jì)數(shù)器與數(shù)量控制的實(shí)現(xiàn)
使用Redis計(jì)數(shù)器可以輕松地解決數(shù)量控制的問題,同時(shí)還能有效地提高應(yīng)用的性能,本文主要介紹了redis計(jì)數(shù)器與數(shù)量控制的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Redis分布式限流組件設(shè)計(jì)與使用實(shí)例
本文主要講解基于 自定義注解+Aop+反射+Redis+Lua表達(dá)式 實(shí)現(xiàn)的限流設(shè)計(jì)方案。實(shí)現(xiàn)的限流設(shè)計(jì)與實(shí)際使用。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08使用redis實(shí)現(xiàn)延遲通知功能(Redis過期鍵通知)
這篇文章主要介紹了使用redis實(shí)現(xiàn)延遲通知功能(Redis過期鍵通知)的相關(guān)知識(shí),本文通過實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-09-09使用百度地圖api通過redis實(shí)現(xiàn)地標(biāo)存儲(chǔ)及范圍坐標(biāo)點(diǎn)查詢功能
這篇文章主要介紹了使用百度地圖api通過redis實(shí)現(xiàn)地標(biāo)存儲(chǔ)及范圍坐標(biāo)點(diǎn)查詢功能,本文通過圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08Redis主從配置和底層實(shí)現(xiàn)原理解析(實(shí)戰(zhàn)記錄)
今天給大家分享Redis主從配置和底層實(shí)現(xiàn)原理解析,本文通過實(shí)戰(zhàn)項(xiàng)目給大家源碼解析,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-06-06