背景
原因是生產環境報錯
MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error..
翻譯一下就是在進行rdb備份的時候出現異常,導致上層應用不能正常訪問redis,可以通過把stop-writes-on-bgsave-error設置為no解決,我的理解是no就是忽略了rdb備份的這個異常,並沒有根本性的解決問題.
查看redis內存情況之后發現redis已經存了4.7g的數據,遠超過預期,依次排查了redis的內存碎片率過高等可能的問題,沒有發現異常.
最后定位到是某一個組件為了做去重處理,把之前接收的數據id都往redis放了一份,大致估了下有1億多條,於是就考慮把較早的一批數據刪掉,在網上找了點資料,綜合整理寫了個lua腳本把問題解決了.
解決方法
根據生產環境抽象了一下需要刪除的數據結構,通過以下腳本構造模擬數據
add.lua
redis.call('select','8')
local start=10000
while (start<20000)
do
redis.call("hset","testAuto",start,"test value")
start=start+1
end
將以上內容保存為文本,並以.lua為擴展名,執行方法如下
redis-cli -a "password" --eval add.lua
腳本跑完之后,redis中數據如下:

這是以個key=自增數字,value="test value"的集合,根據現場情況,需要刪除key小於某一個值的所有值
以下lua腳本是刪除所有key小於15000的數據,共計5000條
del.lua
--切換倒目標數據庫
redis.call('select',8)
redis.replicate_commands()
--需要刪除的hash集合名稱
local mapName="testAuto"
--小於這個id的值都會被刪除
local lastThumbnailId=15000
local myCursor=0
while(true)
do
--日志輸出到redis的日志文件中
redis.log(redis.LOG_WARNING,"myCursor ".. myCursor)
--hscan有一個小問題,如果哈希表中的數據量不夠多(據網友說小於513,具體沒有驗證)的時候,count會不生效,直接返回所有的key和value
local iterator=redis.call('hscan',mapName,myCursor,'count',1000)
for index,keyOrValue in pairs(iterator) do
redis.log(redis.LOG_WARNING,"index "..index)
--redis.log(redis.LOG_WARNING,"v"..v)
if (index==1)
then
myCursor=tonumber(keyOrValue)
end
if (index==2)
then
for subIndex,subKeyOrValue in pairs(keyOrValue) do
--redis.log(redis.LOG_WARNING,"subIndex "..subIndex)
if (type(tonumber(subKeyOrValue)) == "number" and tonumber(subKeyOrValue)<lastThumbnailId )
then
--redis.log(redis.LOG_WARNING,"subKeyOrValue "..subKeyOrValue)
redis.call('hdel',mapName,subKeyOrValue)
end
end
end
end
--如果所有的數據都跑完了,那么寫一次cursor會重新變成0
if(myCursor==0)
then
break
end
end
簡單說一下這個pairs函數的用法,對照上面代碼的變量我標注了一下

跑完del.lua腳本之后可以看到testAuto中數據只有5000條了,刪除成功

對於 redis.replicate_commands()這一行代碼也提一下,如果沒有會產生如下報錯
(error) ERR Error running script (call to f_13a8e8ef956cadf3b0b1c5a2c25337099126724b): @user_script:27: @user_script: 27: Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.
原因是hscan在redis中是屬於一個隨機性命令,這個命令的返回值是不固定的(相同的腳本執行多次可能會產生不同的結果),這種情況會對數據一致性造成破壞,
具體原因可以參考這篇文章redis4.0之Lua腳本新姿勢,非常詳細.
