Redis 常用命令时间复杂度:哪些操作会悄悄拖慢实例

Redis 很快,但“快”并不等于所有命令都是 O(1)。 一次 GET 通常很轻,一次 ZRANGE 可能返回十万个成员,一次 SINTER 可能扫描几个大集合,一次 DEL 也可能为了释放大 key 在主线程上忙很久。它们都只是一条 Redis 命令,对实例造成的压力却完全不同。 这也是线上 Redis 延迟偶尔尖刺时最容易被忽略的地方:QPS 没有明显上涨,CPU 看起来也不算很高,但某个请求做了一次与数据规模相关的 O(N) 操作,后面的请求就一起排队了。 这篇文章按 Redis 的常用数据类型整理时间复杂度。目的不是背完整张命令表,而是学会看到命令时迅速判断两件事:它会扫描多少数据,又会返回多少数据。 如果还不熟悉 Hash、quicklist、intset、listpack 和 skiplist 的关系,可以先看Redis 底层数据结构。底层结构正是这些复杂度的来源。 读复杂度之前,先认识几个变量 后面的表格会反复使用这些字母: 变量 含义 K 一次命令传入的 key 数量 N 当前集合、列表、Hash 或 ZSet 的元素总数 M 本次返回、删除或处理的元素数量 S 为了到达起始位置需要跨过的元素数量 B 需要读取、复制或传输的字节数 比如 ZRANGE rank 0 9 的复杂度是 O(log N + M):先在有序结构中定位起点,再返回 M 个成员。排行榜里有一百万个人并不可怕,只取前十名依然很轻;真正危险的是 ZRANGE rank 0 -1,因为这时 M 也变成了一百万。 还有三个容易被大 O 记号藏起来的事实。 ...

September 4, 2026 · 6 min · Icyyan

Redis Pipeline 与 Lua:减少网络往返和保证原子性是两回事

假设下单时需要在 Redis 里做三件事:读取库存、判断是否足够、扣减库存。 第一次写这段逻辑,很容易想到 Pipeline:把几条命令一次发给 Redis,既然它们挨在一起执行,是不是就不会被其他请求插进来了? 答案是否定的。 Pipeline 解决的是网络往返太多,Lua 脚本解决的才是多步逻辑不能被其他命令打断。两者都能让客户端“一次请求完成多件事”,但它们处理的是完全不同的问题。 理解这一区别之后,Redis 里很多看似相近的概念都会顺起来:为什么批量写缓存适合 Pipeline,为什么扣库存适合 Lua,为什么事务和 Pipeline 经常同时出现在客户端 API 里,以及“Lua 是原子的”到底原子到什么程度。 先用一张表分清四种工具 工具 主要解决什么 多条命令会不会被其他客户端插入 能否根据中间结果继续判断 Pipeline 减少 RTT,提高吞吐 不保证 不适合 MULTI/EXEC 原子执行一组已经确定的命令 不会 很弱,命令只是先排队 WATCH + MULTI/EXEC 乐观锁与条件更新 提交后不会,冲突时提交失败 可以,但通常需要重试 Lua 脚本 在服务端完成读、计算、写 不会 可以 还有一个经常被忽略的选择:如果 Redis 已经有单条命令能完成需求,优先用单条命令。例如计数用 INCRBY,不存在才写入用 SET ... NX。单命令通常比自己拼事务或 Lua 更简单,也天然具有原子性。 Pipeline 为什么快:它省掉的是 RTT 客户端执行一条 Redis 命令,大致要经历下面这段路: 客户端发送命令 -> 网络传到 Redis -> Redis 执行命令 -> 结果通过网络返回 -> 客户端收到结果 如果依次执行 100 条命令,客户端通常要等待 100 次网络往返。即使命令本身只执行几十微秒,网络等待也可能占掉大部分时间。 ...

September 4, 2026 · 4 min · Icyyan

请不要用 Redis 做任何缓存之外的事

Redis 是程序员工具箱里很容易被“过度信任”的东西。 它太顺手了。 想存个值,SET 一下。想自动过期,EXPIRE 一下。想做计数器,INCR 一下。想搞队列,LPUSH / BRPOP 一下。想搞锁,SET NX PX 一下。再看一眼 Stream,好像连消息系统也能顺手安排。 于是很多系统就这么一路滑坡: 第一天:Redis 只是缓存。 第二天:这个状态先放 Redis 吧。 第三天:队列也先放 Redis 吧。 第四天:库存扣减也先放 Redis 吧。 第五天:Redis 挂了,大家开始翻日志考古。 这篇文章想讲的观点很简单: Redis 能做很多事,但不要让它单独拥有真相。 如果一份数据不能丢、不能错、不能重复处理、不能悄悄过期、不能被内存策略淘汰,那它就不应该只待在 Redis 里。 Redis 最舒服的位置是缓存。它可以让系统跑得更快,但不应该决定系统还能不能正常活下去。 先看边界:缓存可以重建,事实不能只靠 Redis 这不是 Redis 不行。 Redis 很强,而且越来越强。它有 RDB,有 AOF,有复制,有 Sentinel,有 Cluster,有 Streams,还有很多高级数据结构。Redis 8 之后,JSON、Search、Time Series、概率数据结构等能力也被合进 Redis Open Source。 所以问题不是“Redis 能不能干更多活”。 问题是:你把活交给 Redis 之后,能不能接受它的失败方式。 缓存的失败方式很朴素: Redis 没了 -> 缓存 miss -> 回源数据库 -> 重新写缓存 这很烦,但不至于让业务当场失忆。 ...

May 23, 2026 · 3 min · Icyyan

Redis 底层数据结构:一个 key 背后到底藏着什么

很多人第一次学 Redis,记住的是五种常用类型:String、List、Hash、Set、ZSet。再往后背面试题,又会遇到 SDS、quicklist、listpack、intset、skiplist、hashtable。 这些名字放在一起很容易让人迷糊:Redis 不是 key-value 数据库吗?为什么一个 Hash 后面还会有 listpack 和 hashtable 两种实现?为什么同样是 Set,有时是 intset,有时又变成哈希表? 真正要理解的不是“Redis 有哪些数据结构”这张清单,而是一个更具体的问题: Redis 为什么要让同一种对外类型,在不同场景下切换不同的内部编码? 顺着这个问题往下看,Redis 的底层数据结构会清楚很多。它不是为了把实现弄复杂,而是在内存、CPU、查询速度、扩容成本之间做了一组很工程化的取舍。 写这篇文章时(2026 年 7 月,版本信息核对于 7 月 21 日),Redis Open Source 的最新稳定版是 8.8.0,2026 年 5 月 25 日 GA,预发布阶段亮相的新数据结构 Array 也在这一版正式落地;8.6 线的最新补丁是 8.6.4。 从底层数据结构这条线看,Redis 8.6 以来最值得关注的不是“又多背几个结构名”,而是大 Hash、大 ZSet 的内存继续被优化,以及 Stream 幂等写入、HOTKEYS、key 内存大小直方图、LRM 淘汰策略这些贴近日常排查的新能力。这些在后文对应的小节里都会具体展开。 对外类型和内部编码不是一回事 平时我们写 Redis 命令,面对的是对外类型: SET name bole LPUSH queue a b c HSET user:1 name bole age 18 SADD tags redis cache ZADD rank 100 alice 这些命令分别对应 String、List、Hash、Set、ZSet。但 Redis 在内存里不会只保存“这是一个 Hash”这么简单的信息。 ...

May 23, 2026 · 6 min · Icyyan