news 2026/10/2 19:23:50

Redis模糊查询全解析:从KEYS阻塞到SCAN与索引设计实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis模糊查询全解析:从KEYS阻塞到SCAN与索引设计实战

如果你在业务代码里写过KEYS user:*,那你大概体会过那种“上线前好好的,一压测 Redis 就报警”的酸爽。Redis 的模糊查询一直是个很矛盾的话题:需求太常见,官方又不推荐用KEYS直接扫。很多人被问到时第一反应是“用 KEYS 不就完了?”,但真正到线上环境跑过一次百万甚至千万级别的 Key 时,才发现这个命令能把单线程 Redis 卡到客户端集体超时。这篇文章我把 Redis 模糊查询这件事完整过一遍:从命令模型、KEYS和SCAN的原理差异,到怎么用数据结构设计索引来实现字段级模糊搜索,再到一个可以直接参考的用户信息搜索完整实例,最后附上我在生产环境踩过的几个坑。无论你是刚接触 Redis 的新手,还是已经在用但想系统梳理一遍的老手,都应该能从里面找到能直接落地的东西。

1. 先搞清楚 Redis 里的“模糊查询”到底是什么

1.1 Redis 的命令模型决定了查询只能走 Key

Redis 本质上是一个 Key-Value 存储系统,它不像 MySQL 那样有表、有列、有 SQL 解析器。你访问任何数据的第一步都是先拿到 Key,然后通过 Key 去取 Value。这个特性决定了:在 Redis 语境里,模糊查询绝大多数时候指的是“对 Key 做模式匹配”,而不是“对存储内容做条件查询”。

举个例子。你把用户信息存成了user:info:1001这样的 Hash,里面有个字段叫name: "zhangsan"。如果你想“按用户名模糊查”,直接跑KEYS user:info:*是拿不到的——因为这个命令只会匹配 Key 本身,不会去看 Hash 里面某个字段的值。这跟 MySQL 里的WHERE name LIKE '%zhang%'完全是两回事。

很多刚接触 Redis 的人在这里会栽跟头,因为他们潜意识里还在用关系型数据库的思维。Redis 的模糊查询其实只有两种实现路径:

  • 第一种,让 Key 本身携带可搜索的信息,这样KEYS或SCAN就能匹配到。
  • 第二种,额外维护一套索引结构(Set、ZSet、Hash 等),先模糊匹配索引,再通过索引拿到真实数据的 Key。

搞懂这个底层模型,后面的所有方案就都顺理成章了。

1.2 Glob 风格匹配符:*?[]的用法

Redis 的KEYS和SCAN系列命令,匹配规则用的是类 Glob 风格,不是正则表达式。常用的符号就这几个:

符号含义示例匹配结果
*匹配任意多个字符(包括 0 个)KEYS user:*匹配user:1001,也匹配user:name:zhang
?匹配单个字符KEYS user:1??匹配user:100、user:123,不匹配user:12
[abc]匹配方括号中的任意一个字符KEYS order:[1,2]*匹配order:1001、order:2001开头的 Key
[a-z]匹配指定区间内的一个字符KEYS log:[a-d]*匹配log:a1、log:d2,不匹配log:e1
\转义符KEYS user:name\*匹配字面量user:name*

需要注意两个点。第一,匹配是区分大小写的,KEYS User:*不会匹配user:*。第二,*在最前面会导致效率很低,比如KEYS *zhang*,这一点在下一节讲阻塞问题时还会放大。

1.3 最常见的模糊查询场景

从实际业务来看,Redis 模糊查询主要集中在三类场景:

  • 清理缓存:按某个固定的业务前缀批量删除 Key,比如session:*、cache:product:*。
  • 后台导出或排查:运营或开发需要根据某个关键词列出相关 Key,人工确认问题。
  • 构造小型索引:比如把用户名拼进 Key 或 Set 成员里,实现简单的搜索能力。

无论场景是什么,心里要先有个分类:这个查询是“临时排查”还是“线上高频调用”?这两者的技术选型完全不同。临时排查用KEYS可能问题不大,但线上接口如果敢直接调KEYS,轻则超时重试,重则引发雪崩。下一节我就详细说这个事。

2. KEYS 命令:演示很方便,上生产前先想清楚

2.1 KEYS 的写法和返回结果

KEYS的语法极其简单:

KEYS pattern

比如:

KEYS user:info:* KEYS order:2024:* KEYS user:name:zhang*

执行后,Redis 会返回一个数组,包含所有匹配的 Key。如果匹配到的 Key 数量很多,返回结果会一次性全部输出,在客户端上可能还要再吃一波内存。在redis-cli里小数据量时很好用,看起来也直观,但它的问题不在语法上,而在执行机制上。

2.2 单线程阻塞:为什么几百万 Key 时它会卡死业务

Redis 的服务端命令执行是单线程的。所谓单线程,是指所有命令在核心执行路径上严格串行排队。KEYS的复杂度是 O(N),其中 N 是当前数据库里的 Key 总数,而不是匹配到的数量。

这个点非常关键。哪怕你的 pattern 是user:info:zhang*,实际能匹配上的 Key 只有 1 个,KEYS也需要把整个 Key 空间从头到尾过一遍才能确定“没有其他匹配项”。在一个有 1000 万个 Key 的实例上跑一次KEYS,意味着 Redis 在命令执行的这几秒内,没办法处理任何其他读写命令。所有请求都会排在这个KEYS后面,然后客户端大量超时,连接堆积,最终把 CPU、内存、网卡全部拖垮。

我见过最典型的线上事故是这样的链路:Redis 出现慢查询,其中一条是KEYS user:*,执行了大概 8 秒。这 8 秒里,正常业务的读写全部排队,Lettuce 客户端开始报RedisCommandTimeoutException,应用层触发重试,重试的请求又继续排队,最后 Redis 的 connected_clients 从几百涨到几千,整个集群进入恶性循环。

2.3 一个可以自己复现的压测脚本

如果你没亲眼看过KEYS的杀伤力,建议在自己的测试环境复现一次。用 Docker 起一个 Redis 实例:

docker run -d --name redis-demo -p 6379:6379 redis:7-alpine

然后造一批 Key,比如 10 万个:

(for i in $(seq 1 100000); do printf "SET user:info:$i:user$i value\r\n"; done) | redis-cli --pipe

接着用time命令对比一下KEYS和后面要讲的SCAN:

time redis-cli KEYS 'user:info:*:user12*'

10 万 Key 时,KEYS可能还不到 100 毫秒,感受不明显。你可以把数量加到 50 万甚至 100 万,然后观察 Redis 的redis-cli --latency输出,你会发现执行KEYS的瞬间,延迟曲线直接拉平到秒级。这个复现成本很低,但带来的感官冲击很值得体验一次——尤其是当你意识到生产环境可能是千万级 Key 的时候。

2.4 什么样的场景才允许用 KEYS

我个人的红线是这样的:

  • 本地开发环境或测试环境,数据量很小,用KEYS没毛病。
  • 线上环境,数量明确在几千以内的低频运维操作,可以接受,但要有心理准备。
  • 线上环境,千万级 Key、高频接口、批量任务,绝对禁止用KEYS扫 Master。

另外有人会想说“我在从库上跑KEYS行不行?不影响主库”。不行。从库虽然不直接服务写请求,但命令执行也是单线程的,KEYS同样会阻塞从库的复制和读请求,甚至可能拉大主从延迟,照样出问题。

3. SCAN 游标遍历:生产环境唯一推荐的通配查询方式

3.1 SCAN 的基本语法和游标机制

SCAN是KEYS的“增量遍历”替代方案。核心思路很简单:不一次性扫描全库,而是每次只遍历一部分,通过游标(cursor)记录进度,多次迭代直到遍历完成。

语法:

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]

第一次调用时,cursor 传 0:

SCAN 0 MATCH user:info:* COUNT 200

返回结果有两部分:一个是下一次要用的游标值,一个是本次命中的 Key 列表。当返回的游标为 0 时,表示遍历结束。

游标不是页码。它内部对应的是 Redis 哈希表的一个槽位指针,每次调用会从当前位置往后扫若干槽位。所以你不能说“SCAN 到第 2 页”,游标只能从上次返回的值继续往下传。

3.2 MATCH、COUNT 这两个参数最常见的误解

先说 MATCH。SCAN 0 MATCH user:info:* COUNT 200里的 MATCH,并不是先在全局范围内筛选出匹配的 Key 再返回,而是在遍历槽位的过程中,对遇到的每个 Key 做模式匹配,匹配的就放进结果里。这意味着:即使整个库里匹配的 Key 很少,只要 Key 空间很大,你仍然需要遍历很多槽位才能完成一次完整扫描。

再说 COUNT。COUNT 默认值是 10,它代表的是“单次迭代要遍历的哈希桶数量”,不是“返回多少条结果”。很多人以为设置COUNT 1000就一定能返回最多 1000 条 Key,这是一个非常常见的误解。实际上单次返回多少条,取决于这 1000 个桶里有多少 Key 命中了 MATCH 条件。匹配率低的时候,COUNT 1000返回 0 条都很正常。

我整理了一个表,方便对照:

参数常见误解正确理解
MATCH先缩小扫描范围遍历过程中的过滤条件
COUNT返回结果条数单次迭代遍历的哈希桶数量
游标页码内部遍历位置标记,为 0 表示结束
返回结果完整、无重复rehash 期间可能重复,客户端要去重

3.3 HSCAN / SSCAN / ZSCAN:对不同结构内成员做模糊匹配

除了对全库 Key 做遍历,Redis 还提供了针对某个具体结构内部的增量扫描命令:

  • HSCAN key cursor [MATCH pattern] [COUNT count]:遍历 Hash 的 field,返回 field-value 成对出现。
  • SSCAN key cursor [MATCH pattern] [COUNT count]:遍历 Set 的成员。
  • ZSCAN key cursor [MATCH pattern] [COUNT count]:遍历 ZSet 的成员,返回 member-score 成对出现。

举个例子。假设我把用户名索引维护在 Set 里,成员格式是name:id:

SADD user:name:index zhangsan:1001 lisi:1002 wangwu:1003

现在想找所有以zhang开头的用户,可以这样:

SSCAN user:name:index 0 MATCH zhang* COUNT 100

同样,cursor 返回 0 才算遍历完。这套命令组合在 4.2 节会派上大用场。使用它们的注意点和 SCAN 一样:如果这个 Hash / Set / ZSet 本身很大,比如单成员几百万,那么 HSCAN / SSCAN / ZSCAN 单次也可能比较耗时,但它依然是渐进式的,不会像 KEYS 那样一次性占死 CPU。

3.4 使用 SCAN 时的几个正确习惯

根据我的使用经验,写 SCAN 相关代码时最好固定养成这几个习惯:

  • 用 Set 在客户端去重。SCAN 在哈希表 rehash 期间,可能出现重复返回同一个 Key 的情况。
  • 循环遍历直到游标为 0,不要只调一次就以为拿全了。
  • 遍历过程中 Key 可能被新增或删除,SCAN 不保证快照一致性,这是正常现象。
  • 如果是对匹配结果做批量删除,优先用UNLINK而不是DEL。UNLINK在 4.0+ 是异步删除,可以避免一次性删除大 Key 时让 Redis 卡顿。
  • 集群模式下要逐个节点跑,然后合并结果,这个坑我在 6.4 节展开说。

4. 让数据结构为模糊查询服务:键名设计与索引方案

4.1 键名设计:把需要检索的字段拼进 Key

既然 Redis 的模糊查询默认匹配的是 Key 本身,那最简单的思路就是:设计 Key 的时候,把可能需要搜索的字段放进去。

常见的格式是用冒号分层,比如:

user:info:{id}:{name} order:info:{orderId}:{userId}

拿第一个来说,如果要按名字模糊查,直接:

SCAN 0 MATCH user:info:*:zhang* COUNT 200

这种做法的优点是实时性好,写入时不需要额外维护索引,查的时候直接按前缀过滤。缺点是 Key 会变长,而且如果业务里要改名字,就得同步处理 Key,否则旧 Key 和新 Key 会不一致。

我的建议是:不要把所有字段都往 Key 里塞。Redis 的 Key 越长,占用的内存越多,而且每个层级都用冒号分隔,会在大批量遍历时增加匹配开销。一般只把最高频查询的那一两个字段拼进去,其他字段还是老老实实存在 Hash 里。

4.2 Set/Hash 索引实现字段级模糊查询

如果用户数据已经存在 Hash 里,Key 本身是user:info:1001,Value 里才有name等字段,这种情况用 SCAN 扫业务 Key 是查不到内容的。正确做法是额外建一个索引。

比如维护一个 Set:

SADD user:name:index zhangsan:1001 lisi:1002 wangwu:1003

成员格式统一是名字:ID。做模糊查询时:

SSCAN user:name:index 0 MATCH zhang* COUNT 500

拿到zhangsan:1001后,解析出 ID 1001,再通过 Pipeline 批量HGETALL user:info:1001,就能拿到完整用户信息。

这套方案的优点是结构简单,写入时多做一次 SADD 而已。缺点是当这个 Set 膨胀到几十万、上百万成员时,SSCAN 虽然不会阻塞 Redis 太久,但整体遍历时间会变长,查询耗时不可控。遇到这种情况,就需要做分桶。

4.3 ZSET 字典序索引与 ZRANGEBYLEX 的前缀匹配

一个比较高级的优化方案是利用 ZSet 的字典序特性。ZSet 的成员如果分数全部相同,Redis 会自动按照成员字符串的字典序排列。在这个前提下,可以用ZRANGEBYLEX按字典序区间来取数据,速度比SSCAN的全量遍历快很多。

假设索引结构是:

ZADD user:name:zset 0 zhangsan:1001 0 lisi:1002 0 wangwu:1003

查询所有以zhang开头的成员,可以这样:

ZRANGEBYLEX user:name:zset [zhang (zhang{ LIMIT 0 20

这里的小技巧:[zhang代表闭区间起点,(zhang{代表开区间上界。因为(后面是{,而{的 ASCII 码是 123,比所有常规字母和数字的 ASCII 码都大,所以任何以zhang开头的常规字符串,字典序都小于zhang{。这样就能圈出一个准确的前缀区间。

需要提醒一句:这个技巧依赖 ASCII 边界,如果业务数据里包含{、|、}这类高位字符,上界要换成更保守的哨兵字符,或者干脆用 SSCAN 兜底。另外 ZRANGEBYLEX 要求所有成员的分数保持一致,否则字典序排列不成立,返回结果会变得不可预测。

4.4 什么时候应该换掉 Redis 另起炉灶

写到这里我必须泼一盆冷水:Redis 的模糊查询,本质上都是在“全量遍历”框架下的妥协方案。SCAN 只是把一次性的阻塞拆成了多次小步快跑,并没有把 O(N) 变成 O(logN) 或 O(1)。如果业务需要的是高频、复杂条件组合、结果集还要排序分页的“搜索引擎”,Redis 不是合适的载体。

我的选型经验是这样的:

  • 简单前缀查询、缓存清理、小规模索引(万级到十万级):用 SCAN / SSCAN / ZRANGEBYLEX 这套。
  • 中等规模、查询条件单一但量大(百万级):可以考虑分桶索引 + Redis,但设计和运维成本会明显上升。
  • 复杂查询、全文搜索、多条件过滤、大数据量:直接上 MySQL 的 LIKE + 索引,或者 Elasticsearch,不要让 Redis 去干它不擅长的事。

在 Redis 里硬造一个功能残缺的搜索引擎,前期看着爽,后期维护起来会非常痛苦。

5. 一个完整的用户信息模糊搜索实例

5.1 需求与方案选型

这部分我给出一个可以直接抄作业的实例。假设我们有一个用户管理后台,需求是:输入一个关键词,对用户名做前缀模糊匹配,展示匹配用户的信息(ID、姓名、邮箱、年龄),支持分页。

数据结构我设计成三层:

  • 用户详情:user:info:{id},Hash 结构,字段包括name、email、age、createdAt。
  • 用户名索引:user:name:index:{首字母},Set 结构,成员格式是name:id,按用户名首字母分桶。
  • 之所以按首字母分桶,是为了避免所有用户名都堆在一个 Set 里。比如zhangsan:1001分到z桶,lisi:1002分到l桶,这样每个桶的规模大概是总用户数除以 26,遍历效率会高很多。

先说明为什么不直接在业务 Key 上做 SCAN:用户详情 Key 是user:info:1001,里面存的是 Hash 字段,SCAN 匹配不到 Hash 里的name。所以必须依赖单独的索引结构。为什么不直接用一个总的user:name:index:用户量到几十万以后,单个 Set 的 SSCAN 耗时还是偏长,分桶后单桶只有一两万成员,毫秒级就能遍历完。

5.2 写入流程与索引维护

注册一个新用户时,需要同时写入用户详情和索引。为了保证一致性,建议用 Lua 脚本原子执行。

-- KEYS[1] = user:info:{id} -- ARGV[1] = name -- ARGV[2] = email -- ARGV[3] = age -- ARGV[4] = id redis.call('HSET', KEYS[1], 'name', ARGV[1], 'email', ARGV[2], 'age', ARGV[3]) local first = string.lower(string.sub(ARGV[1], 1, 1)) redis.call('SADD', 'user:name:index:' .. first, ARGV[1] .. ':' .. ARGV[4])

如果支持用户名修改,需要额外处理:先读出旧名字,从旧首字母桶里 REM 掉旧成员,再往新首字母桶里 SADD 新成员。如果直接用的 Jedis 或 Spring Data Redis,也可以在 Java 代码里用事务包裹这几个操作,但 Lua 更干净,也能避免客户端多次 RTT。

5.3 模糊查询核心代码

查询流程分三步:定位分桶 → 对桶内 Set 做 SSCAN 模糊匹配 → 拿到 ID 列表后批量取 Hash 详情。

我用 Spring Data Redis 写一个可直接参考的 Java 实现:

public List<Map<Object, Object>> searchByUserName(String keyword, int offset, int limit) { if (keyword == null || keyword.isEmpty()) { return Collections.emptyList(); } String first = String.valueOf(Character.toLowerCase(keyword.charAt(0))); String bucketKey = "user:name:index:" + first; ScanOptions options = ScanOptions.scanOptions() .match(keyword + "*") .count(500) .build(); List<Long> ids = new ArrayList<>(); Cursor<byte[]> cursor = stringRedisTemplate.execute( (RedisCallback<Cursor<byte[]>>) connection -> connection.sScan(bucketKey.getBytes(StandardCharsets.UTF_8), options)); while (cursor.hasNext()) { String member = new String(cursor.next(), StandardCharsets.UTF_8); // member 格式 name:id,按最后一个冒号切分拿到 id int idx = member.lastIndexOf(':'); if (idx > 0 && idx < member.length() - 1) { ids.add(Long.parseLong(member.substring(idx + 1))); } } // 候选集去重后做内存分页 List<Long> pageIds = ids.stream().distinct() .skip(offset) .limit(limit) .collect(Collectors.toList()); if (pageIds.isEmpty()) { return Collections.emptyList(); } // 批次取 Hash 详情,用 pipeline 减少 RTT List<Object> details = stringRedisTemplate.executePipelined( (RedisCallback<Object>) connection -> { for (Long id : pageIds) { connection.hGetAll(("user:info:" + id).getBytes(StandardCharsets.UTF_8)); } return null; }); return details.stream() .map(obj -> obj instanceof Map<?, ?> ? (Map<Object, Object>) obj : Map.of()) .collect(Collectors.toList()); }

这里有几个细节需要特别解释。

第一,分页方式:SCAN / SSCAN 家族没有“偏移量”的概念,游标是遍历位置,不是过滤后的结果序号。所以最稳妥的办法是先把候选 ID 集合全部拿到内存,再用skip和limit做分页。当候选集不大时(比如前缀zhang*只有几十上百人),这个方案完全够用。如果关键字是a*这种能命中十几万的宽泛前缀,内存分页就会很吃力,这种场景需要考虑换搜索引擎,或者把索引升级成 ZSet 再用 ZRANGEBYLEX 做区间分页。

第二,批量读取:拿到分页 ID 之后千万不要逐条 HGETALL,那会产生大量 RTT。上面用executePipelined,一次网络往返把整页数据全部取回。如果存的是 String 结构,还可以直接用 MGET 一把梭。

5.4 性能分析、分页与批量读取的边界

这套方案在 50 万用户量级下,实测效果很不错。假设用户名均匀分布在 26 个桶里,每个桶约 2 万成员。一次SSCAN user:name:index:z 0 MATCH zhang* COUNT 500,通常几次迭代就能遍历完整个桶,耗时在毫秒级。拿到 ID 后流水线 HGETALL,一页 20 条用户详情,整体接口耗时一般不会超过 10 毫秒。

但边界也要心里有数:如果业务形态偏“后缀模糊”或“包含模糊”,比如用户输入ang要匹配zhangsan,这套方案直接失效,因为分桶的前提是“前缀首字母清晰”。后缀模糊、中间模糊,是 Redis 模糊查询的天敌。遇到这种需求,要么把常见后缀也建索引,要么换 Elasticsearch。

6. 我在生产环境踩过的模糊查询相关的坑

6.1 KEYS 阻塞引发命令超时雪崩

我第一次在线上看到“Redis command timed out”这个异常时,人还是懵的。那是一个运营后台的导出功能,代码里为了按条件捞出所有相关用户 Key,直接写了KEYS user:*。当时线上 Redis 大概 1000 万 Key,这条命令执行了 8 秒左右。8 秒内所有业务读写全部排队,Lettuce 客户端大量超时,服务端堆积的请求又继续压进 Redis,最终 CPU 打满、连接数暴涨。

排查链路是这样的:先看监控确认 Redis 的慢查询,通过SLOWLOG GET看到几条巨大的KEYS记录,然后对照服务端日志的时间点,发现超时异常和慢查询完全吻合。处理方案不复杂:把KEYS改成SCAN,导出任务改成异步分批执行;同时把运营后台的查询接口加了一层 Redis 命令白名单,凡是模糊查询统一走封装好的 Scan 工具类。从那以后我很确定一件事:工具类代码里最容易藏雷,后台功能也要按生产标准来写。

6.2 可视化工具扫 Key 把 Redis 扫卡了

有一次更冤。某个测试同学为了排查问题,用 Redis Desktop Manager 连上了生产环境的 Redis,点了一下“刷新 Key 列表”,瞬间 Redis CPU 飙到 90% 以上。原因就是老版本的可视化工具在刷新 Key 列表时,底层执行的是KEYS *,相当于把全库所有 Key 从头到尾扫了一遍。

现在的工具基本都支持 SCAN 遍历,比如 Another Redis Desktop Manager 在设置里可以选“Use SCAN”之类的选项。但生产环境的安全实践应该是这样的:不允许开发测试人员直接连生产 Redis;连上了也尽量不要用图形化工具去浏览全量 Key;如果确实需要排查,用命令行的 SCAN 模式,或者让 DBA 执行。工具是无辜的,关键是要让使用工具的人知道命令背后发生了什么。

6.3 SCAN 返回结果少于预期与重复 Key

SCAN 有一个非常容易让人困惑的行为:明明库里有 100 个匹配的 Key,你设置COUNT 500,结果第一次调用只返回了几个,甚至返回 0 个。

这不是 Bug,而是 COUNT 的语义问题。COUNT 代表的是扫描的哈希桶数量,不是返回条数。匹配率低时,哪怕翻了 500 个桶,也可能一个匹配项都没有。正确做法是循环迭代,直到游标返回 0 为止。

另外,SCAN 在哈希表 rehash 期间可能返回重复 Key。我见过有同事拿 SCAN 结果直接去删数据,同一个 Key 被删两次,因为第二次会报“no such key”,程序直接抛异常。正确做法是先放到 Set 里去重再处理。这两个问题看起来是大白话,但在开发中踩到的人真的不少。

6.4 集群环境下 SCAN 需要每个节点分别跑

最后说一个集群模式的坑。Redis Cluster 的 Key 分散在多个 slot 和多个节点上,单条SCAN命令只能遍历当前连接到的那个节点。用 Spring Data Redis 的connection.scan(options)在集群下直接调用,你会遇到两种情况:要么只返回了部分节点上的 Key,要么直接报跨 slot 错误。

正确做法是从 ClusterTopology 里拿到所有主节点的连接,对每个 master 分别执行 SCAN,最后在应用层合并结果。类似 JedisCluster 在新版本里提供了封装好的 scan 方法,但如果你用的是 Lettuce 或者 Spring Data Redis,还是老老实实自己遍历节点更可控。我有一个建议:把“集群全量扫描+去重”这段逻辑封装成一个独立的工具类,不要散落在业务代码各处。因为踩过一次坑之后,你会发现这类逻辑每个项目都会用到。

说实话,Redis 模糊查询这件事本身并不复杂,复杂的是你愿不愿意在设计阶段就想清楚:这个查询是给在线用户用的,还是给自己排查用的;是需要实时全量遍历,还是可以接受索引一致性维护成本。把这些边界想明白之后,SCAN、SSCAN、分桶索引、ZSet 字典序这些工具才能各就各位。希望这篇分享能让你少走一趟我走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 19:23:05

3.14复试冲刺指南:从笔试面试到调剂的全流程准备策略

每年这个时候&#xff0c;考研人都会盯着日历上的一个个数字计算时间节点&#xff0c;"3.14 复试学习"这个标题&#xff0c;说白了就是围绕3月14日这个关键日期展开的复试冲刺准备。很多学校的复试通知会在3月中上旬密集发布&#xff0c;初试成绩出来之后有二十几天到…

作者头像 李华
网站建设 2026/10/2 19:23:04

一文带你吃透C++继承

1.1继承的概念继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构&#xff0c;体现…

作者头像 李华
网站建设 2026/10/2 19:22:34

无需计算p(x)的贝叶斯分类器:从生成式到判别式的统一视角

1. 从“没有 p(x)”说起&#xff1a;贝叶斯分类器到底在算什么 很多人第一次学贝叶斯分类器&#xff0c;脑子里都会被一个公式钉死&#xff1a;后验概率正比于似然乘以先验&#xff0c;也就是 (p(y|x) \propto p(x|y)p(y))。然后紧接着教材就会告诉你&#xff0c;分母 (p(x)) 是…

作者头像 李华
网站建设 2026/10/2 19:22:11

Java遍历全攻略:从数组到二叉树,彻底搞懂遍历方式与坑

提到Java遍历&#xff0c;我第一反应不是去背API&#xff0c;而是先问一句&#xff1a;你要遍历的到底是什么结构&#xff1f;是数组、List、Set还是Map&#xff1f;遍历过程中要不要删除元素&#xff1f;数据量大不大&#xff1f;需不需要并行&#xff1f;这些听起来像面试题&…

作者头像 李华
网站建设 2026/10/2 19:21:52

机器人空间描述与坐标变换:旋转矩阵、欧拉角与齐次变换

1. 先把问题摆清楚&#xff1a;机器人为什么非要和坐标系较劲带过几届做机器人方向的学生和实习生&#xff0c;我发现一个挺有意思的规律&#xff1a;真正让大家在入门阶段卡住的&#xff0c;往往不是后面的雅可比矩阵&#xff0c;也不是动力学方程&#xff0c;而是第一章的空间…

作者头像 李华
网站建设 2026/10/2 19:21:42

5G高负荷判定:联合流量与用户数的资源利用率计算

简介&#xff1a;针对5G网络高负荷小区的识别与优化需求&#xff0c;这份由通信网络工程师整理的规范文档&#xff0c;系统定义了第五代移动通信网络中高负荷状态的判定标准&#xff0c;面向无线优化工程师、网络规划与运维人员&#xff0c;可辅助完成容量评估、扩容决策及多场…

作者头像 李华