Redis 中 Keys 與 Scan 的使用

finecho發表於2019-03-22

@這是小豪的第十二篇文章

上篇文章 釋出後,在公眾號下方的留言中看到了這麼一句話 "keys 操作有點恐怖哈",當時就在想為啥恐怖,有點不知所以然,也沒過多的去管他,直到今天在社群文章中又看到了人家的提醒 "keys 操作是不行的".... 哈哈,需要去了解為什麼啦!

keys 是什麼

  • keys - KEYS PATTERN

    用於查詢所有符合給定模式 pattern 的 key

為什麼會用到 keys

$redis->keys('login:201903*')
$redis->bitop('AND',  'monthActivities'', $redis->keys('login:201903*'));

echo "連續一個月簽到使用者數量:" . $redis->bitCount('monthActivities');

這是上篇文章中用到 keys 的地方,當時是為了統計連續一個月簽到使用者數量,通過 keys('login:201903') 獲取到三月所有 key ,然後加以聚合統計。

keys 使用會造成的後果

大家知道 Redis 是單執行緒程式,是按照順序執行指令的,如果說我們現在正在執行 keys 命令,那麼其它指令必須等到當前的 keys 指令執行完了才可以繼續,再加上 keys 操作是遍歷演算法,複雜度是 O(n),乍一想就知道問題所在了,當例項中資料量過大的時候,Redis 服務可能會卡頓,其餘指令可能會延時甚至超時報錯....

再者 keys 中沒有 offset、limit 引數,如果說滿足查詢條件的 keys 特別多,那就有點尷尬了,哈哈。

所以說官方的建議是:生產環境遮蔽掉 keys 命令。

替代方案 scan

說了那麼多,這也不行那也不好的,究竟怎麼辦呢,Redis 為了解決這個問題,它在 2.8 版本中加入了指令:scan。

好,那我們現在就來看一下這個命令:

  • scan - cursor [MATCH pattern] [COUNT count]

    用於迭代當前資料庫中的資料庫鍵

相比 keys ,我們來看一下 scan 的特點:

  • 複雜度雖然也是 O(n),但是它是通過遊標分步進行的,不會阻塞執行緒;
  • 提供 limit 引數,可以控制每次返回結果的最大條數,limit 只是對增量式迭代命令的一種提示 (hint),返回的結果可多可少;
  • 同 keys 一樣,它也提供模式匹配功能;
  • 伺服器不需要為遊標儲存狀態,遊標的唯一狀態就是 scan 返回給客戶端的遊標整數;
  • 返回的結果可能會有重複,需要客戶端去重複,這點非常重要;
  • 遍歷的過程中如果有資料修改,改動後的資料能不能遍歷到是不確定的;
  • 單次返回的結果是空的並不意味著遍歷結束,而要看返回的遊標值是否為零

現在我們來實踐一下:

> keys 201903*
    1) "login:20190311"
    2) "login:20190312"
    3) "login:20190313"
> scan 0 match login:201903*
    1) "0"
    2) 1) "login:20190313"
       2) "login:20190311"
       3) "login:20190312"
> scan 0 match login:201903* count 2
    1) "5"
    2) 1) "login:20190313"
       2) "login:20190311"

看到這裡估計有點蒙圈,scan 0 是個啥意思,為啥下面的結果中第一個資料有的為 0 ,有的為 5。

其實是這樣的,當我們第一次遍歷查詢時,cursor 值為 0,如果說資料全部查詢完畢,那麼返回結果的第一個資料就為 0 表示查詢完畢,如果說返回的不為 0 ,那麼就需要將這個資料作為下一次遍歷的 cursor,也就是現在的 scan 5,一直遍歷到返回的第一個資料為 0 為止。第二個引數一目瞭然哈,就是控制返回數量,那究竟是不是這樣呢,我們來看一下:

> scan 0
    1) "0"
    2) 1) "age"
       2) "login:20190313"
       3) "names"
       4) "login:20190311"
       5) "name"
       6) "login:20190312"
       7) "sex"
       8) "ages"
> scan 0 match login:201903* count 2
    1) "1"
    2) 1) "login:20190313"

現在問題就來了,不是 count 2 嗎。怎麼查詢出來的資料只有 1 條,是不是壞了?不是滴,哈哈。這是因為這個 count 不是限定返回結果的數量,而是限定伺服器單次遍歷的字典槽位數量(約等於),所以就明白了吧。它查出來是空的也不要擔心,只要結果的第一個資料不為 0 就繼續遍歷迴圈下去。

說到這裡,大家應該對,scan 有了大致的瞭解了吧,那我們就回歸到之前的問題,統計該如何修改呢,大家來看一下基本的查詢程式碼:

\dd($redis->scan(0, 'match', 'login:201903*', 'count', 1000));

來看一下結果:
file

就這樣就 ok 啦,至於統計程式碼該如何改,我就沒寫出來噠,大家可以自己思考一下怎麼去寫,然後貼在評論區,哈哈。

結束語

至此 keysscan 的講解就結束噠,有什麼不明白的或者有錯誤的地方,還望大家在評論區留言。

相關連結:

finecho # Lhao

相關文章