/** * Key(鍵) * DEL,DUMP,EXISTS,EXPIRE,EXPIREAT,KEYS,MIGRATE,MOVE,OBJECT,PERSIST,PEXPIRE,PEXPIREAT,PTTL,RANDOMKEY,RENAME,RENAMENX,RESTORE,SORT,TTL,TYPE,SCAN */ public class KeyTest { private Jedis jedis; private static final String KEY = "key"; private static final String VALUE = "layman"; @Before public void setUp() { this.jedis = new Jedis(new JedisShardInfo("192.168.133.188", 6379)); } /** * DEL key [key ...] * 刪除給定的一個或多個 key 。 * 不存在的 key 會被忽略。 */ @Test public void DEL() { System.out.println(jedis.del(KEY, KEY + 0)); } /** * DUMP key * 序列化給定 key ,並返回被序列化的值,使用 RESTORE 命令可以將這個值反序列化為 Redis 鍵。 * 序列化生成的值有以下幾個特點: * 它帶有 64 位的校驗和,用於檢測錯誤, RESTORE 在進行反序列化之前會先檢查校驗和。 * 值的編碼格式和 RDB 文件保持一致。 * RDB 版本會被編碼在序列化值當中,如果因為 Redis 的版本不同造成 RDB 格式不兼容,那么 Redis 會拒絕對這個值進行反序列化操作。 * 序列化的值不包括任何生存時間信息。 */ @Test public void DUMP() { jedis.set(KEY, VALUE); System.out.println(jedis.get(KEY)); byte[] dump = jedis.dump(KEY); System.out.println(dump.length); System.out.println(jedis.get(KEY)); } /** * EXISTS key * 檢查給定 key 是否存在。 */ @Test public void EXISTS() { System.out.println(jedis.exists(KEY)); System.out.println(jedis.exists(KEY + 0)); } /** * EXPIRE key seconds * 為給定 key 設置生存時間,當 key 過期時(生存時間為 0 ),它會被自動刪除。 * 在 Redis 中,帶有生存時間的 key 被稱為『易失的』(volatile)。 * 生存時間可以通過使用 DEL 命令來刪除整個 key 來移除,或者被 SET 和 GETSET 命令覆寫(overwrite),這意味着, * 如果一個命令只是修改(alter)一個帶生存時間的 key 的值而不是用一個新的 key 值來代替(replace)它的話,那么生存時間不會被改變。 * 比如說,對一個 key 執行 INCR 命令,對一個列表進行 LPUSH 命令,或者對一個哈希表執行 HSET 命令,這類操作都不會修改 key 本身的生存時間。 * 另一方面,如果使用 RENAME 對一個 key 進行改名,那么改名后的 key 的生存時間和改名前一樣。 * RENAME 命令的另一種可能是,嘗試將一個帶生存時間的 key 改名成另一個帶生存時間的 another_key ,這時舊的 another_key (以及它的生存時間)會被刪除, * 然后舊的 key 會改名為 another_key ,因此,新的 another_key 的生存時間也和原本的 key 一樣。 * 使用 PERSIST 命令可以在不刪除 key 的情況下,移除 key 的生存時間,讓 key 重新成為一個『持久的』(persistent) key 。 * 更新生存時間 * 可以對一個已經帶有生存時間的 key 執行 EXPIRE 命令,新指定的生存時間會取代舊的生存時間。 * <p/> * PEXPIRE key milliseconds * 這個命令和 EXPIRE 命令的作用類似,但是它以毫秒為單位設置 key 的生存時間,而不像 EXPIRE 命令那樣,以秒為單位。 */ @Test public void EXPIRE() { System.out.println(jedis.expire(KEY, 600)); } /** * EXPIREAT key timestamp * EXPIREAT 的作用和 EXPIRE 類似,都用於為 key 設置生存時間。 * 不同在於 EXPIREAT 命令接受的時間參數是 UNIX 時間戳(unix timestamp)。 * <p/> * PEXPIREAT key milliseconds-timestamp * 這個命令和 EXPIREAT 命令類似,但它以毫秒為單位設置 key 的過期 unix 時間戳,而不是像 EXPIREAT 那樣,以秒為單位。 */ @Test public void EXPIREAT() { System.out.println(jedis.expireAt(KEY, new DateTime().plusMillis(30).getMillis())); } /** * KEYS pattern * 查找所有符合給定模式 pattern 的 key 。 * KEYS * 匹配數據庫中所有 key 。 * KEYS h?llo 匹配 hello , hallo 和 hxllo 等。 * KEYS h*llo 匹配 hllo 和 heeeeello 等。 * KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。 * 特殊符號用 \ 隔開 * KEYS 的速度非常快,但在一個大的數據庫中使用它仍然可能造成性能問題,如果你需要從一個數據集中查找特定的 key ,你最好還是用 Redis 的集合結構(set)來代替。 */ @Test public void KEYS() { System.out.println(jedis.keys("*")); } /** * MIGRATE host port key destination-db timeout [COPY] [REPLACE] * 將 key 原子性地從當前實例傳送到目標實例的指定數據庫上,一旦傳送成功, key 保證會出現在目標實例上,而當前實例上的 key 會被刪除。 * 這個命令是一個原子操作,它在執行的時候會阻塞進行遷移的兩個實例,直到以下任意結果發生:遷移成功,遷移失敗,等待超時。 * 命令的內部實現是這樣的:它在當前實例對給定 key 執行 DUMP 命令 ,將它序列化,然后傳送到目標實例,目標實例再使用 RESTORE 對數據進行反序列化, * 並將反序列化所得的數據添加到數據庫中;當前實例就像目標實例的客戶端那樣,只要看到 RESTORE 命令返回 OK ,它就會調用 DEL 刪除自己數據庫上的 key 。 * timeout 參數以毫秒為格式,指定當前實例和目標實例進行溝通的最大間隔時間。這說明操作並不一定要在 timeout 毫秒內完成,只是說數據傳送的時間不能超過這個 timeout 數。 * MIGRATE 命令需要在給定的時間規定內完成 IO 操作。如果在傳送數據時發生 IO 錯誤,或者達到了超時時間,那么命令會停止執行,並返回一個特殊的錯誤: IOERR 。 * 當 IOERR 出現時,有以下兩種可能: * key 可能存在於兩個實例 * key 可能只存在於當前實例 */ @Test public void MIGRATE() { /** * redis 127.0.0.1:6379> SET greeting "Hello from 6379 instance" OK redis 127.0.0.1:6379> MIGRATE 127.0.0.1 7777 greeting 0 1000 OK * */ } /** * MOVE key db * 將當前數據庫的 key 移動到給定的數據庫 db 當中。 * 如果當前數據庫(源數據庫)和給定數據庫(目標數據庫)有相同名字的給定 key ,或者 key 不存在於當前數據庫,那么 MOVE 沒有任何效果。 * 因此,也可以利用這一特性,將 MOVE 當作鎖(locking)原語(primitive)。 */ @Test public void MOVE() { System.out.println(jedis.move("set0", 1)); /** * 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> keys * 1) "set0" 127.0.0.1:6379[1]> SMEMBERS set0 1) "layman3" 2) "layman13" 3) "layman0" 4) "layman28" 5) "layman1" 6) "layman22" */ } /** * OBJECT subcommand [arguments [arguments]] * OBJECT 命令允許從內部察看給定 key 的 Redis 對象。 * 它通常用在除錯(debugging)或者了解為了節省空間而對 key 使用特殊編碼的情況。 * 當將Redis用作緩存程序時,你也可以通過 OBJECT 命令中的信息,決定 key 的驅逐策略(eviction policies)。 * OBJECT 命令有多個子命令: * OBJECT REFCOUNT <key> 返回給定 key 引用所儲存的值的次數。此命令主要用於除錯。 * OBJECT ENCODING <key> 返回給定 key 鎖儲存的值所使用的內部表示(representation)。 * OBJECT IDLETIME <key> 返回給定 key 自儲存以來的空閑時間(idle, 沒有被讀取也沒有被寫入),以秒為單位。 * 對象可以以多種方式編碼: * 字符串可以被編碼為 raw (一般字符串)或 int (為了節約內存,Redis 會將字符串表示的 64 位有符號整數編碼為整數來進行儲存)。 * 列表可以被編碼為 ziplist 或 linkedlist 。 ziplist 是為節約大小較小的列表空間而作的特殊表示。 * 集合可以被編碼為 intset 或者 hashtable 。 intset 是只儲存數字的小集合的特殊表示。 * 哈希表可以編碼為 zipmap 或者 hashtable 。 zipmap 是小哈希表的特殊表示。 * 有序集合可以被編碼為 ziplist 或者 skiplist 格式。 ziplist 用於表示小的有序集合,而 skiplist 則用於表示任何大小的有序集合。 */ @Test public void OBJECT() { /** * redis> SET game "COD" # 設置一個字符串 OK redis> OBJECT REFCOUNT game # 只有一個引用 (integer) 1 redis> OBJECT IDLETIME game # 等待一陣。。。然后查看空閑時間 (integer) 90 redis> GET game # 提取game, 讓它處於活躍(active)狀態 "COD" redis> OBJECT IDLETIME game # 不再處於空閑狀態 (integer) 0 redis> OBJECT ENCODING game # 字符串的編碼方式 "raw" redis> SET big-number 23102930128301091820391092019203810281029831092 # 非常長的數字會被編碼為字符串 OK redis> OBJECT ENCODING big-number "raw" redis> SET small-number 12345 # 而短的數字則會被編碼為整數 OK redis> OBJECT ENCODING small-number "int" */ } /** * PERSIST key * 移除給定 key 的生存時間,將這個 key 從『易失的』(帶生存時間 key )轉換成『持久的』(一個不帶生存時間、永不過期的 key )。 */ @Test public void PERSIST() { System.out.println(jedis.persist(KEY)); } /** * PTTL key * <p/> * 這個命令類似於 TTL 命令,但它以毫秒為單位返回 key 的剩余生存時間,而不是像 TTL 命令那樣,以秒為單位。 * <p/> * TTL key * 以秒為單位,返回給定 key 的剩余生存時間(TTL, time to live)。 */ @Test public void PTTL() { EXPIRE(); System.out.println(jedis.pttl(KEY)); } /** * RANDOMKEY * 從當前數據庫中隨機返回(不刪除)一個 key 。 */ @Test public void RANDOMKEY() { System.out.println(jedis.randomKey()); } /** * RENAME key newkey * 將 key 改名為 newkey 。 * 當 key 和 newkey 相同,或者 key 不存在時,返回一個錯誤。 * 當 newkey 已經存在時, RENAME 命令將覆蓋舊值。 */ @Test public void RENAME() { System.out.println(jedis.keys("*")); System.out.println(jedis.rename("sorted_set0", "sorted_set")); System.out.println(jedis.keys("*")); } /** * RENAMENX key newkey * 當且僅當 newkey 不存在時,將 key 改名為 newkey 。 * 當 key 不存在時,返回一個錯誤。 */ @Test public void RENAMENX() { System.out.println(jedis.keys("*")); System.out.println(jedis.renamenx(KEY, "age")); } /** * RESTORE key ttl serialized-value [REPLACE] * 反序列化給定的序列化值,並將它和給定的 key 關聯。 * 參數 ttl 以毫秒為單位為 key 設置生存時間;如果 ttl 為 0 ,那么不設置生存時間。 * RESTORE 在執行反序列化之前會先對序列化值的 RDB 版本和數據校驗和進行檢查,如果 RDB 版本不相同或者數據不完整的話,那么 RESTORE 會拒絕進行反序列化,並返回一個錯誤。 * 如果鍵 key 已經存在, 並且給定了 REPLACE 選項, 那么使用反序列化得出的值來代替鍵 key 原有的值; 相反地, 如果鍵 key 已經存在, 但是沒有給定 REPLACE 選項, 那么命令返回一個錯誤。 */ @Test public void RESTORE() { /** * # 創建一個鍵,作為 DUMP 命令的輸入 redis> SET greeting "hello, dumping world!" OK redis> DUMP greeting "\x00\x15hello, dumping world!\x06\x00E\xa0Z\x82\xd8r\xc1\xde" # 將序列化數據 RESTORE 到另一個鍵上面 redis> RESTORE greeting-again 0 "\x00\x15hello, dumping world!\x06\x00E\xa0Z\x82\xd8r\xc1\xde" OK redis> GET greeting-again "hello, dumping world!" # 在沒有給定 REPLACE 選項的情況下,再次嘗試反序列化到同一個鍵,失敗 redis> RESTORE greeting-again 0 "\x00\x15hello, dumping world!\x06\x00E\xa0Z\x82\xd8r\xc1\xde" (error) ERR Target key name is busy. # 給定 REPLACE 選項,對同一個鍵進行反序列化成功 redis> RESTORE greeting-again 0 "\x00\x15hello, dumping world!\x06\x00E\xa0Z\x82\xd8r\xc1\xde" REPLACE OK */ } /** * SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination] * 返回或保存給定列表、集合、有序集合 key 中經過排序的元素。 * 排序默認以數字作為對象,值被解釋為雙精度浮點數,然后進行比較。 * 一般 SORT 用法 * 最簡單的 SORT 使用方法是 SORT key 和 SORT key DESC : * SORT key 返回鍵值從小到大排序的結果。 * SORT key DESC 返回鍵值從大到小排序的結果。 */ @Test public void SORT() { /** 假設 today_cost 列表保存了今日的開銷金額, 那么可以用 SORT 命令對它進行排序: # 開銷金額列表 redis> LPUSH today_cost 30 1.5 10 8 (integer) 4 # 排序 redis> SORT today_cost 1) "1.5" 2) "8" 3) "10" 4) "30" # 逆序排序 redis 127.0.0.1:6379> SORT today_cost DESC 1) "30" 2) "10" 3) "8" 4) "1.5" 使用 ALPHA 修飾符對字符串進行排序 因為 SORT 命令默認排序對象為數字, 當需要對字符串進行排序時, 需要顯式地在 SORT 命令之后添加 ALPHA 修飾符: # 網址 redis> LPUSH website "www.reddit.com" (integer) 1 redis> LPUSH website "www.slashdot.com" (integer) 2 redis> LPUSH website "www.infoq.com" (integer) 3 # 默認(按數字)排序 redis> SORT website 1) "www.infoq.com" 2) "www.slashdot.com" 3) "www.reddit.com" # 按字符排序 redis> SORT website ALPHA 1) "www.infoq.com" 2) "www.reddit.com" 3) "www.slashdot.com" 如果系統正確地設置了 LC_COLLATE 環境變量的話,Redis能識別 UTF-8 編碼。 使用 LIMIT 修飾符限制返回結果 排序之后返回元素的數量可以通過 LIMIT 修飾符進行限制, 修飾符接受 offset 和 count 兩個參數: offset 指定要跳過的元素數量。 count 指定跳過 offset 個指定的元素之后,要返回多少個對象。 以下例子返回排序結果的前 5 個對象( offset 為 0 表示沒有元素被跳過)。 # 添加測試數據,列表值為 1 指 10 redis 127.0.0.1:6379> RPUSH rank 1 3 5 7 9 (integer) 5 redis 127.0.0.1:6379> RPUSH rank 2 4 6 8 10 (integer) 10 # 返回列表中最小的 5 個值 redis 127.0.0.1:6379> SORT rank LIMIT 0 5 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 可以組合使用多個修飾符。以下例子返回從大到小排序的前 5 個對象。 redis 127.0.0.1:6379> SORT rank LIMIT 0 5 DESC 1) "10" 2) "9" 3) "8" 4) "7" 5) "6" 使用外部 key 進行排序 可以使用外部 key 的數據作為權重,代替默認的直接對比鍵值的方式來進行排序。 假設現在有用戶數據如下: uid user_name_{uid} user_level_{uid} 1 admin 9999 2 jack 10 3 peter 25 4 mary 70 以下代碼將數據輸入到 Redis 中: # admin redis 127.0.0.1:6379> LPUSH uid 1 (integer) 1 redis 127.0.0.1:6379> SET user_name_1 admin OK redis 127.0.0.1:6379> SET user_level_1 9999 OK # jack redis 127.0.0.1:6379> LPUSH uid 2 (integer) 2 redis 127.0.0.1:6379> SET user_name_2 jack OK redis 127.0.0.1:6379> SET user_level_2 10 OK # peter redis 127.0.0.1:6379> LPUSH uid 3 (integer) 3 redis 127.0.0.1:6379> SET user_name_3 peter OK redis 127.0.0.1:6379> SET user_level_3 25 OK # mary redis 127.0.0.1:6379> LPUSH uid 4 (integer) 4 redis 127.0.0.1:6379> SET user_name_4 mary OK redis 127.0.0.1:6379> SET user_level_4 70 OK BY 選項 默認情況下, SORT uid 直接按 uid 中的值排序: redis 127.0.0.1:6379> SORT uid 1) "1" # admin 2) "2" # jack 3) "3" # peter 4) "4" # mary 通過使用 BY 選項,可以讓 uid 按其他鍵的元素來排序。 比如說, 以下代碼讓 uid 鍵按照 user_level_{uid} 的大小來排序: redis 127.0.0.1:6379> SORT uid BY user_level_* 1) "2" # jack , level = 10 2) "3" # peter, level = 25 3) "4" # mary, level = 70 4) "1" # admin, level = 9999 user_level_* 是一個占位符, 它先取出 uid 中的值, 然后再用這個值來查找相應的鍵。 比如在對 uid 列表進行排序時, 程序就會先取出 uid 的值 1 、 2 、 3 、 4 , 然后使用 user_level_1 、 user_level_2 、 user_level_3 和 user_level_4 的值作為排序 uid 的權重。 GET 選項 使用 GET 選項, 可以根據排序的結果來取出相應的鍵值。 比如說, 以下代碼先排序 uid , 再取出鍵 user_name_{uid} 的值: redis 127.0.0.1:6379> SORT uid GET user_name_* 1) "admin" 2) "jack" 3) "peter" 4) "mary" 組合使用 BY 和 GET 通過組合使用 BY 和 GET , 可以讓排序結果以更直觀的方式顯示出來。 比如說, 以下代碼先按 user_level_{uid} 來排序 uid 列表, 再取出相應的 user_name_{uid} 的值: redis 127.0.0.1:6379> SORT uid BY user_level_* GET user_name_* 1) "jack" # level = 10 2) "peter" # level = 25 3) "mary" # level = 70 4) "admin" # level = 9999 現在的排序結果要比只使用 SORT uid BY user_level_* 要直觀得多。 獲取多個外部鍵 可以同時使用多個 GET 選項, 獲取多個外部鍵的值。 以下代碼就按 uid 分別獲取 user_level_{uid} 和 user_name_{uid} : redis 127.0.0.1:6379> SORT uid GET user_level_* GET user_name_* 1) "9999" # level 2) "admin" # name 3) "10" 4) "jack" 5) "25" 6) "peter" 7) "70" 8) "mary" GET 有一個額外的參數規則,那就是 —— 可以用 # 獲取被排序鍵的值。 以下代碼就將 uid 的值、及其相應的 user_level_* 和 user_name_* 都返回為結果: redis 127.0.0.1:6379> SORT uid GET # GET user_level_* GET user_name_* 1) "1" # uid 2) "9999" # level 3) "admin" # name 4) "2" 5) "10" 6) "jack" 7) "3" 8) "25" 9) "peter" 10) "4" 11) "70" 12) "mary" 獲取外部鍵,但不進行排序 通過將一個不存在的鍵作為參數傳給 BY 選項, 可以讓 SORT 跳過排序操作, 直接返回結果: redis 127.0.0.1:6379> SORT uid BY not-exists-key 1) "4" 2) "3" 3) "2" 4) "1" 這種用法在單獨使用時,沒什么實際用處。 不過,通過將這種用法和 GET 選項配合, 就可以在不排序的情況下, 獲取多個外部鍵, 相當於執行一個整合的獲取操作(類似於 SQL 數據庫的 join 關鍵字)。 以下代碼演示了,如何在不引起排序的情況下,使用 SORT 、 BY 和 GET 獲取多個外部鍵: redis 127.0.0.1:6379> SORT uid BY not-exists-key GET # GET user_level_* GET user_name_* 1) "4" # id 2) "70" # level 3) "mary" # name 4) "3" 5) "25" 6) "peter" 7) "2" 8) "10" 9) "jack" 10) "1" 11) "9999" 12) "admin" 將哈希表作為 GET 或 BY 的參數 除了可以將字符串鍵之外, 哈希表也可以作為 GET 或 BY 選項的參數來使用。 比如說,對於前面給出的用戶信息表: uid user_name_{uid} user_level_{uid} 1 admin 9999 2 jack 10 3 peter 25 4 mary 70 我們可以不將用戶的名字和級別保存在 user_name_{uid} 和 user_level_{uid} 兩個字符串鍵中, 而是用一個帶有 name 域和 level 域的哈希表 user_info_{uid} 來保存用戶的名字和級別信息: redis 127.0.0.1:6379> HMSET user_info_1 name admin level 9999 OK redis 127.0.0.1:6379> HMSET user_info_2 name jack level 10 OK redis 127.0.0.1:6379> HMSET user_info_3 name peter level 25 OK redis 127.0.0.1:6379> HMSET user_info_4 name mary level 70 OK 之后, BY 和 GET 選項都可以用 key->field 的格式來獲取哈希表中的域的值, 其中 key 表示哈希表鍵, 而 field 則表示哈希表的域: redis 127.0.0.1:6379> SORT uid BY user_info_*->level 1) "2" 2) "3" 3) "4" 4) "1" redis 127.0.0.1:6379> SORT uid BY user_info_*->level GET user_info_*->name 1) "jack" 2) "peter" 3) "mary" 4) "admin" 保存排序結果 默認情況下, SORT 操作只是簡單地返回排序結果,並不進行任何保存操作。 通過給 STORE 選項指定一個 key 參數,可以將排序結果保存到給定的鍵上。 如果被指定的 key 已存在,那么原有的值將被排序結果覆蓋。 # 測試數據 redis 127.0.0.1:6379> RPUSH numbers 1 3 5 7 9 (integer) 5 redis 127.0.0.1:6379> RPUSH numbers 2 4 6 8 10 (integer) 10 redis 127.0.0.1:6379> LRANGE numbers 0 -1 1) "1" 2) "3" 3) "5" 4) "7" 5) "9" 6) "2" 7) "4" 8) "6" 9) "8" 10) "10" redis 127.0.0.1:6379> SORT numbers STORE sorted-numbers (integer) 10 # 排序后的結果 redis 127.0.0.1:6379> LRANGE sorted-numbers 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "7" 8) "8" 9) "9" 10) "10" 可以通過將 SORT 命令的執行結果保存,並用 EXPIRE 為結果設置生存時間,以此來產生一個 SORT 操作的結果緩存。 這樣就可以避免對 SORT 操作的頻繁調用:只有當結果集過期時,才需要再調用一次 SORT 操作。 另外,為了正確實現這一用法,你可能需要加鎖以避免多個客戶端同時進行緩存重建(也就是多個客戶端,同一時間進行 SORT 操作,並保存為結果集),具體參見 SETNX 命令。 */ } /** * TYPE key * 返回 key 所儲存的值的類型。 */ @Test public void TYPE() { System.out.println(jedis.type(KEY)); System.out.println(jedis.type("set")); System.out.println(jedis.type("sorted_set")); System.out.println(jedis.type("list")); System.out.println(jedis.type("hash")); } /** * SCAN cursor [MATCH pattern] [COUNT count] * SCAN 命令及其相關的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用於增量地迭代(incrementally iterate)一集元素(a collection of elements): * SCAN 命令用於迭代當前數據庫中的數據庫鍵。 * SSCAN 命令用於迭代集合鍵中的元素。 * HSCAN 命令用於迭代哈希鍵中的鍵值對。 * ZSCAN 命令用於迭代有序集合中的元素(包括元素成員和元素分值)。 * 以上列出的四個命令都支持增量式迭代, 它們每次執行都只會返回少量元素, 所以這些命令可以用於生產環境, 而不會出現像 KEYS 命令、 SMEMBERS 命令帶來的問題 * —— 當 KEYS 命令被用於處理一個大的數據庫時, 又或者 SMEMBERS 命令被用於處理一個大的集合鍵時, 它們可能會阻塞服務器達數秒之久。 * 不過, 增量式迭代命令也不是沒有缺點的: 舉個例子, 使用 SMEMBERS 命令可以返回集合鍵當前包含的所有元素, 但是對於 SCAN 這類增量式迭代命令來說, * 因為在對鍵進行增量式迭代的過程中, 鍵可能會被修改, 所以增量式迭代命令只能對被返回的元素提供有限的保證 (offer limited guarantees about the returned elements)。 * 因為 SCAN 、 SSCAN 、 HSCAN 和 ZSCAN 四個命令的工作方式都非常相似, 所以這個文檔會一並介紹這四個命令, 但是要記住: * SSCAN 命令、 HSCAN 命令和 ZSCAN 命令的第一個參數總是一個數據庫鍵。 * 而 SCAN 命令則不需要在第一個參數提供任何數據庫鍵 —— 因為它迭代的是當前數據庫中的所有數據庫鍵。 */ @Test public void SCAN() { /** SCAN 命令的基本用法 SCAN 命令是一個基於游標的迭代器(cursor based iterator): SCAN 命令每次被調用之后, 都會向用戶返回一個新的游標, 用戶在下次迭代時需要使用這個新游標作為 SCAN 命令的游標參數, 以此來延續之前的迭代過程。 當 SCAN 命令的游標參數被設置為 0 時, 服務器將開始一次新的迭代, 而當服務器向用戶返回值為 0 的游標時, 表示迭代已結束。 以下是一個 SCAN 命令的迭代過程示例: redis 127.0.0.1:6379> scan 0 1) "17" 2) 1) "key:12" 2) "key:8" 3) "key:4" 4) "key:14" 5) "key:16" 6) "key:17" 7) "key:15" 8) "key:10" 9) "key:3" 10) "key:7" 11) "key:1" redis 127.0.0.1:6379> scan 17 1) "0" 2) 1) "key:5" 2) "key:18" 3) "key:0" 4) "key:2" 5) "key:19" 6) "key:13" 7) "key:6" 8) "key:9" 9) "key:11" 在上面這個例子中, 第一次迭代使用 0 作為游標, 表示開始一次新的迭代。 第二次迭代使用的是第一次迭代時返回的游標, 也即是命令回復第一個元素的值 —— 17 。 從上面的示例可以看到, SCAN 命令的回復是一個包含兩個元素的數組, 第一個數組元素是用於進行下一次迭代的新游標, 而第二個數組元素則是一個數組, 這個數組中包含了所有被迭代的元素。 在第二次調用 SCAN 命令時, 命令返回了游標 0 , 這表示迭代已經結束, 整個數據集(collection)已經被完整遍歷過了。 以 0 作為游標開始一次新的迭代, 一直調用 SCAN 命令, 直到命令返回游標 0 , 我們稱這個過程為一次完整遍歷(full iteration)。 SCAN 命令的保證(guarantees) SCAN 命令, 以及其他增量式迭代命令, 在進行完整遍歷的情況下可以為用戶帶來以下保證: 從完整遍歷開始直到完整遍歷結束期間, 一直存在於數據集內的所有元素都會被完整遍歷返回; 這意味着, 如果有一個元素, 它從遍歷開始直到遍歷結束期間都存在於被遍歷的數據集當中, 那么 SCAN 命令總會在某次迭代中將這個元素返回給用戶。 然而因為增量式命令僅僅使用游標來記錄迭代狀態, 所以這些命令帶有以下缺點: 同一個元素可能會被返回多次。 處理重復元素的工作交由應用程序負責, 比如說, 可以考慮將迭代返回的元素僅僅用於可以安全地重復執行多次的操作上。 如果一個元素是在迭代過程中被添加到數據集的, 又或者是在迭代過程中從數據集中被刪除的, 那么這個元素可能會被返回, 也可能不會, 這是未定義的(undefined)。 SCAN 命令每次執行返回的元素數量 增量式迭代命令並不保證每次執行都返回某個給定數量的元素。 增量式命令甚至可能會返回零個元素, 但只要命令返回的游標不是 0 , 應用程序就不應該將迭代視作結束。 不過命令返回的元素數量總是符合一定規則的, 在實際中: 對於一個大數據集來說, 增量式迭代命令每次最多可能會返回數十個元素; 而對於一個足夠小的數據集來說, 如果這個數據集的底層表示為編碼數據結構(encoded data structure,適用於是小集合鍵、小哈希鍵和小有序集合鍵), 那么增量迭代命令將在一次調用中返回數據集中的所有元素。 最后, 用戶可以通過增量式迭代命令提供的 COUNT 選項來指定每次迭代返回元素的最大值。 COUNT 選項 雖然增量式迭代命令不保證每次迭代所返回的元素數量, 但我們可以使用 COUNT 選項, 對命令的行為進行一定程度上的調整。 基本上, COUNT 選項的作用就是讓用戶告知迭代命令, 在每次迭代中應該從數據集里返回多少元素。 雖然 COUNT 選項只是對增量式迭代命令的一種提示(hint), 但是在大多數情況下, 這種提示都是有效的。 COUNT 參數的默認值為 10 。 在迭代一個足夠大的、由哈希表實現的數據庫、集合鍵、哈希鍵或者有序集合鍵時, 如果用戶沒有使用 MATCH 選項, 那么命令返回的元素數量通常和 COUNT 選項指定的一樣, 或者比 COUNT 選項指定的數量稍多一些。 在迭代一個編碼為整數集合(intset,一個只由整數值構成的小集合)、 或者編碼為壓縮列表(ziplist,由不同值構成的一個小哈希或者一個小有序集合)時, 增量式迭代命令通常會無視 COUNT 選項指定的值, 在第一次迭代就將數據集包含的所有元素都返回給用戶。 並非每次迭代都要使用相同的 COUNT 值。 用戶可以在每次迭代中按自己的需要隨意改變 COUNT 值, 只要記得將上次迭代返回的游標用到下次迭代里面就可以了。 MATCH 選項 和 KEYS 命令一樣, 增量式迭代命令也可以通過提供一個 glob 風格的模式參數, 讓命令只返回和給定模式相匹配的元素, 這一點可以通過在執行增量式迭代命令時, 通過給定 MATCH <pattern> 參數來實現。 以下是一個使用 MATCH 選項進行迭代的示例: redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood (integer) 6 redis 127.0.0.1:6379> sscan myset 0 match f* 1) "0" 2) 1) "foo" 2) "feelsgood" 3) "foobar" 需要注意的是, 對元素的模式匹配工作是在命令從數據集中取出元素之后, 向客戶端返回元素之前的這段時間內進行的, 所以如果被迭代的數據集中只有少量元素和模式相匹配, 那么迭代命令或許會在多次執行中都不返回任何元素。 以下是這種情況的一個例子: redis 127.0.0.1:6379> scan 0 MATCH *11* 1) "288" 2) 1) "key:911" redis 127.0.0.1:6379> scan 288 MATCH *11* 1) "224" 2) (empty list or set) redis 127.0.0.1:6379> scan 224 MATCH *11* 1) "80" 2) (empty list or set) redis 127.0.0.1:6379> scan 80 MATCH *11* 1) "176" 2) (empty list or set) redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000 1) "0" 2) 1) "key:611" 2) "key:711" 3) "key:118" 4) "key:117" 5) "key:311" 6) "key:112" 7) "key:111" 8) "key:110" 9) "key:113" 10) "key:211" 11) "key:411" 12) "key:115" 13) "key:116" 14) "key:114" 15) "key:119" 16) "key:811" 17) "key:511" 18) "key:11" 如你所見, 以上的大部分迭代都不返回任何元素。 在最后一次迭代, 我們通過將 COUNT 選項的參數設置為 1000 , 強制命令為本次迭代掃描更多元素, 從而使得命令返回的元素也變多了。 並發執行多個迭代 在同一時間, 可以有任意多個客戶端對同一數據集進行迭代, 客戶端每次執行迭代都需要傳入一個游標, 並在迭代執行之后獲得一個新的游標, 而這個游標就包含了迭代的所有狀態, 因此, 服務器無須為迭代記錄任何狀態。 中途停止迭代 因為迭代的所有狀態都保存在游標里面, 而服務器無須為迭代保存任何狀態, 所以客戶端可以在中途停止一個迭代, 而無須對服務器進行任何通知。 即使有任意數量的迭代在中途停止, 也不會產生任何問題。 使用錯誤的游標進行增量式迭代 使用間斷的(broken)、負數、超出范圍或者其他非正常的游標來執行增量式迭代並不會造成服務器崩潰, 但可能會讓命令產生未定義的行為。 未定義行為指的是, 增量式命令對返回值所做的保證可能會不再為真。 只有兩種游標是合法的: 在開始一個新的迭代時, 游標必須為 0 。 增量式迭代命令在執行之后返回的, 用於延續(continue)迭代過程的游標。 迭代終結的保證 增量式迭代命令所使用的算法只保證在數據集的大小有界(bounded)的情況下, 迭代才會停止, 換句話說, 如果被迭代數據集的大小不斷地增長的話, 增量式迭代命令可能永遠也無法完成一次完整迭代。 從直覺上可以看出, 當一個數據集不斷地變大時, 想要訪問這個數據集中的所有元素就需要做越來越多的工作, 能否結束一個迭代取決於用戶執行迭代的速度是否比數據集增長的速度更快。 可用版本: >= 2.8.0 時間復雜度: 增量式迭代命令每次執行的復雜度為 O(1) , 對數據集進行一次完整迭代的復雜度為 O(N) , 其中 N 為數據集中的元素數量。 返回值: SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一個包含兩個元素的 multi-bulk 回復: 回復的第一個元素是字符串表示的無符號 64 位整數(游標), 回復的第二個元素是另一個 multi-bulk 回復, 這個 multi-bulk 回復包含了本次被迭代的元素。 SCAN 命令返回的每個元素都是一個數據庫鍵。 SSCAN 命令返回的每個元素都是一個集合成員。 HSCAN 命令返回的每個元素都是一個鍵值對,一個鍵值對由一個鍵和一個值組成。 ZSCAN 命令返回的每個元素都是一個有序集合元素,一個有序集合元素由一個成員(member)和一個分值(score)組成。 */ } }