Redis Scan 命令使用教程(高效遍历海量数据的方法)
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">Redis 中 Scan 命令使用教程:高效遍历海量数据</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">一、介绍</a></li><ul class="third_class_ul"><li><a href="#_label3_0_0_0">1. 为什么需要 Scan?</a></li><li><a href="#_label3_0_0_1">2. 典型应用场景</a></li></ul><li><a href="#_lab2_0_1">二、使用原理</a></li><ul class="third_class_ul"><li><a href="#_label3_0_1_2">1. Redis 键空间的存储结构</a></li><li><a href="#_label3_0_1_3">2. 游标迭代机制</a></li><li><a href="#_label3_0_1_4">3. 避免漏遍历与重复遍历的设计</a></li><li><a href="#_label3_0_1_5">4. 计数参数(count)的作用</a></li></ul><li><a href="#_lab2_0_2">三、使用方式</a></li><ul class="third_class_ul"><li><a href="#_label3_0_2_6">1. 基础语法</a></li><li><a href="#_label3_0_2_7">2. 实操示例</a></li><li><a href="#_label3_0_2_8">3. 代码示例(Golang)</a></li><li><a href="#_label3_0_2_9">4. 注意事项</a></li></ul><li><a href="#_lab2_0_3">四、总结</a></li><ul class="third_class_ul"></ul></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>Redis 中 Scan 命令使用教程:高效遍历海量数据</h2><p>在 Redis 中,当需要遍历所有键或指定模式的键时,传统的 <code>KEYS</code> 命令会因阻塞主线程、无法分页等问题,在海量数据场景下表现糟糕。而 <code>Scan</code> 命令凭借 “非阻塞”“分批遍历” 的特性,成为解决大规模数据遍历的最优方案。本文将从基础介绍、实现原理、实操方式到实践总结,全面讲解 <code>Scan</code> 命令的使用。</p>
<p class="maodian"><a name="_lab2_0_0"></a></p><h3>一、介绍</h3>
<p><code>Scan</code> 是 Redis 2.8 版本引入的迭代式遍历命令,主要用于遍历 Redis 中的键集合或集合类型(如 Hash、Set、Sorted Set)的元素,核心目标是解决传统遍历命令的性能问题。</p>
<p class="maodian"><a name="_label3_0_0_0"></a></p><h4>1. 为什么需要 Scan?</h4>
<p>传统的 <code>KEYS</code> 命令存在明显缺陷:</p>
<ul><li><strong>阻塞主线程</strong>:<code>KEYS</code> 会一次性遍历所有符合条件的键,若数据量达百万级,会占用大量 CPU 时间,导致 Redis 无法响应其他请求;</li><li><strong>无分页能力</strong>:<code>KEYS</code> 只能一次性返回所有结果,无法分批处理,容易造成客户端内存溢出;</li><li><strong>不支持复杂筛选</strong>:仅能通过简单的通配符(如 <code>*</code>、<code>?</code>)匹配,灵活性低。</li></ul>
<p>而 <code>Scan</code> 命令恰好弥补这些不足,具备以下核心特性:</p>
<ul><li><strong>非阻塞</strong>:分批遍历数据,每次只处理少量元素,避免长时间占用主线程;</li><li><strong>游标迭代</strong>:通过 “游标(cursor)” 记录遍历位置,支持断点续传;</li><li><strong>安全遍历</strong>:遍历过程中数据的新增、删除、修改不会导致漏遍历或重复遍历(存在极小概率重复,但可通过业务层去重解决);</li><li><strong>多类型支持</strong>:除了遍历所有键(<code>SCAN</code>),还支持遍历 Hash 字段(<code>HSCAN</code>)、Set 元素(<code>SSCAN</code>)、Sorted Set 元素(<code>ZSCAN</code>)。</li></ul>
<p class="maodian"><a name="_label3_0_0_1"></a></p><h4>2. 典型应用场景</h4>
<ul><li><strong>海量键统计</strong>:如统计 Redis 中所有以 <code>user:</code> 为前缀的键数量;</li><li><strong>数据清理</strong>:分批删除过期或无用的键(如删除所有 <code>temp:</code> 前缀的临时键);</li><li><strong>集合元素遍历</strong>:遍历大型 Hash 中的所有字段值,避免一次性加载导致内存溢出;</li><li><strong>定期数据校验</strong>:分批检查键的过期时间或数据完整性。</li></ul>
<p class="maodian"><a name="_lab2_0_1"></a></p><h3>二、使用原理</h3>
<p><code>Scan</code> 命令的核心是基于 “游标” 和 “哈希表遍历” 实现的,理解其底层原理能帮助更好地使用命令。</p>
<p class="maodian"><a name="_label3_0_1_2"></a></p><h4>1. Redis 键空间的存储结构</h4>
<p>Redis 的键空间(keyspace)底层基于<strong>哈希表</strong>存储,每个键通过哈希函数映射到哈希表的某个 “桶(bucket)” 中。<code>Scan</code> 命令本质是遍历哈希表的桶,并通过游标记录当前遍历到的桶位置。</p>
<p class="maodian"><a name="_label3_0_1_3"></a></p><h4>2. 游标迭代机制</h4>
<p><code>Scan</code> 的遍历过程类似 “翻书”,游标就是 “页码”,具体流程如下:</p>
<ul><li><strong>初始游标</strong>:首次调用 <code>Scan</code> 时,游标值设为 <code>0</code>,表示从哈希表的起始位置开始遍历;</li><li><strong>分批遍历</strong>:Redis 会根据游标位置,返回当前批次的元素(默认 10 个),并返回新的游标值;</li><li><strong>结束条件</strong>:当返回的游标值为 <code>0</code> 时,表示已遍历完所有元素;</li><li><strong>断点续传</strong>:若遍历中断(如客户端重启),下次可使用上次返回的非 0 游标继续遍历,无需从头开始。</li></ul>
<p class="maodian"><a name="_label3_0_1_4"></a></p><h4>3. 避免漏遍历与重复遍历的设计</h4>
<p>由于 Redis 哈希表在扩容(rehash)时会重新分配桶的位置,<code>Scan</code> 通过以下机制保证遍历的准确性:</p>
<ul><li><strong>渐进式 rehash 兼容</strong>:遍历过程中若触发哈希表扩容,<code>Scan</code> 会同时遍历旧哈希表和新哈希表,确保所有键都能被访问到;</li><li><strong>允许重复遍历</strong>:为了简化实现,<code>Scan</code> 不保证元素只出现一次(尤其是在 rehash 过程中),但重复概率极低,业务层可通过去重(如用 Set 暂存结果)解决。</li></ul>
<p class="maodian"><a name="_label3_0_1_5"></a></p><h4>4. 计数参数(count)的作用</h4>
<p><code>Scan</code> 命令中的 <code>count</code> 参数用于指定 “每次遍历的桶数量”,而非 “返回的元素数量”:</p>
<ul><li>默认 <code>count=10</code>,表示每次遍历 10 个桶,返回这些桶中的所有符合条件的元素;</li><li><code>count</code> 并非严格限制,Redis 会根据桶中元素数量动态调整返回结果(如某个桶中没有符合条件的键,可能返回少于 <code>count</code> 个元素);</li><li>海量数据场景下,可适当增大 <code>count</code>(如 <code>count=1000</code>),减少遍历次数,提升效率。</li></ul>
<p class="maodian"><a name="_lab2_0_2"></a></p><h3>三、使用方式</h3>
<p><code>Scan</code> 命令家族包括 <code>SCAN</code>(遍历键空间)、<code>HSCAN</code>(遍历 Hash)、<code>SSCAN</code>(遍历 Set)、<code>ZSCAN</code>(遍历 Sorted Set),核心用法类似,以下以最常用的 <code>SCAN</code> 为例讲解,其他命令用法可类比。</p>
<p class="maodian"><a name="_label3_0_2_6"></a></p><h4>1. 基础语法</h4>
<h5>(1)SCAN 命令(遍历所有键)</h5>
<div class="jb51code"><pre class="brush:bash;"> 语法:SCAN cursor
cursor:游标值(首次为 0,后续用上次返回的游标)
MATCH pattern:通配符匹配,筛选符合条件的键(可选)
COUNT count:每次遍历的桶数量(可选,默认 10)
TYPE type:按键类型筛选(如 string、hash、set,可选,Redis 6.0+ 支持)</pre></div>
<h5>(2)其他命令语法(类比)</h5>
<ul><li><code>HSCAN key cursor </code>:遍历 Hash 键 <code>key</code> 的字段和值;</li><li><code>SSCAN key cursor </code>:遍历 Set 键 <code>key</code> 的元素;</li><li><code>ZSCAN key cursor </code>:遍历 Sorted Set 键 <code>key</code> 的元素和分数。</li></ul>
<p class="maodian"><a name="_label3_0_2_7"></a></p><h4>2. 实操示例</h4>
<h5>(1)遍历所有键(无筛选)</h5>
<div class="jb51code"><pre class="brush:plain;"># 首次遍历:游标 0,默认 count=10
127.0.0.1:6379> SCAN 0
1) "17"# 下次遍历的游标
2) 1) "user:1001"
2) "product:2003"
3) "order:5001"
# 本次返回 3 个键(少于 count=10,因部分桶无符合条件的键)
# 第二次遍历:使用上次返回的游标 17
127.0.0.1:6379> SCAN 17
1) "0"# 游标为 0,遍历结束
2) 1) "user:1002"
2) "temp:3001"</pre></div>
<h5>(2)按前缀筛选键(MATCH)</h5>
<div class="jb51code"><pre class="brush:plain;"># 遍历所有以 "user:" 为前缀的键,count=20
127.0.0.1:6379> SCAN 0 MATCH user:* COUNT 20
1) "23"
2) 1) "user:1001"
2) "user:1002"
3) "user:1003"
# 继续遍历,直到游标返回 0
127.0.0.1:6379> SCAN 23 MATCH user:* COUNT 20
1) "0"
2) 1) "user:1004"</pre></div>
<h5>(3)按类型筛选键(TYPE,Redis 6.0+)</h5>
<div class="jb51code"><pre class="brush:plain;"># 遍历所有 string 类型的键
127.0.0.1:6379> SCAN 0 TYPE string
1) "12"
2) 1) "user:1001"# 假设该键是 string 类型
2) "temp:3001"</pre></div>
<h5>(4)遍历 Hash 键(HSCAN)</h5>
<div class="jb51code"><pre class="brush:plain;"># 先创建一个 Hash 键
127.0.0.1:6379> HMSET user:1001 name "Alice" age "25" city "Beijing"
OK
# 遍历该 Hash 的字段和值
127.0.0.1:6379> HSCAN user:1001 0
1) "0"# 游标为 0,Hash 元素少,一次遍历完
2) 1) "name"
2) "Alice"
3) "age"
4) "25"
5) "city"
6) "Beijing"</pre></div>
<p class="maodian"><a name="_label3_0_2_8"></a></p><h4>3. 代码示例(Golang)</h4>
<p>以遍历所有 <code>user:</code> 前缀的键为例,使用 <code>go-redis</code> 客户端实现:</p>
<div class="jb51code"><pre class="brush:go;">package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
// 初始化 Redis 客户端
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
defer client.Close()
ctx := context.Background()
// Scan 遍历所有 user: 前缀的键
var cursor uint64 = 0// 初始游标
count := int64(20) // 每次遍历的桶数量
pattern := "user:*" // 匹配模式
fmt.Println("开始遍历 user: 前缀的键:")
for {
// 执行 Scan 命令
result, err := client.Scan(ctx, cursor, pattern, count).Result()
if err != nil {
fmt.Printf("Scan 执行失败:%v\n", err)
return
}
// 获取本次结果和下次游标
cursor = result.Cursor
keys := result.Keys
// 处理本次获取的键
for _, key := range keys {
fmt.Printf("找到键:%s\n", key)
}
// 游标为 0,遍历结束
if cursor == 0 {
break
}
}
fmt.Println("遍历完成")
}</pre></div>
<p class="maodian"><a name="_label3_0_2_9"></a></p><h4>4. 注意事项</h4>
<ul><li><strong>游标必须正确传递</strong>:每次遍历需使用上次返回的游标,否则会导致重复遍历或漏遍历;</li><li><strong>避免过度依赖 MATCH</strong>:<code>MATCH</code> 是在遍历结果中筛选,而非提前过滤,若符合条件的键极少,会导致多次空遍历,建议结合业务场景优化匹配模式;</li><li><strong>count 参数按需调整</strong>:数据量小时用默认 <code>count=10</code> 即可,海量数据时可增大 <code>count</code>(如 1000~10000),但不宜过大(避免单次操作耗时过长);</li><li><strong>业务层去重</strong>:因 <code>Scan</code> 可能返回重复元素,需在业务层通过 Set 或哈希表去重;</li><li><strong>不建议在主库高频使用</strong>:虽然 <code>Scan</code> 非阻塞,但频繁遍历仍会占用 CPU 资源,建议在从库执行(需确保主从数据同步及时)。</li></ul>
<p class="maodian"><a name="_lab2_0_3"></a></p><h3>四、总结</h3>
<p><code>Scan</code> 命令是 Redis 中处理海量数据遍历的核心工具,其核心优势可总结为:</p>
<ul><li><strong>非阻塞设计</strong>:分批遍历避免阻塞主线程,保障 Redis 服务可用性;</li><li><strong>游标迭代</strong>:支持断点续传,适合长时间、大规模的遍历任务;</li><li><strong>灵活筛选</strong>:通过 <code>MATCH</code> 和 <code>TYPE</code> 实现精准筛选,满足多样化需求;</li><li><strong>多类型支持</strong>:覆盖键空间和所有集合类型,适用场景广泛。</li></ul>
<p>在实际使用中,需注意以下关键要点:</p>
<ul><li>正确传递游标,确保遍历的连续性;</li><li>根据数据量调整 <code>count</code> 参数,平衡遍历效率和资源占用;</li><li>结合业务场景优化筛选逻辑,减少无效遍历;</li><li>对遍历结果进行去重,避免重复处理。</li></ul>
<p>总之,<code>Scan</code> 命令彻底解决了传统 <code>KEYS</code> 命令的性能痛点,是 Redis 运维和开发中处理海量数据的 “必备工具”,掌握其用法能显著提升大规模 Redis 集群的管理效率。</p>
頁:
[1]