淺談Redis批量刪除的大坑
引言
Redis作為高性能的鍵值存儲(chǔ)系統(tǒng),被廣泛應(yīng)用于緩存、消息隊(duì)列、會(huì)話管理等場(chǎng)景。然而,當(dāng)數(shù)據(jù)量增長(zhǎng)到一定規(guī)模時(shí),如何高效、安全地刪除大量鍵(Keys)成為了一個(gè)棘手的問(wèn)題。最近,我在生產(chǎn)環(huán)境中遇到了一個(gè)Redis批量刪除的“大坑”,差點(diǎn)導(dǎo)致系統(tǒng)崩潰,甚至讓我加班到天亮。本文將詳細(xì)剖析這個(gè)問(wèn)題的根源、解決方案以及背后的技術(shù)原理,希望能幫助大家避開(kāi)類似的陷阱。
背景:為什么需要批量刪除?
在實(shí)際業(yè)務(wù)中,批量刪除Redis鍵的場(chǎng)景非常常見(jiàn),例如:
- 清理過(guò)期或無(wú)效的緩存
- 遷移數(shù)據(jù)時(shí)清空舊鍵
- 測(cè)試環(huán)境的數(shù)據(jù)重置
常見(jiàn)的批量刪除方式包括:
- 使用
DEL命令逐個(gè)刪除 - 使用
KEYS命令匹配鍵后刪除 - 使用
SCAN命令迭代刪除 - 使用
UNLINK命令異步刪除
然而,這些方法在數(shù)據(jù)量較大時(shí)可能會(huì)引發(fā)嚴(yán)重問(wèn)題,尤其是KEYS和DEL的組合,稍有不慎就會(huì)導(dǎo)致Redis阻塞甚至宕機(jī)。
主體:踩坑經(jīng)歷與技術(shù)分析
1. 最初的“簡(jiǎn)單”方案:KEYS + DEL
最初,我嘗試用以下命令批量刪除匹配模式的鍵:
redis-cli KEYS "user:*" | xargs redis-cli DEL
看起來(lái)簡(jiǎn)單高效,但問(wèn)題很快出現(xiàn)了:
- 問(wèn)題現(xiàn)象*:
- Redis服務(wù)器CPU飆升至100%
- 客戶端請(qǐng)求超時(shí),業(yè)務(wù)接口大面積報(bào)錯(cuò)
- Redis日志顯示“BUSY”警告
- 原因分析*:
KEYS命令是阻塞式的,它會(huì)遍歷整個(gè)鍵空間(時(shí)間復(fù)雜度O(n)),當(dāng)鍵數(shù)量達(dá)到百萬(wàn)級(jí)時(shí),執(zhí)行時(shí)間可能長(zhǎng)達(dá)數(shù)秒甚至更久。DEL命令也是阻塞式的,刪除大量鍵時(shí)會(huì)占用大量CPU和內(nèi)存資源。- 兩者組合會(huì)導(dǎo)致Redis主線程長(zhǎng)時(shí)間無(wú)法處理其他請(qǐng)求,引發(fā)服務(wù)不可用。
2. 改進(jìn)方案:SCAN + DEL
為了避免KEYS的阻塞問(wèn)題,我改用SCAN命令迭代刪除:
redis-cli --scan --pattern "user:*" | xargs redis-cli DEL
- 改進(jìn)點(diǎn)*:
SCAN是非阻塞的,通過(guò)游標(biāo)分批返回鍵,避免一次性遍歷所有鍵。- 減少了對(duì)主線程的長(zhǎng)時(shí)間占用。
- 新問(wèn)題*:
DEL仍然是同步操作,刪除大量鍵時(shí)仍可能引發(fā)短時(shí)阻塞。- 如果鍵數(shù)量極大(例如千萬(wàn)級(jí)),刪除時(shí)間可能仍然無(wú)法接受。
3. 進(jìn)一步優(yōu)化:SCAN + UNLINK
Redis 4.0引入了UNLINK命令,它是DEL的異步版本:
redis-cli --scan --pattern "user:*" | xargs redis-cli UNLINK
- 優(yōu)勢(shì)*:
UNLINK不會(huì)立即釋放內(nèi)存,而是將鍵標(biāo)記為刪除,由后臺(tái)線程異步回收內(nèi)存。- 避免了主線程的阻塞,對(duì)性能影響極小。
- 注意事項(xiàng)*:
- 內(nèi)存不會(huì)立即釋放,如果短時(shí)間內(nèi)需要大量?jī)?nèi)存,可能會(huì)導(dǎo)致內(nèi)存不足。
- 需要監(jiān)控Redis的內(nèi)存碎片率(
mem_fragmentation_ratio),適時(shí)執(zhí)行MEMORY PURGE。
4. 終極方案:Lua腳本 + 分批刪除
對(duì)于超大規(guī)模數(shù)據(jù)的刪除(例如億級(jí)鍵),即使UNLINK也可能不夠高效。此時(shí)可以結(jié)合Lua腳本和分批刪除:
local cursor = 0
local batch_size = 5000
repeat
local reply = redis.call("SCAN", cursor, "MATCH", ARGV[1], "COUNT", batch_size)
cursor = tonumber(reply[1])
local keys = reply[2]
if #keys > 0 then
redis.call("UNLINK", unpack(keys))
end
until cursor == 0
- 優(yōu)勢(shì)*:
- 通過(guò)
COUNT參數(shù)控制每批處理的鍵數(shù)量,避免單次操作壓力過(guò)大。 - 減少網(wǎng)絡(luò)往返開(kāi)銷(相比多次執(zhí)行
SCAN和UNLINK)。
深入探討:Redis刪除操作的底層機(jī)制
1.DELvsUNLINK
DEL:同步刪除鍵,立即釋放內(nèi)存。時(shí)間復(fù)雜度為O(1)(單鍵)或O(n)(多鍵)。UNLINK:異步刪除鍵,僅將鍵從鍵空間中移除,內(nèi)存由后臺(tái)線程回收。時(shí)間復(fù)雜度與DEL相同,但對(duì)主線程無(wú)阻塞。
2. Redis的單線程模型
Redis采用單線程處理命令,因此任何長(zhǎng)時(shí)間運(yùn)行的命令(如KEYS、大批量DEL)都會(huì)阻塞其他請(qǐng)求。異步命令(如UNLINK)是解決這一問(wèn)題的關(guān)鍵。
3. 內(nèi)存回收與碎片整理
異步刪除可能導(dǎo)致內(nèi)存碎片問(wèn)題??梢酝ㄟ^(guò)以下方式優(yōu)化:
- 定期執(zhí)行
MEMORY PURGE(Redis 4.0+)。 - 啟用
activedefrag配置(Redis 4.0+)。
總結(jié)與最佳實(shí)踐
避免踩坑的黃金法則
- 禁止在生產(chǎn)環(huán)境使用KEYS命令:改用SCAN迭代遍歷。
- 優(yōu)先使用UNLINK而非DEL:尤其是刪除大量鍵時(shí)。
- 分批刪除:通過(guò)COUNT參數(shù)控制每批處理的鍵數(shù)量。
- 監(jiān)控內(nèi)存和性能:關(guān)注mem_fragmentation_ratio和Redis的延遲指標(biāo)。
最終建議的批量刪除命令
redis-cli --scan --pattern "user:*" --count 1000 | xargs -n 1000 redis-cli UNLINK
--count 1000:每批掃描1000個(gè)鍵。xargs -n 1000:每批刪除1000個(gè)鍵,避免參數(shù)過(guò)長(zhǎng)。
通過(guò)這次踩坑經(jīng)歷,我深刻認(rèn)識(shí)到:在分布式系統(tǒng)中,即使是看似簡(jiǎn)單的操作(如刪除數(shù)據(jù)),也可能隱藏著巨大的風(fēng)險(xiǎn)。只有深入理解底層原理,才能設(shè)計(jì)出穩(wěn)健可靠的解決方案。希望本文能幫助你在未來(lái)的Redis運(yùn)維中少走彎路!
到此這篇關(guān)于淺談Redis批量刪除的大坑的文章就介紹到這了,更多相關(guān)Redis 批量刪除坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis 通過(guò) RDB 方式進(jìn)行數(shù)據(jù)備份與還原的方法
這篇文章主要介紹了Redis 通過(guò) RDB 方式進(jìn)行數(shù)據(jù)備份與還原,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
阿里云官方Redis開(kāi)發(fā)規(guī)范總結(jié)
本文主要介紹了阿里云官方Redis開(kāi)發(fā)規(guī)范總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

