Redis中漸進(jìn)式命令scan詳解與使用
在 Redis 日常開發(fā)中,我們經(jīng)常需要遍歷數(shù)據(jù)庫中的鍵或集合中的元素。傳統(tǒng)的keys命令雖然簡單直接,但在數(shù)據(jù)量較大時會嚴(yán)重阻塞 Redis 服務(wù),甚至引發(fā)生產(chǎn)事故。而scan命令作為 Redis 2.8 版本引入的漸進(jìn)式遍歷方案,完美解決了這一痛點(diǎn)。
一、為什么需要 scan?先看 keys 命令的 “坑”
在了解scan之前,我們首先要明確:為什么不能在生產(chǎn)環(huán)境中隨意使用keys命令?
keys命令的工作方式是全量遍歷—— 它會一次性遍歷 Redis 中的所有鍵,并將符合模式的結(jié)果全部返回。這種方式存在兩個致命問題:
- 阻塞服務(wù):Redis 是單線程模型,
keys命令執(zhí)行期間會占用主線程,導(dǎo)致其他請求(如讀寫操作)無法響應(yīng),數(shù)據(jù)量越大,阻塞時間越長(毫秒級到秒級不等); - 內(nèi)存暴漲:若符合條件的鍵數(shù)量極多(如 100 萬 +),
keys會一次性將所有結(jié)果加載到內(nèi)存,可能引發(fā) Redis 內(nèi)存溢出或 OS 級別的 Swap,嚴(yán)重時導(dǎo)致服務(wù)崩潰。
而scan命令的設(shè)計(jì)理念是 **“漸進(jìn)式遍歷”**:將全量遍歷拆分為多次小規(guī)模遍歷,每次只返回少量結(jié)果(默認(rèn) 10 個),既不會阻塞主線程,也不會占用過多內(nèi)存。這使得它成為生產(chǎn)環(huán)境中遍歷鍵的首選方案。
二、scan 命令的基礎(chǔ)用法:從語法到示例
2.1 核心語法
scan命令的基本格式如下:
SCAN cursor \[MATCH pattern] \[COUNT count] \[TYPE type]
各參數(shù)的含義如下:
- cursor:游標(biāo),是
scan的核心標(biāo)識,用于記錄遍歷的 “位置”。首次遍歷需傳入0,后續(xù)遍歷需傳入上一次返回的游標(biāo)值,直到游標(biāo)返回0表示遍歷結(jié)束; - MATCH pattern:可選參數(shù),用于過濾符合指定模式的鍵,支持通配符
*(匹配任意字符)、?(匹配單個字符)、[](匹配指定范圍內(nèi)的字符); - COUNT count:可選參數(shù),用于指定每次遍歷的 “預(yù)期返回?cái)?shù)量”,默認(rèn)值為
10。注意:這只是 “預(yù)期值”,并非實(shí)際返回?cái)?shù)量,Redis 會根據(jù)內(nèi)部數(shù)據(jù)結(jié)構(gòu)調(diào)整,可能多也可能少; - TYPE type:可選參數(shù),用于過濾指定數(shù)據(jù)類型的鍵,支持
string、hash、list、set、zset等,Redis 6.0 + 版本支持。
2.2 基礎(chǔ)示例:遍歷所有鍵
假設(shè) Redis 中存在以下鍵:user:100、user:101、order:200、product:300,我們通過scan遍歷所有鍵:
- 首次遍歷:傳入游標(biāo)
0,不指定MATCH和COUNT(默認(rèn)返回 10 個):
127.0.0.1:6379> SCAN 0 1\) "14" # 下一次遍歷的游標(biāo) 2\) 1) "user:100"   2\) "order:200"   3\) "product:300"
結(jié)果中,第一個元素"14"是下一次遍歷的游標(biāo),第二個元素是本次返回的鍵列表(共 3 個,少于默認(rèn)的 10 個,符合 “預(yù)期值” 特性)。
- 第二次遍歷:傳入上一次返回的游標(biāo)
14:
127.0.0.1:6379> SCAN 14 1\) "0" # 游標(biāo)返回0,遍歷結(jié)束 2\) 1) "user:101"
此時游標(biāo)返回0,表示所有鍵已遍歷完成,本次返回剩余的user:101。
2.3 過濾場景:MATCH 與 TYPE 的使用
場景 1:匹配 “user:” 前綴的鍵
通過MATCH user:*過濾前綴為user:的鍵:
127.0.0.1:6379> SCAN 0 MATCH user:\* 1\) "0" 2\) 1) "user:100"   2\) "user:101"
由于符合條件的鍵較少,一次遍歷就完成,游標(biāo)直接返回0。
場景 2:只遍歷 string 類型的鍵
假設(shè)user:100是string類型,order:200是hash類型,通過TYPE string過濾:
127.0.0.1:6379> SCAN 0 TYPE string 1\) "8" 2\) 1) "user:100"
三、scan 的核心原理:為什么能 “漸進(jìn)式” 遍歷?
要正確使用scan,必須理解其底層原理 ——基于 Redis 的哈希表結(jié)構(gòu)和游標(biāo)跳轉(zhuǎn)。
3.1 Redis 的鍵空間存儲:哈希表
Redis 的鍵空間(keyspace)是通過哈希表實(shí)現(xiàn)的,哈希表中的每個 “桶”(bucket)存儲若干個鍵(通過哈希沖突鏈解決沖突)。scan的遍歷本質(zhì)是遍歷哈希表的桶,而非直接遍歷鍵。
3.2 游標(biāo)與 “高位進(jìn)位”
scan的游標(biāo)并非簡單的 “桶索引”,而是基于 “高位進(jìn)位” 的算法設(shè)計(jì):
- Redis 會為哈希表分配一個固定的 “位數(shù)”(如 16 位,對應(yīng) 65536 個桶),游標(biāo)是一個無符號整數(shù),長度與哈希表位數(shù)一致;
- 每次遍歷后,游標(biāo)會按照 “高位進(jìn)位” 的規(guī)則生成下一個游標(biāo)(例如,16 位游標(biāo)
0000000000000000(0)的下一個游標(biāo)可能是0000000000001000(8),再下一個是0000000000010000(16)等); - 當(dāng)游標(biāo)再次回到
0時,表示所有桶已遍歷完成,即整個鍵空間遍歷結(jié)束。
3.3 COUNT 參數(shù)的 “預(yù)期” 特性
為什么COUNT是 “預(yù)期值” 而非 “固定值”?原因有兩點(diǎn):
- 哈希沖突:一個桶中可能存儲多個鍵,遍歷該桶時會返回所有鍵,導(dǎo)致實(shí)際數(shù)量超過
COUNT; - 空桶跳過:若某個桶中沒有鍵,Redis 會直接跳過,導(dǎo)致實(shí)際數(shù)量少于
COUNT。
例如,設(shè)置COUNT 2,但實(shí)際返回 3 個鍵:
127.0.0.1:6379> SCAN 0 COUNT 2 1\) "12" 2\) 1) "user:100"   2\) "order:200"   3\) "product:300" # 因哈希沖突,桶中鍵數(shù)量超過COUNT
四、scan 家族命令:不止遍歷鍵
scan是一個 “家族”,除了遍歷整個鍵空間的scan命令,還有針對特定數(shù)據(jù)類型的漸進(jìn)式遍歷命令,用法與scan一致,僅作用對象不同:
| 命令 | 作用對象 | 用途 |
|---|---|---|
| HSCAN | Hash 類型的字段 | 遍歷 Hash 中的 field-value 對 |
| SSCAN | Set 類型的元素 | 遍歷 Set 中的所有元素 |
| ZSCAN | Sorted Set 類型的元素 | 遍歷 ZSet 中的 member-score 對 |
示例:HSCAN 遍歷 Hash 字段
假設(shè)user:100是 Hash 類型,存儲用戶信息:
127.0.0.1:6379> HSET user:100 name "zhangsan" age 25 city "beijing" (integer) 3
通過HSCAN遍歷其字段:
\# 首次遍歷:游標(biāo)0,COUNT 2 127.0.0.1:6379> HSCAN user:100 0 COUNT 2 1\) "2" # 下一次游標(biāo) 2\) 1) "name"   2\) "zhangsan"   3\) "age"   4\) "25" \# 第二次遍歷:游標(biāo)2 127.0.0.1:6379> HSCAN user:100 2 1\) "0" # 遍歷結(jié)束 2\) 1) "city"   2\) "beijing"
五、使用 scan 的注意事項(xiàng)與常見誤區(qū)
5.1 避免 “重復(fù)” 與 “遺漏”
scan的設(shè)計(jì)目標(biāo)是 “不重復(fù)、不遺漏”,但在以下場景可能出現(xiàn)問題:
- 數(shù)據(jù)動態(tài)變化:遍歷過程中,若鍵被添加、刪除或修改,可能導(dǎo)致某個鍵被重復(fù)遍歷或遺漏。這是漸進(jìn)式遍歷的固有特性,無法完全避免,需在業(yè)務(wù)層做兼容(如通過唯一 ID 去重);
- 游標(biāo)失效:若兩次遍歷間隔過長(如超過 Redis 的超時時間),游標(biāo)可能失效,導(dǎo)致遍歷中斷。建議縮短遍歷間隔,確保游標(biāo)連續(xù)性。
5.2 合理設(shè)置 COUNT 參數(shù)
- 小數(shù)據(jù)量:默認(rèn)
COUNT 10即可,無需調(diào)整; - 大數(shù)據(jù)量:若需加快遍歷速度,可適當(dāng)增大
COUNT(如COUNT 1000),但需注意:COUNT越大,單次遍歷占用的主線程時間越長,需在 “速度” 和 “阻塞風(fēng)險” 間平衡,建議不超過10000。
5.3 避免在遍歷中使用復(fù)雜過濾
MATCH和TYPE過濾是在 “遍歷后” 執(zhí)行的 ——Redis 會先遍歷出一批鍵,再過濾掉不符合條件的鍵。若符合條件的鍵比例極低(如MATCH user:100000*,但實(shí)際只有 1 個),會導(dǎo)致大量無效遍歷,浪費(fèi)資源。此時建議在業(yè)務(wù)層過濾,或通過 Redis 的鍵命名規(guī)范減少無效匹配。
5.4 不要依賴返回結(jié)果的順序
scan的返回結(jié)果是基于哈希表的桶順序,與鍵的插入順序無關(guān),且每次遍歷的順序可能不同。業(yè)務(wù)層不應(yīng)依賴scan的返回順序。
到此這篇關(guān)于Redis中漸進(jìn)式命令scan詳解與使用的文章就介紹到這了,更多相關(guān)Redis 漸進(jìn)式命令scan內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
windows環(huán)境下Redis+Spring緩存實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了windows環(huán)境下Redis+Spring緩存實(shí)例教程,感興趣的小伙伴們可以參考一下2016-04-04
RediSearch加RedisJSON大于Elasticsearch的搜索存儲引擎
這篇文章主要為大家介紹了RediSearch加RedisJSON大于Elasticsearch的王炸使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
redis中hiredis-API函數(shù)的調(diào)用方法
這篇文章主要介紹了redis中hiredis-API函數(shù)的調(diào)用,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-09-09

