• Redis SCAN命令的原理和工作方式详解
  • 发布于 1个月前
  • 54 热度
    0 评论
一  概述

Redis的SCAN命令被用来逐步迭代数据库中的键(keys),而不是一次性地返回所有键,从而避免长时间阻塞Redis服务器。它尤其适合于大型数据集的操作。


二 Scan命令集
SCAN 命令及其相关的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用于增量地迭代集合类元素。
SCAN 命令用于迭代当前数据库中的数据库键;
SSCAN 命令用于迭代集合键中的元素;
HSCAN 命令用于迭代哈希键中的键值对;
ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值);

以上列出的四个命令都支持增量式迭代, 它们每次执行都只会返回少量元素, 所以这些命令可以用于生产环境, 而不会出现像 KEYS 命令、 SMEMBERS 命令带来的问题。当 KEYS 命令被用于处理一个大的数据库时,又或者 SMEMBERS 命令被用于处理一个大的集合键时,它们可能会阻塞服务器达数秒之久。

不过,增量式迭代命令也有很大的缺点,比如在对键进行增量式迭代的过程中,键可能会被修改;字典在进行 rehash 时,缩容的情况会造成重复读取数据;在增量迭代过程,如果增加或删除元素在一定程度上也会导致问题(如果把增量迭代看作完整的过程)。所以增量式迭代命令只能对被返回的元素提供有限的保证。

三  Scan原理
SCAN命令使用游标(cursor)的概念。一个开始为0的游标被传给SCAN。在每次调用之后,SCAN都会返回一个新的游标值,直到这个游标值为0,表示迭代结束。命令本身接收两个参数:游标和可选的匹配模式。
SCAN cursor [MATCH pattern] [COUNT count]
cursor: 迭代开始的游标位置
MATCH pattern: 指定一个glob样式的模式,只返回与该模式相匹配的元素
COUNT count: 提示Redis在每次迭代中返回多少元素

四  SCAN工作方式
当你第一次执行SCAN时,你应该将游标设置为0,然后在后续的迭代中使用前一个SCAN命令返回的游标值进行调用。当SCAN返回的游标值再次为0时,迭代就结束了。

需要注意的是SCAN保证了在迭代过程中多次返回同一个元素的可能性,但不会漏掉任何元素。因此,它适合于非增量式扫描(比如缓存失效策略)而不适合去重或者计数等需要唯一性保证的场景。

命令行操作如下:

以下是基于golang实现的redis scan工作方式
package main

import (
  "context"
  "fmt"
  "github.com/go-redis/redis/v8"
)

var ctx = context.Background()

func main() {
  rdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379", // Redis地址
    Password: "",              // Redis密码,没有则留空
    DB:       0,               // 默认数据库,默认是0
  })

  var cursor uint64
  var count int64
  var keys []string
  var err error
  count = 10 // 每次迭代期望返回值数量

  for {
    keys, cursor, err = rdb.Scan(ctx, cursor, "*", count).Result()
    if err != nil {
      panic(err)
    }
    // 处理keys...
    for _, key := range keys {
      fmt.Println(key)
    }
    // 堆代码 duidaima.com
    // 当游标回到0时结束循环
    if cursor == 0 {
      break
    }
  }
}
上述代码会连接到本地运行的Redis服务,并开始以每次10个元素的方式遍历数据库中所有的键。注意,你可能想要将"*"替换为你自己的匹配模式。在每次迭代过程中提取出的键被打印出来,直到最终游标值为0,表示迭代结束。在实际应用中,你可能需要细化如何处理迭代到的键。比如,你可以将它们加入到某个列表中进行批量操作,或者逐一进行分析和处理。

五  SCAN踩坑
重复的key:在使用SCAN进行迭代过程中,如果有更新发生(新增、删除键),可能会导致迭代到重复的键。所以SCAN通常不适用于需要返回唯一结果集的场景。
性能问题:即使SCAN不会像KEYS命令那样阻塞服务器,但在每次调用时返回太多结果(通过COUNT参数调整)也会对性能产生影响。尤其是当匹配模式对应的keys很多时,网络IO可能会成为瓶颈。

COUNT参数误解:COUNT只是建议在一次迭代中期望的返回值数量,并不是一个严格的限制。实际返回的结果数量可能多于或少于这个数值。开发人员不应依赖COUNT来猜测迭代次数或结果数量。


为了正确使用SCAN以及避免相关的陷阱,建议做到:
1.明白SCAN提供的是渐进式迭代,并且对结果的完整性和一致性没有保证,适合于对顺序没有严格要求的场景。
2.适当调节COUNT参数以优化每次迭代的开销与性能平衡。
3.准备好处理重复结果的逻辑,尤其是在更复杂的使用案例中。
4.如果你需要准确、一致的查询结果并且数据集较小,可以考虑其它的命令如KEYS,但要注意这可能会引起的性能问题。
5.考虑pipeline或lua脚本来降低网络往返次数和延迟。
6.基于应用场景选择合适的迭代方案,例如有时通过维护一个索引集合可能比全库扫描更高效。
用户评论