日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

原本以為自己對redis命令還蠻熟悉的,各種數據模型各種基于redis的騷操作。但是最近在使用redis的scan的命令式卻踩了一個坑,頓時發覺自己原來對redis的游標理解的很有限。所以記錄下這個踩坑的過程,背景如下:

公司因為redis服務器內存吃緊,需要刪除一些無用的沒有設置過期時間的key。大概有500多w的key。雖然key的數目聽起來挺嚇人。但是自己玩redis也有年頭了,這種事還不是手到擒來?

當時想了下,具體方案是通過lua腳本來過濾出500w的key。然后進行刪除動作。lua腳本在redis server上執行,執行速度快,執行一批只需要和redis server建立一次連接。篩選出來key,然后一次刪1w。然后通過shell腳本循環個500次就能刪完所有的。以前通過lua腳本做過類似批量更新的操作,3w一次也是秒級的。基本不會造成redis的阻塞。這樣算起來,10分鐘就能搞定500w的key。

然后,我就開始直接寫lua腳本。首先是篩選。

用過redis的人,肯定知道redis是單線程作業的,肯定不能用keys命令來篩選,因為keys命令會一次性進行全盤搜索,會造成redis的阻塞,從而會影響正常業務的命令執行。

500w數據量的key,只能增量迭代來進行。redis提供了scan命令,就是用于增量迭代的。這個命令可以每次返回少量的元素,所以這個命令十分適合用來處理大的數據集的迭代,可以用于生產環境。

Redis千萬不要亂用scan命令,不然會挨打的

 

scan命令會返回一個數組,第一項為游標的位置,第二項是key的列表。如果游標到達了末尾,第一項會返回0。

2

所以我寫的第一版的lua腳本如下:

local c = 0
local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000)
c = tonumber(resp[1])
local dataList = resp[2]

for i=1,#dataList do
    local d = dataList[i]
    local ttl = redis.call('TTL',d)
    if ttl == -1 then
        redis.call('DEL',d)
    end
end

if c==0 then
  return 'all finished'
else
  return 'end'
end

在本地的測試redis環境中,通過執行以下命令mock了20w的測試數據:

eval "for i = 1, 200000 do redis.call('SET','authToken_' .. i,i) end" 0

然后執行script load命令上傳lua腳本得到SHA值,然后執行evalsha去執行得到的SHA值來運行。具體過程如下:

Redis千萬不要亂用scan命令,不然會挨打的

 

我每刪1w數據,執行下dbsize(因為這是我本地的redis,里面只有mock的數據,dbsize也就等同于這個前綴key的數量了)。

奇怪的是,前面幾行都是正常的。但是到了第三次的時候,dbsize變成了16999,多刪了1個,我也沒太在意,但是最后在dbsize還剩下124204個的時候,數量就不動了。之后無論再執行多少遍,數量還依舊是124204個。

隨即我直接運行scan命令:

Redis千萬不要亂用scan命令,不然會挨打的

 

發現游標雖然沒有到達末尾,但是key的列表卻是空的。

這個結果讓我懵逼了一段時間。我仔細檢查了lua腳本,沒有問題啊。難道是redis的scan命令有bug?難道我理解的有問題?

我再去翻看redis的命令文檔對count選項的解釋:

Redis千萬不要亂用scan命令,不然會挨打的

 

經過詳細研讀,發現count選項所指定的返回數量還不是一定的,雖然知道可能是count的問題,但無奈文檔的解釋實在難以很通俗的理解,依舊不知道具體問題在哪

3

后來經過某個小伙伴的提示,看到了另外一篇對于scan命令count選項通俗的解釋:

Redis千萬不要亂用scan命令,不然會挨打的

 

看完之后恍然大悟。原來count選項后面跟的數字并不是意味著每次返回的元素數量,而是scan命令每次遍歷字典槽的數量

我scan執行的時候每一次都是從游標0的位置開始遍歷,而并不是每一個字典槽里都存放著我所需要篩選的數據,這就造成了我最后的一個現象:雖然我count后面跟的是10000,但是實際redis從開頭往下遍歷了10000個字典槽后,發現沒有數據槽存放著我所需要的數據。所以我最后的dbsize數量永遠停留在了124204個。

所以在使用scan命令的時候,如果需要迭代的遍歷,需要每次調用都需要使用上一次這個調用返回的游標作為該次調用的游標參數,以此來延續之前的迭代過程。

至此,心中的疑惑就此解開,改了一版lua:

local c = tonumber(ARGV[1])
local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000)
c = tonumber(resp[1])
local dataList = resp[2]

for i=1,#dataList do
    local d = dataList[i]
    local ttl = redis.call('TTL',d)
    if ttl == -1 then
        redis.call('DEL',d)
    end
end

return c

在本地上傳后執行:

Redis千萬不要亂用scan命令,不然會挨打的

 


Redis千萬不要亂用scan命令,不然會挨打的

 

可以看到,scan命令沒法完全保證每次篩選的數量完全等同于給定的count,但是整個迭代卻很好的延續下去了。最后也得到了游標返回0,也就是到了末尾。至此,測試數據20w被全部刪完。

這段lua只要在套上shell進行循環就可以直接在生產上跑了。經過估算大概在12分鐘左右能刪除掉500w的數據。

知其然,知其所以然。雖然scan命令以前也曾玩過。但是的確不知道其中的細節。況且文檔的翻譯也不是那么的準確,以至于自己在面對錯誤的結果時整整浪費了近1個多小時的時間。記錄下來,加深理解。

作者:菠菜東

來源:https://www.cnblogs.com/bryan31/p/13338969.html

分享到:
標簽:命令 Redis scan
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定