redis中数据模糊查询scan用法详解

 更新时间:2025年09月09日 09:31:01   作者:xswlw_guoquanbao  
Redis模糊查询应避免KEYS,改用SCAN非阻塞迭代,优先优化键结构(如IndexSet、SortedSet),提升效率,复杂场景可结合外部搜索引擎,平衡内存、延迟及实时性需求,本文给大家介绍redis中数据模糊查询scan用法,感兴趣的朋友一起看看吧

redis中数据模糊查找-scan用法

1.查找方法

Redis中有一个经典的问题,在巨大的数据量的情况下,做类似于查找符合某种规则的Key的信息,这里就有两种方式,

一是keys命令,简单粗暴,由于Redis单线程这一特性,keys命令是以阻塞的方式执行的,keys是以遍历的方式实现的复杂度是 O(n),Redis库中的key越多,查找实现代价越大,产生的阻塞时间越长。

二是scan命令,以非阻塞的方式实现key值的查找,绝大多数情况下是可以替代keys命令的,可选性更强

2.keys命令

127.0.0.1:6379> keys s*
1) "s1"
2) "sddddf"
3) "sss"
4) "s358"
127.0.0.1:6379> 

3.scan命令

SCAN cursor [MATCH pattern] [COUNT count] 
cursor - 游标。
pattern - 匹配的模式。
count - 指定从数据集里返回多少元素,默认值为 10 。
可用版本
>= 2.8.0
 

示例:

127.0.0.1:6379> keys s*
1) "s1"
2) "sddddf"
3) "sss"
4) "s358"
127.0.0.1:6379> scan 0 match s* count 20
1) "0"
2) 1) "s1"
   2) "sss"
   3) "s358"
   4) "sddddf"
127.0.0.1:6379> scan 0 match s* count 2
1) "4"
2) 1) "s1"
127.0.0.1:6379> scan 4 match s* count 2
1) "1"
2) 1) "sss"
   2) "s358"
127.0.0.1:6379> scan 1 match s* count 2
1) "3"
2) 1) "sddddf"

SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。

4.Java代码中的实现

public Set<String> scan(String matchKey) {
    Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
        Set<String> keysTmp = new HashSet<>();
        Cursor<byte[]> cursor = connection.scan(
                new ScanOptions.ScanOptionsBuilder().match("*" + matchKey + "*").count(1000).build()
        );
        while (cursor.hasNext()) {
            keysTmp.add(new String(cursor.next()));
        }
        return keysTmp;
    });
    return keys;
}

redis的模糊查询提高效率

Redis 中进行模糊查询(例如使用 KEYS pattern 或 SCAN cursor MATCH pattern)本质上效率是相对较低的,尤其是在大型数据集上,因为 Redis 的核心优势在于通过精确键进行 O(1) 复杂度的快速访问,而模糊查询通常需要遍历键空间。

提高 Redis 模糊查询效率的核心策略是:避免在运行时进行全键空间扫描!

以下是一些有效的方法来提高效率或规避效率瓶颈:

🛑 1. 避免使用 KEYS 命令 (最重要!)

  • 问题: KEYS 命令会阻塞 Redis 服务,直到遍历完所有键并返回匹配结果。在生产环境的大数据集上,这可能导致服务不可用。
  • 解决方案: 绝对禁止 在生产环境使用 KEYS。使用 SCAN 替代。

🔍 2. 使用 SCAN 命令进行迭代式查询

原理: SCAN 命令使用游标(cursor)进行迭代,每次只返回一小部分匹配的键。它不会阻塞服务器,因为每次调用只占用少量时间。

优点:

  • 非阻塞: 不会导致服务停顿。
  • 增量式: 可以分批处理结果,减轻客户端和服务端压力。

缺点:

  • 不是原子快照: 在迭代过程中,如果键空间发生变化(增、删、改),可能会看到重复的键或遗漏部分键。这通常可以接受。
  • 整体耗时可能不短: 虽然每次调用快,但要获取所有匹配结果,最终需要完成的“工作总量”和 KEYS 类似(都需要遍历大部分或全部键空间)。
  • 客户端逻辑复杂: 需要管理游标和循环。

用法:

SCAN 0 MATCH user:profile:*:email COUNT 100

0 是起始游标(第一次调用)。

MATCH pattern 指定模糊匹配模式(可选)。

COUNT n 建议每次迭代返回的元素数量(只是个提示,Redis 可能返回更多或更少)。适当增加 COUNT (如 500, 1000) 可以在网络往返次数和单次耗时之间取得平衡,提高整体效率。

变种: SSCAN (扫描 Set), HSCAN (扫描 Hash), ZSCAN (扫描 Sorted Set)。这些用于扫描特定键内部的大集合元素,避免阻塞或大结果集。

🧠 3. 设计可查询的键结构 (最重要的优化方向!)

核心思想是将运行时扫描转化为精确查找或小范围查找。这通常需要牺牲一些存储空间(空间换时间)和增加写入/更新时的维护成本。

  • a) 使用索引集合 (Index Set):
    • 场景: 查询具有特定前缀、后缀或中间部分的键(如 user:123:profileorder:abc:details)。
    • 方法:
      • 创建一个专门的 Set 类型键(如 index:user:ids)。
      • 每当创建一个新用户键(如 SET user:123:profile {...}),同时将 123 添加到索引集合(SADD index:user:ids 123)。
      • 当需要查询所有用户键时,使用 SMEMBERS index:user:ids 或 SSCAN index:user:ids 获取所有用户 ID。
      • 客户端拿到 ID 列表后,再通过精确键(GET user:<id>:profile)获取数据。
    • 优点: 获取键列表非常快(O(1) 或 O(N),N 是用户数而非总键数),避免了全键扫描。
    • 缺点: 需要维护索引;占用额外内存;获取完整数据需要多次查询(N+1 问题)。
  • b) 使用 Sorted Set 按模式存储键或引用:
    • 场景: 需要按范围(如时间范围、分数范围)查询,或者需要排序。
    • 方法:
      • 创建一个 Sorted Set 键(如 zindex:orders:by_time)。
      • 成员(member)可以是:
        • 完整的键名(如 order:abc:details) - 适用于键名本身包含信息(如时间戳)。
        • 或一个唯一 ID(如 abc),分数(score)是查询依据(如订单创建时间戳)。
      • 当需要查询某时间段内的订单:
        • 使用 ZRANGEBYSCORE zindex:orders:by_time start_timestamp end_timestamp 获取键名或 ID。
        • 再通过精确键获取数据。
    • 优点: 支持高效的范围查询和排序。
    • 缺点: 维护索引;额外内存;潜在 N+1 问题。
  •  使用 Hash 存储子字段索引:

    • 场景: 需要根据对象内部字段的值进行查询(如查找所有 email 以 @gmail.com 结尾的用户)。

    • 方法:

      创建辅助数据结构:

      • 反向索引(Inverted Index): 对于需要查询的字段值(如邮箱后缀 gmail.com),创建一个 Set(如 index:email_suffix:gmail.com),存储拥有该后缀的用户 ID。

      更新数据时:

      • 修改用户 Hash (HSET user:123 email new@domain.com)。

      • 将用户 123 从旧后缀索引集移除(SREM index:email_suffix:old.com 123)。

      • 将用户 123 添加到新后缀索引集(SADD index:email_suffix:new.com 123)。

      查询时:SMEMBERS index:email_suffix:gmail.com 获取用户 ID 列表,然后 HGETALL user:<id>

    • 优点: 对于特定字段的等值查询非常高效。

    • 缺点: 维护成本最高(尤其字段值频繁更新时);占用大量额外内存;只适用于等值查询或有限模式(后缀=SADD 时存储后缀);N+1 问题。

  • d) 拆分键名 + 利用集合操作:

    • 场景: 键名由多个部分组成(如 country:region:city:userid),需要按不同层级查询。

    • 方法:

      为每个层级维护索引集:

      • countries = { 'us', 'uk', 'jp' ... }

      • regions:us = { 'ca', 'ny', 'tx' ... }

      • cities:us:ca = { 'sf', 'la', 'sd' ... }

      查询用户时:

      • 先通过精确键获取国家列表、特定国家的地区列表、特定国家地区的城市列表。

      • 然后构造出所有可能的键前缀(如 us:ca:sf)。

      • 最后用 SSCAN 遍历 user:us:ca:sf:*(范围大大缩小)。

    • 优点: 将全键扫描缩小到特定小范围扫描。

    • 缺点: 需要精心设计键结构和索引;维护索引;可能仍需小范围 SCAN

📦 4. 缓存模糊查询结果

  • 场景: 模糊查询模式相对固定且结果变化不频繁(如查询所有“活跃用户”列表)。
  • 方法: 定期(如每分钟)或在数据变更时触发一次 SCAN,将结果存入一个 Redis Set 或 List 中。
  • 查询时: 直接读取这个缓存的结果集合。
  • 优点: 查询速度极快(O(1) 或 O(N) 读集合)。
  • 缺点: 数据不是实时最新(最终一致性);需要维护缓存更新逻辑;占用额外内存。

🌐 5. 使用外部索引/搜索引擎 (对于复杂查询或海量数据)

  • 原理: 当 Redis 内置的查询能力(即使是优化后的索引)无法满足复杂模式匹配(如全文搜索、多字段组合查询)或数据量极大时。
  • 工具: Redis 官方模块 RediSearchElasticsearchSolrOpenSearch 等。
  • 方法:
    • 数据写入/更新 Redis 的同时,异步写入索引到搜索引擎。
    • 查询请求发送到搜索引擎,获取匹配的键 ID 或文档。
    • 根据 ID 回 Redis 获取完整数据(或搜索引擎已存储所需数据)。
  • 优点: 提供极其强大和高效的全文搜索、复杂过滤、聚合分析能力;避免 Redis 自身遍历。
  • 缺点: 系统架构复杂度显著增加;需要维护额外的服务;数据同步有延迟(异步);运维成本高。

📌 总结与建议

  • 绝对禁止 KEYS: 总是用 SCAN 替代。
  • 优先考虑设计优化: 这是最根本的解决方案。思考业务查询需求,通过设计可查询的键结构(索引集合、Sorted Set、Hash 索引)将运行时模糊匹配转化为精确查找或小范围扫描。 这是“空间换时间”的经典应用。
  • 合理使用 SCAN
    • 对于无法避免扫描的场景,务必用 SCAN
    • 适当调整 COUNT 值(如 500-1000),在单次耗时和总网络往返次数之间找到最佳平衡点。
    • 在客户端处理好游标迭代。
  • 考虑缓存: 对结果变化慢、查询频繁的模式,缓存 SCAN 结果。
  • 评估外部索引: 当数据量大、查询模式复杂、性能要求极高时,认真考虑引入 RediSearch 或 Elasticsearch 等专用搜索引擎。它们是为这类场景量身定制的。

关键权衡:

  • 内存 vs CPU/延迟: 优化设计(索引)会消耗更多内存,但极大降低查询延迟和 CPU 消耗(避免了遍历)。
  • 写入复杂度 vs 读取复杂度: 维护索引增加了写入/更新操作的复杂度(需要同时更新索引),但极大简化并加速了读取操作。
  • 实时性 vs 效率: 外部索引通常是异步更新,牺牲了一点实时性换取了强大的查询能力和可扩展性。

选择哪种策略取决于:

  • 你的数据规模
  • 查询模式的复杂度和频率
  • 对查询延迟的要求
  • 对数据实时性的要求
  • 可接受的内存开销
  • 系统的复杂度容忍度

务必根据你的具体应用场景进行设计和选择! 没有放之四海而皆准的最优解,但遵循“避免运行时扫描”的核心原则是关键。💪🏻

到此这篇关于redis中数据模糊查询scan用法详解的文章就介绍到这了,更多相关redis scan模糊查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis安全策略详解

    Redis安全策略详解

    缓存穿透是指当用户在查询一条数据的时候,而此时数据库和缓存却没有关于这条数据的任何记录,而这条数据在缓存中没找到就会向数据库请求获取数据。用户拿不到数据时,就会一直发请求,查询数据库,这样会对数据库的访问造成很大的压力
    2022-07-07
  • 使用宝塔在服务器上配置Redis的详细图文教程

    使用宝塔在服务器上配置Redis的详细图文教程

    这篇文章主要给大家介绍了关于使用宝塔在服务器上配置Redis的相关资料,包括下载和安装Redis,开放端口,修改配置文件以允许远程访问和设置密码,该过程对于理解Redis在项目部署中的配置提供了实用指导,需要的朋友可以参考下
    2024-11-11
  • Redis数据结构SortedSet的底层原理解析

    Redis数据结构SortedSet的底层原理解析

    这篇文章主要介绍了Redis数据结构SortedSet的底层原理解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Linux环境下升级redis的详细步骤记录

    Linux环境下升级redis的详细步骤记录

    这篇文章主要给大家介绍了关于Linux环境下升级redis的详细步骤,描述了如何从旧版本升级到新版本Redis,包括备份旧数据、下载和安装新版本、复制配置文件和数据、停止旧版本并启动新版本的过程,需要的朋友可以参考下
    2024-12-12
  • Redis中缓存穿透/击穿/雪崩问题和解决方法

    Redis中缓存穿透/击穿/雪崩问题和解决方法

    大家好,本篇文章主要讲的是Redis中缓存穿透/击穿/雪崩问题和解决方法,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下哦,方便下次浏览
    2021-12-12
  • 如何使用注解方式实现 Redis 分布式锁

    如何使用注解方式实现 Redis 分布式锁

    这篇文章主要介绍了如何使用注解方式实现Redis分布式锁,文章围绕主题展开详细的内容介绍,教大家如何优雅的使用Redis分布式锁,感兴趣的小伙伴可以参考一下
    2022-07-07
  • 基于session Redis实现登录

    基于session Redis实现登录

    这篇文章主要介绍了基于session Redis实现登录的相关资料,需要的朋友可以参考下
    2023-10-10
  • 使用Redis实现实时排行榜的示例

    使用Redis实现实时排行榜的示例

    为了实现一个实时排行榜系统,我们可以使用Redis的有序集合,本文主要介绍了使用Redis实现实时排行榜的示例,具有一定的参考价值,感兴趣的可以了解一下
    2025-04-04
  • Redis连接池配置方式

    Redis连接池配置方式

    文章介绍了Redis连接池的配置方法,包括与数据库连接时引入连接池的必要性、Java中使用Redis连接池的示例、jar包准备、编写配置代码以及连接池参数的设置
    2024-12-12
  • Redis使用RedisTemplate导致key乱码问题解决

    Redis使用RedisTemplate导致key乱码问题解决

    本文主要介绍了Redis使用RedisTemplate导致key乱码问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06

最新评论