天天看點

談談使用Redis緩存時高效的批量删除的幾種方案

作者:Java小蟲
談談使用Redis緩存時高效的批量删除的幾種方案

前因後果

之前我們的服務,在上線的時候發現有一些大Key的使用不是很規範,特别是沒有設定過期時間,是以導緻redis中記憶體的資料越來越多,目前Redis節點的記憶體已經快撐不住了。是以根據緩存鍵的規則去批量删除這些資料,比較常見的就是按字首去删除。

現在由于不得以為的原因要删除這幾百個Key-Value的資料,這個時候我們肯定就要把緩存鍵全部删除掉。一般情況下在Redis中是可以很容易去實作的。但是如果在不阻塞業務的前提下,并且以高效的方式進行清理記憶體資料。就需要好好想想辦法了。

批量删除redis資料方法

利用的是Linux的xargs指令

我們可以通過redis-cli的模式,進行通路之後登入到了Redis-Server服務,由于是必須要使用Linux的xargs指令,是以必須要連帶指令在Linux環境,而不能提前通過redis-cli進行登入到redis-server服務。否則會報錯說xargs無效。

redis-cli -h [ip] -p [port ]  -a  [password]  keys "prefix*" | xargs redis-cli -h 127.0.0.1 -p 6379 -a '123' del
複制代碼           

上面的指令主要由三部分連接配接組成:

  • redis-cli -h [ip] -p [port ] -a [password]:主要需要用于登入到redis-cli的隻處理操作。
  • keys "prefix*":随後主要是通過redis-cli的指令進行 keys指令進行比對某字首相關的資料集合。
  • | xargs redis-cli -h [ip] -p [port ] -a [password] del:主要是通過管道符進行連接配接,之後再進行連接配接redis-server服務,之後進行将之前的參數傳入到xargs之後,作為del的參數進行執行删除操作。

xargs指令

xargs:是一條Unix和類Unix作業系統的常用指令;它的作用是将參數清單轉換成小塊分段傳遞給其他指令,以避免參數清單過長的問題。可單獨使用,也可使用管道符、重定位符等與其他指令配合使用。

xargs [ -p ] [ -t] [ -e[ EOFString ] ] [ -EEOFString ] [ -i[ ReplaceString ] ] [ -IReplaceString ] [ -l [ Number ] ] [ -L Number ] [ -n Number [ -x ] ] [ -s Size ] [ Command [ Argument ... ] ]
複制代碼           

指令格式

xargs:一般是和管道一起使用。

somecommand |xargs -item  command
複制代碼           

參數:

  • -a file 從檔案中讀入作為 stdin
  • -e flag ,注意有的時候可能會是-E,flag必須是一個以空格分隔的标志,當xargs分析到含有flag這個标志的時候就停止。
  • -p 當每次執行一個argument的時候詢問一次使用者。
  • -n num 後面加次數,表示指令在執行的時候一次用的argument的個數,預設是用所有的。
  • -t 表示先列印指令,然後再執行。
  • -i 或者是-I,這得看linux支援了,将xargs的每項名稱,一般是一行一行指派給 {},可以用 {} 代替。
  • -r no-run-if-empty 當xargs的輸入為空的時候則停止xargs,不用再去執行了。
  • -s num 指令行的最大字元數,指的是 xargs 後面那個指令的最大指令行字元數。
  • -L num 從标準輸入一次讀取 num 行送給 command 指令。
  • -l 同 -L。
  • -d delim 分隔符,預設的xargs分隔符是回車,argument的分隔符是空格,這裡修改的是xargs的分隔符。
  • -x exit的意思,主要是配合-s使用。。
  • -P 修改最大的程序數,預設是1,為0時候為as many as it can ,這個例子我沒有想到,應該平時都用不到的吧。

使用Lua腳本删除百萬/千萬級的key

如果以上xargs方法删除不了的,或者執行xargs指令報錯的。那麼可以使用lua腳本,redis有内置的lua解釋器。在lua腳本中使用scan掃描key,并依次删除,當删除數量達到1萬時,腳本直接傳回,完成本次調用,如果删除的key數量大于0,就循環調用腳本進行删除。

Warning: a NUL character occurred in the input.  It cannot be passed through in the argument list.  Did you mean to use the --null option?
複制代碼           

Lua腳本是什麼?

Lua是一種輕量小巧的腳本語言,用标準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程式中,進而為應用程式提供靈活的擴充和定制功能。其設計目的是為了嵌入應用程式中,進而為應用程式提供靈活的擴充和定制功能。

Lua腳本的指令格式

有興趣的小夥伴,可以參考:redis.cn/commands/ev…

EVAL script numkeys key [key …] arg [arg …]
複制代碼           
  • script:待執行的腳本檔案
  • numkeys:key的個數

Lua腳本執行參數

  • [key …]:對應的key,可以是一個,可以是多個
  • [arg …]:與key對應的值,可以是一個,可以是多個

Lua擷取傳參資料

Lua的下表索引是從1開始的,key的擷取方式,KEYS[下标索引],如KEYS[1],取第一個值,值的擷取,ARGV[1]

示例

eval “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 2 key1 key2 first second
複制代碼           

Lua腳本的案例(keys)

  1. 擷取傳入的需要批量删除的key的字首
  2. 記住 lua的下标索引是從1開始 不是0 不是0 不是0
local key = KEYS[1]
if( key ~= nil) then
        --這裡通過keys查詢出所有符合條件的資料
        local dataList = redis.call('keys',KEYS[1])
        --判斷是否找到資料
        if(dataList ~= nil) then
                --循環删除
                for i=1,#dataList,1 do
                        redis.call('del',dataList[i])
                end
                --傳回删除的行數
                return #dataList
        else
                return 0
        end
else
        return 0
end
複制代碼           

推薦使用scan擷取資料删除,我們知道redis是一個單線程的,當我們庫裡面存在大量資料的時候,使用keys * 的方式比對資料的時候,可能需要好幾秒才能處理完,那麼在這個幾秒的時間裡是處于線程阻塞的,其他所有的redis操作都是處于等待狀态,這樣對系統的可用性是有影響的,是以,這裡使用scan的方式比對資料。

scan介紹

SCAN cursor [MATCH pattern] [COUNT count]
複制代碼           

SCAN 指令是一個基于遊标的疊代器(cursor based iterator): SCAN 指令每次被調用之後, 都會向使用者傳回一個新的遊标, 使用者在下次疊代時需要使用這個新遊标作為 SCAN 命 令的遊标參數, 以此來延續之前的疊代過程。

Lua腳本的案例(scan)

local limitSize = tonumber(ARGV[1]) -- 最多删除多少個key
local batchSize = limitSize -- scan一次最多掃描多少個key
if (batchSize > 10000) then -- 一次掃描不能超過1w條
    batchSize = 10000
end
local function scan(key)
    local cursor = 0
    local keynum = 0
    repeat
        local res = redis.call("scan", cursor, "match", key, 'COUNT', batchSize)
        if (res ~= nil and #res >= 0) then
            redis.replicate_commands()
            cursor = tonumber(res[1])
            local ks = res[2]
            local size = #ks
            for i=1,size,1 do
                redis.call("del", tostring(ks[i]))
                keynum = keynum + 1
                if (keynum >= limitSize) then -- 已經删除了指定數量的key, 傳回
                  return keynum
                end
            end
        end
    until (cursor <= 0)
    return keynum
end
local total = scan(KEYS[1])
return total
複制代碼           

當 SCAN 指令的遊标參數被設定為 0 時, 伺服器将開始一次新的疊代, 而當伺服器向使用者傳回值為 0 的遊标時, 表示疊代已結束。

通俗點了解就是,基于遊标的疊代器redis會慢慢一次次的将資料傳回回來,進而防止線程阻塞。

此外還有一個小貼士就是可以使用UNLINK删除,差別于del的是這個是異步執行的,這條指令要版本大于4.0.0 小于4.0.0就使用del

redis.call("UNLINK",key)
複制代碼           

原文連結:https://juejin.cn/post/7190954379535450167

繼續閱讀