天天看點

Redis周遊所有key的兩個指令 -- KEYS 和 SCAN

當我們需要周遊Redis所有key或者指定模式的key時,首先想到的是KEYS指令:

KEYS pattern           

官網對于KEYS指令有一個提示: KEYS 的速度非常快,例如,Redis在一個有1百萬個key的資料庫裡面執行一次查詢需要的時間是40毫秒 。但在一個大的資料庫中使用它仍然可能造成性能問題,如果你需要從一個資料集中查找特定的 

KEYS

, 你最好還是用 Redis 的集合結構 SETS 來代替。

KEYS指令使用很簡單。

  1. redis> MSET one 1 two 2 three 3 four 4
  2. OK
  3. redis> KEYS *o*
  4. 1) "four"
  5. 2) "one"
  6. 3) "two"
  7. redis> KEYS t??
  8. 1) "two"
  9. redis> KEYS *
  10. 2) "three"
  11. 3) "one"
  12. 4) "two"
  13. redis>

但由于KEYS指令一次性傳回所有比對的key,是以,當redis中的key非常多時,對于記憶體的消耗和redis伺服器都是一個隐患,

對于Redis 2.8以上版本給我們提供了一個更好的周遊key的指令 SCAN 該指令的基本格式:

SCAN cursor [MATCH pattern] [COUNT count]           

SCAN 每次執行都隻會傳回少量元素,是以可以用于生産環境,而不會出現像 KEYS 或者 SMEMBERS 指令帶來的可能會阻塞伺服器的問題。

SCAN指令是一個基于遊标的疊代器。這意味着指令每次被調用都需要使用上一次這個調用傳回的遊标作為該次調用的遊标參數,以此來延續之前的疊代過程

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

簡單的疊代示範:

  1. redis 127.0.0.1:6379> scan 0
  2. 1) "17"
  3. 2) 1) "key:12"
  4. 2) "key:8"
  5. 3) "key:4"
  6. 4) "key:14"
  7. 5) "key:16"
  8. 6) "key:17"
  9. 7) "key:15"
  10. 8) "key:10"
  11. 9) "key:3"
  12. 10) "key:7"
  13. 11) "key:1"
  14. redis 127.0.0.1:6379> scan 17
  15. 1) "0"
  16. 2) 1) "key:5"
  17. 2) "key:18"
  18. 3) "key:0"
  19. 4) "key:2"
  20. 5) "key:19"
  21. 6) "key:13"
  22. 7) "key:6"
  23. 8) "key:9"
  24. 9) "key:11"

在上面這個例子中, 第一次疊代使用 0 作為遊标, 表示開始一次新的疊代。第二次疊代使用的是第一次疊代時傳回的遊标 17 ,作為新的疊代參數 。

顯而易見,SCAN指令的傳回值 是一個包含兩個元素的數組, 第一個數組元素是用于進行下一次疊代的新遊标, 而第二個數組元素則又是一個數組, 這個數組中包含了所有被疊代的元素。

注意:傳回的遊标不一定是遞增的,可能後一次傳回的遊标比前一次的小。

在第二次調用 SCAN 指令時, 指令傳回了遊标 0 , 這表示疊代已經結束, 整個資料集已經被完整周遊過了。

full iteration :以 0 作為遊标開始一次新的疊代, 一直調用 SCAN 指令, 直到指令傳回遊标 0 , 我們稱這個過程為一次完整周遊。

SCAN增量式疊代指令并不保證每次執行都傳回某個給定數量的元素,甚至可能會傳回零個元素, 但隻要指令傳回的遊标不是 0 , 應用程式就不應該将疊代視作結束。

不過指令傳回的元素數量總是符合一定規則的, 對于一個大資料集來說, 增量式疊代指令每次最多可能會傳回數十個元素;而對于一個足夠小的資料集來說,可能會一次疊代傳回所有的key

COUNT選項

對于增量式疊代指令不保證每次疊代所傳回的元素數量,我們可以使用COUNT選項, 對指令的行為進行一定程度上的調整。COUNT 選項的作用就是讓使用者告知疊代指令, 在每次疊代中應該從資料集裡傳回多少元素。使用COUNT 選項對于對增量式疊代指令相當于一種提示, 大多數情況下這種提示都比較有效的控制了傳回值的數量。

注意:COUNT選項并不能嚴格控制傳回的key數量,隻能說是一個大緻的限制。并非每次疊代都要使用相同的 COUNT 值,使用者可以在每次疊代中按自己的需要随意改變 COUNT 值, 隻要記得将上次疊代傳回的遊标用到下次疊代裡面就可以了。

MATCH 選項

類似于KEYS 指令,增量式疊代指令通過給定 MATCH 參數的方式實作了通過提供一個 glob 風格的模式參數, 讓指令隻傳回和給定模式相比對的元素。

MATCH 選項對元素的模式比對工作是在指令從資料集中取出元素後和向用戶端傳回元素前的這段時間内進行的, 是以如果被疊代的資料集中隻有少量元素和模式相比對, 那麼疊代指令或許會在多次執行中都不傳回任何元素。

以下是這種情況的一個例子:

  1. redis 127.0.0.1:6379> scan 0 MATCH *11*
  2. 1) "288"
  3. 2) 1) "key:911"
  4. redis 127.0.0.1:6379> scan 288 MATCH *11*
  5. 1) "224"
  6. 2) (empty list or set)
  7. redis 127.0.0.1:6379> scan 224 MATCH *11*
  8. 1) "80"
  9. redis 127.0.0.1:6379> scan 80 MATCH *11*
  10. 1) "176"
  11. redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
  12. 2) 1) "key:611"
  13. 2) "key:711"
  14. 3) "key:118"
  15. 4) "key:117"
  16. 5) "key:311"
  17. 6) "key:112"
  18. 7) "key:111"
  19. 8) "key:110"
  20. 9) "key:113"
  21. 10) "key:211"
  22. 11) "key:411"
  23. 12) "key:115"
  24. 13) "key:116"
  25. 14) "key:114"
  26. 15) "key:119"
  27. 16) "key:811"
  28. 17) "key:511"
  29. 18) "key:11"
  30. redis 127.0.0.1:6379>

可以看出,以上的大部分疊代都不傳回任何元素。在最後一次疊代, 我們通過将 COUNT 選項的參數設定為 1000 , 強制指令為本次疊代掃描更多元素, 進而使得指令傳回的元素也變多了。

基于SCAN的這種安全性,建議大家在生産環境都使用SCAN指令來代替KEYS,不過注意,該指令是在2.8.0版本之後加入的,如果你的Redis低于這個版本,則需要更新Redis。

下面用PHP代碼示範SCAN指令的使用:

  1. <?php
  2. $redis = new Redis();
  3. $redis->connect('127.0.0.1', 6379);
  4. /* 設定周遊的特性為不重複查找,該情況下擴充隻會scan一次,是以可能會傳回空集合 */
  5. $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY);
  6. $it = NULL;
  7. $pattern = '*';
  8. $count = 50; // 每次周遊50條,注意是周遊50條,周遊出來的50條key還要去比對你的模式,是以并不等于就能夠取出50條key
  9. do
  10. {
  11. $keysArr = $redis->scan($it, $pattern, $count);
  12. if ($keysArr)
  13. {
  14. foreach ($keysArr as $key)
  15. {
  16. echo $key . "\n";
  17. }
  18. }
  19. } while ($it > 0); //每次調用 Scan會自動改變 $it 值,當$it = 0時 這次周遊結束 退出循環
  20. echo '---------------------------------------------------------------------------------' . "\n";
  21. /* 設定擴充在一次scan沒有查找出記錄時 進行重複的scan 直到查詢出結果或者周遊結束為止 */
  22. $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
  23. //這種用法下我們隻需要簡單判斷傳回結果是否為空即可, 如果為空說明周遊結束
  24. while ($keysArr = $redis->scan($it, $pattern, $count))
  25. foreach ($keysArr as $key)
  26. echo $key . "\n";
  27. }

執行結果:

  1. [root@localhost php]# /usr/local/php/bin/php scan.php
  2. bm
  3. bm2
  4. h1
  5. name
  6. bit
  7. bm1
  8. places
  9. cities
  10. hhl
  11. ---------------------------------------------------------------------------------

注意:如果php執行報錯 請更新到較新版本的Redis擴充

更多請參考:

http://www.redis.cn/commands/keys.html

http://www.redis.cn/commands/scan.html

https://github.com/phpredis/phpredis#scan