Redis模糊匹配的命令,爲什麼要用scan而不能用keys

        對於Redis服務器的維護,有時我們需要從成千上萬的key中,找出我們指定的key,也就是模糊匹配出來的key,redis提供了一個簡單粗暴的命令:keys,它可以用來列出所有滿足特定正則字符串規則的 key。

但是對於這個簡單粗暴的命令,要是不想被同事吊,生產環境就忘記有這個命令的存在,或者是這個命令在生產環境已經被老大給和諧了,原因想必大家也知道,這個指令沒有offset、limit 參數,一次性吐出所有滿足條件的key,萬一實例中有上百萬個key滿足條件,你就傻眼了,並且其他同事開發的模塊也在調用同一個Redis服務器的時候,他們想殺你的心都有了,keys算法是遍歷算法,複雜度是 O(n),如果實例中有千萬級以上的 key,這個指令就會導致 Redis 服務卡頓,所有讀寫Redis的其它的指令都會被延後甚至會超時報錯,因爲 Redis是單線程程序,順序執行所有指令,其它指令必須等到當前的 keys 指令執行完了纔可以繼續。這裏插個閒話,Redis也逃脫不了真香定理,要向多線程方向發展,Redis 6.0引入的最重大的改變就是多線程IO,對性能提升至少是一倍以上,等大家升級到6.0版本以上之後,也許keys命令的弊病就沒有了,不過還沒驗證。

先不說多線程版本的Redis,先來說一下單線程版本的Redis是怎麼解決這個遍歷key的問題,redis提供了另外一個命令,就是scan:

scan命令的特點如下:

1、複雜度和keys命令一樣,也是 O(n),但是它是通過遊標分步進行的,不會阻塞線程

2、提供limit參數,可以控制每次返回結果的最大條數,這裏是最大條數,而不是等於limit的條數,因爲是匹配查詢,是在limit的範圍內匹配查詢

3、返回的結果可能會有重複

4、遍歷的過程中如果有數據修改,改動後的數據能不能遍歷到是不確定的;

5、單次返回的結果是空的並不意味着遍歷結束,而要看返回的遊標值是否爲零;

scan的命令格式爲:

SCAN cursor [MATCH pattern] [COUNT count]

其中cursor是遊標位置,是整數值;pattern是遍歷key的正則表達式;count是遍歷元素的最大條數。

先寫一個程序向redis服務器寫入一萬個key:

然後用scan遍歷符合bingmayong88的元素:

大家都知道Java中HashMap的底層實現結構,是數組+鏈表的形式,在JDK1.7之後還加入了紅黑樹,在Redis當中,所有的key都存儲在一個很大的字典中,這個字典結構就是一維數組+二維鏈表的結構,scan指令返回的遊標就是第一維數組的位置索引,這個位置索引稱爲槽 (slot)。 如果不考慮字典的擴容縮容,直接按數組下標挨個遍歷就行了。limit 參數就表示需要遍歷的槽位數,之所以返回的結果可能多可能少,是因爲不是所有的槽位上都會掛接鏈表,有些槽 位可能是空的,還有些槽位上掛接的鏈表上的元素可能會有多個。每一次遍歷都會將 limit 數量的槽位上掛接的所有鏈表元素進行模式匹配過濾後,一次性返回給客戶端。

從圖中可以看出,scan 的遍歷順序非常特別。它不是從第一維數組的第 0 位一直遍歷到末尾,而是採用 了高位進位加法來遍歷。之所以使用這樣特殊的方式進行遍歷,是考慮到字典的擴容和縮容 時避免槽位的遍歷重複和遺漏。關於高位進位加法,大家有興趣的可以查閱資料瞭解一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章