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.  
    1) "four"
  11.  
    2) "three"
  12.  
    3) "one"
  13.  
    4) "two"
  14.  
    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.  
    2) ( empty list or set)
  10.  
    redis 127.0.0.1:6379> scan 80 MATCH *11*
  11.  
    1) "176"
  12.  
    2) ( empty list or set)
  13.  
    redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
  14.  
    1) "0"
  15.  
    2) 1) " key:611"
  16.  
    2) " key:711"
  17.  
    3) " key:118"
  18.  
    4) " key:117"
  19.  
    5) " key:311"
  20.  
    6) " key:112"
  21.  
    7) " key:111"
  22.  
    8) " key:110"
  23.  
    9) " key:113"
  24.  
    10) " key:211"
  25.  
    11) " key:411"
  26.  
    12) " key:115"
  27.  
    13) " key:116"
  28.  
    14) " key:114"
  29.  
    15) " key:119"
  30.  
    16) " key:811"
  31.  
    17) " key:511"
  32.  
    18) " key:11"
  33.  
    redis 127.0.0.1:6379>
可以看出,以上的大部分迭代都不返回任何元素。在最后一次迭代, 我們通過將 COUNT 選項的參數設置為 1000 , 強制命令為本次迭代掃描更多元素, 從而使得命令返回的元素也變多了。
 
基於SCAN的這種安全性,建議大家在生產環境都使用SCAN命令來代替KEYS,不過注意,該命令是在2.8.0版本之后加入的,如果你的Redis低於這個版本,則需要升級Redis。
 
 
下面用PHP代碼演示SCAN命令的使用:
 
  1.  
    <?php
  2.  
     
  3.  
    $redis = new Redis();
  4.  
     
  5.  
    $redis->connect( '127.0.0.1', 6379);
  6.  
     
  7.  
     
  8.  
    /* 設置遍歷的特性為不重復查找,該情況下擴展只會scan一次,所以可能會返回空集合 */
  9.  
    $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY);
  10.  
     
  11.  
    $it = NULL;
  12.  
    $pattern = '*';
  13.  
    $count = 50; // 每次遍歷50條,注意是遍歷50條,遍歷出來的50條key還要去匹配你的模式,所以並不等於就能夠取出50條key
  14.  
     
  15.  
    do
  16.  
    {
  17.  
    $keysArr = $redis->scan($it, $pattern, $count);
  18.  
     
  19.  
    if ($keysArr)
  20.  
    {
  21.  
    foreach ($keysArr as $key)
  22.  
    {
  23.  
    echo $key . "\n";
  24.  
    }
  25.  
    }
  26.  
     
  27.  
    } while ($it > 0); //每次調用 Scan會自動改變 $it 值,當$it = 0時 這次遍歷結束 退出循環
  28.  
     
  29.  
     
  30.  
    echo '---------------------------------------------------------------------------------' . "\n";
  31.  
     
  32.  
     
  33.  
    /* 設置擴展在一次scan沒有查找出記錄時 進行重復的scan 直到查詢出結果或者遍歷結束為止 */
  34.  
    $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
  35.  
     
  36.  
    $it = NULL;
  37.  
    $pattern = '*';
  38.  
    $count = 50; // 每次遍歷50條,注意是遍歷50條,遍歷出來的50條key還要去匹配你的模式,所以並不等於就能夠取出50條key
  39.  
     
  40.  
    //這種用法下我們只需要簡單判斷返回結果是否為空即可, 如果為空說明遍歷結束
  41.  
    while ($keysArr = $redis->scan($it, $pattern, $count))
  42.  
    {
  43.  
    foreach ($keysArr as $key)
  44.  
    {
  45.  
    echo $key . "\n";
  46.  
    }
  47.  
    }
 
執行結果:

  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.  
    ---------------------------------------------------------------------------------
  12.  
    bm
  13.  
    bm2
  14.  
    h1
  15.  
    name
  16.  
    bit
  17.  
    bm1
  18.  
    places
  19.  
    cities
  20.  
    hhl
 
注意:如果php執行報錯 請升級到較新版本的Redis擴展
 
更多請參考:
 
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM