如果你还停在 Redis 8.4 或更早,8.6 值得升,但目标版本必须是 8.6.5 或更新——8.6.3 与 8.6.5 分别修复了多个可能导致远程代码执行的漏洞。功能面上,8.6 最实质的变化是 XADD 支持幂等生产(IDMP / IDMPAUTO),让分布式生产者在重连或崩溃重启后重发消息时不再产生重复条目;此外还有 LRM 淘汰策略、HOTKEYS 热键定位和 TLS 证书自动认证。本文按「补丁优先级 → 新能力怎么用 → 升级门禁」的顺序展开。
Redis 8.6 带来了什么
按官方 8.6 发布说明,相对 8.4 的主要变化是:
- 显著的性能提升,以及 hash(hashtable 编码)与 sorted set(skiplist 编码)的显著内存下降
- Streams:
XADD幂等生产(至多一次投递保证),新增IDMPAUTO与IDMP参数 - 新增淘汰策略:最近最少修改(LRM)——
volatile-lrm与allkeys-lrm - 热键检测与上报,新增
HOTKEYS命令 - 基于 TLS 证书的客户端自动认证
- 时间序列支持 NaN,新增
COUNTNAN与COUNTALL聚合器
8.6.0 的 GA 时间是 2026 年 2 月,随后陆续发布了 8.6.1 到 8.6.5 五个补丁版本。
为什么必须停在 8.6.5,而不是 8.6.0
这是本次升级里优先级最高的一件事。8.6 系列的补丁不是可选的性能改进,而是包含多个高危安全修复。按发布说明记录:
| 版本 | 时间 | 紧急程度 | 关键内容 |
|---|---|---|---|
| 8.6.1 | 2026-02 | SECURITY | 攻击者可通过在错误回复中注入 \r\n 序列操纵连接读到的数据 |
| 8.6.2 | 2026-03 | SECURITY | 模块字符串回复的免拷贝路径存在潜在释放后使用;同时修复多个 IDMP 相关缺陷 |
| 8.6.3 | 2026-05 | SECURITY | CVE-2026-23479(解除阻塞流程 UAF)、CVE-2026-25243 与 CVE-2026-25588、CVE-2026-25589(RESTORE 非法内存访问)、CVE-2026-23631(Lua UAF),均可能导致远程代码执行 |
| 8.6.4 | 2026-06 | HIGH | AArch64 上 Redis 无法启动;XREADGROUP 消费者复制不一致;SENTINEL SET 配置注入;SCAN 的 COUNT 整数溢出;潜在 TCP 停滞与死锁 |
| 8.6.5 | 2026-07 | SECURITY | 构造的 stream RESTORE 载荷可让两个消费者共享同一个 NACK,导致释放后使用并可能远程代码执行;RedisBloom 与 TDigest 的构造 RESTORE 载荷可触发越界写 |
两点值得单独强调。
第一,8.6.4 修复的「AArch64 上 Redis 无法启动」是一个真实的部署阻断项。如果你的目标环境是 Graviton、Ampere 或 Apple Silicon 构建的镜像,跳过这个补丁会直接卡在启动阶段。
第二,多个高危漏洞的触发面是 RESTORE。这意味着任何允许不受信任来源投喂序列化载荷的路径都是攻击面——包括迁移工具、备份恢复流程和开放给外部的管理接口。升级之外,顺手复核一遍谁能调用 RESTORE 是划算的。
XADD 幂等生产:解决什么问题
这是 8.6 最值得单独学一遍的能力。
官方幂等消息处理文档把问题定义得很清楚:生产者需要重发消息的场景有两种。一是生产者与 Redis 之间网络中断——如果断连发生在执行 XADD 之后、收到回复之前,生产者无法判断消息是否已经写入。二是生产者崩溃重启——同样是在调用 XADD 之后、收到回复并标记「已投递」之前崩溃,重启后无从判断。
两种情况下,为了保证消息不丢,生产者必须重发。在 8.6 之前,重发的代价就是重复条目,去重责任全部落在消费端。8.6 把这件事下沉到了服务端。
机制是给每条消息关联一个幂等 ID(idempotent ID,简称 iid),配合生产者 ID(pid)构成去重键。
IDMP 与 IDMPAUTO 怎么选
XADD 的完整语法是:
XADD key [NOMKSTREAM] [KEEPREF | DELREF | ACKED]
[IDMPAUTO producer-id | IDMP producer-id idempotent-id]
[<MAXLEN | MINID> [= | ~] threshold [LIMIT count]] <* | id>
field value [field value ...]
两种模式:
XADD mystream IDMP producer-1 iid-1 * field value
XADD mystream IDMPAUTO producer-2 * field value
IDMP 是手动模式,pid 和 iid 都由你提供。iid 可以直接复用消息本身已有的标识——事务 ID、单调计数器或 UUID。它更快,因为不需要计算哈希,而且你对唯一性有完全控制权。如果这个 (pid, iid) 组合已经用过,命令会返回原条目的 ID,而不是创建重复条目。
IDMPAUTO 是自动模式,只提供 pid,由 Redis 根据字段值内容计算 iid。同样内容产生同样 iid。代价是多一次哈希计算,官方描述为「略慢」。
选择标准很直接:上游已经有稳定业务 ID 就用 IDMP。订单号、事件 ID、CDC 的 LSN 都是天然的 iid。只有在消息本身没有可靠标识、且内容确实能代表唯一性时才用 IDMPAUTO——注意后者的语义是「内容相同即视为重复」,如果你的消息体里含时间戳或随机字段,自动模式会失效。
两个模式共同的硬性要求:只能在条目 ID 为 *(自动生成)时使用,并且生产者应用重启后必须使用同一个 pid。pid 如果每次重启都变(比如用了容器实例 ID 或随机 UUID),幂等就是纸面上的,实际一条也去重不掉。这是落地时最常见的错误。
保留窗口怎么算
去重不是永久的,iid 有保留窗口,用 XCFGSET 按流配置:
XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 1000
IDMP-DURATION:iid 保留多少秒,取值 1 到 86400,默认 100IDMP-MAXSIZE:每个生产者最多跟踪多少个 iid,取值 1 到 10000,默认 100
服务端层面还有 stream-idmp-duration 与 stream-idmp-maxsize 两个配置项,作为流级别未显式配置时的默认值。
两个参数是「或」的关系:满足任一条件 iid 就会被移除。官方特别指出 IDMP-MAXSIZE 比 IDMP-DURATION 更强——Redis 绝不会为单个 pid 保留超过 IDMP-MAXSIZE 个 iid,即使还没到时间。
怎么定值?官方给了明确的思路。
IDMP-DURATION 是一个运维保证:在这段时间内,Redis 不会丢弃已经见过的 iid(除非撞到 MAXSIZE 上限)。所以它应该覆盖生产者从崩溃到恢复并重新开始发消息的最长时间。官方举的例子是:如果生产者崩溃后最长需要 1000 秒才能恢复并重发,就把 IDMP-DURATION 设为 1000。设得过高只是白白占内存。
IDMP-MAXSIZE 的定值取决于「标记延迟」(mark-delay)——生产者从拿到 XADD 回复,到把消息标记为已投递之间的时间。官方给的公式是:
IDMP-MAXSIZE = mark-delay(毫秒) × (消息数 / 毫秒) + 余量
官方例子:生产者发送 1K msgs/sec(即 1 msg/ms),标记每条消息最长需要 80 ms,那么 IDMP-MAXSIZE 应设为 1 × 80 + 余量 = 100。文档也提醒,这个数通常可以很小,很多时候一个就够了。
如果你的生产者是同步标记(拿到回复立刻写入事务库再继续),标记延迟接近零,默认的 100 绰绰有余。异步批量标记的场景才需要认真算。
生产者之间互不干扰
每个生产者独立跟踪,不同 pid 用相同 iid 不会冲突:
XADD mystream IDMP producer-1 iid-1 * field value
XADD mystream IDMP producer-2 iid-1 * field value
这意味着 iid 只需要在单个 pid 内唯一,不需要全局唯一。分片生产者可以各自用本地自增计数器,不必引入分布式 ID 服务。
怎么验证幂等真的生效
XINFO STREAM 在启用幂等后会返回额外字段:
XINFO STREAM mystream
idmp-duration/idmp-maxsize:当前生效的配置pids-tracked:当前跟踪的生产者数量iids-tracked:当前跟踪的 iid 总数iids-added:带幂等 ID 的消息累计数iids-duplicates:累计拦截的重复消息数
iids-duplicates 是最有价值的一个。上线后如果它长期为 0,而你的生产者确实经历过重连,那多半是 pid 不稳定导致幂等没生效,值得回头查。反过来,如果这个数异常高,说明生产者重发过于频繁,问题可能在网络或超时配置上。
pids-tracked 则要盯着看是否持续增长——它增长意味着新 pid 不断出现,通常就是「重启换 pid」这个反模式的信号。
三个必须知道的边界
第一,8.6.0 有一条已知限制。 发布说明明确写着:使用 appendonly yes 且 aof-use-rdb-preamble no(非默认配置)时,避免使用带 IDMP 或 IDMPAUTO 的 XADD,并注明该限制会在下一个补丁中移除。如果你的持久化配置恰好是这个组合,先确认目标补丁版本已解除限制,或者把 aof-use-rdb-preamble 调回默认。
第二,改配置会清空去重表。 官方明确说明:对某个键执行 XCFGSET 时,如果 IDMP-DURATION 或 IDMP-MAXSIZE 与当前值不同,会清空该键的 IDMP 映射。换句话说,运行期调参会开一个去重空窗。要改就在流量低谷改,或者接受那一瞬间的重复风险。
第三,开销很小但不是零。 官方给出的量化是:吞吐下降 2% 到 5%,额外内存占用低于 1.5%,单次操作延迟影响可忽略。手动模式(IDMP)因为省掉哈希计算,比自动模式略快。这个开销对绝大多数场景都可以接受,但如果你在跑极限吞吐的写入路径,值得在压测里单独确认。
好消息是持久化没有缺口:官方说明 RDB 与 AOF 都会保存所有 pid-iid 对,重启后跟踪继续有效,IDMP-DURATION 与 IDMP-MAXSIZE 配置也会持久化。
和任务队列的关系
Streams 加上幂等生产之后,用它承载任务队列的可行性明显提高了——生产端的重复投递问题在服务端解决,消费端只需要处理自己的幂等。但它仍然不等同于一个具备重试策略、依赖编排和可视化的工作流系统。什么时候用轻量队列、什么时候需要可恢复工作流,我们在FastAPI 后台任务选型里对比过三类方案的可靠性边界,判断标准同样适用于 Redis Streams。
如果你的场景是定时任务、任务依赖和失败重试的编排,而不是高吞吐消息流,用一个专门的调度器通常更省事。我们自己的 Cronova 就是这个定位:单二进制自托管,用 YAML 定义 DAG,内置 SQLite、Web Console、REST API 和实时日志,把重试和通知交给调度层而不是消息层。
LRM 淘汰策略适合什么场景
8.6 新增了「最近最少修改」(Least Recently Modified)淘汰策略。按官方淘汰策略文档,LRM 与 LRU 的区别只有一处,但这一处很关键:
- LRU:读和写都更新时间戳
- LRM:只有写操作更新时间戳,读不更新
对应两个策略值:allkeys-lrm 和 volatile-lrm。
官方给出的适用判断是:当你希望保留被频繁读取的数据、但淘汰掉近期没有被修改过的数据时使用。典型场景是读多写少的负载,你需要区分「正在被更新的活跃数据」和「只是一直被读的静态数据」。
举个具体的反差。用 allkeys-lru 时,一个被高频读取但从不更新的配置项会永远留在内存里;用 allkeys-lrm 时它会被当作陈旧数据淘汰掉,腾出空间给正在变化的数据。哪种更好完全取决于你的缓存语义——如果缓存值本身不会变,LRU 更合适;如果缓存的是可能过期的派生数据,LRM 能更快清掉陈旧副本。
实现上 LRM 与 LRU 一样是近似算法:随机采样一小批键,淘汰其中最久未修改的,同样受 maxmemory-samples 影响。
需要注意 volatile-* 系列的共同行为:如果没有任何键设置了 TTL,它们的表现等同于 noeviction,写入会直接报错。
顺带一提,缓存失效本身的正确性比选哪个淘汰策略更重要。发布内容和缓存版本怎么协同、Mongo 已提交但缓存失效失败时怎么办,这类问题我们在用 FastAPI、MongoDB 与 Redis 构建原子发布的双语内容系统里单独讨论过。
HOTKEYS 怎么定位热键
HOTKEYS 是 8.6.0 新增的容器命令,用来在指定的跟踪时间窗内识别热键。
官方定义的「热」有两个口径:
- 跟踪期内该键占用的 CPU 时间占总时间的百分比
- 跟踪期内该键消耗的网络字节(入 + 出)占 Redis 总网络字节的百分比
工作流是:启动跟踪 → 让它跑一段时间 → 取 Top K 结果。指标记录在一个概率型数据结构里。
四个子命令:
HOTKEYS START:按指定指标开始跟踪HOTKEYS STOP:停止跟踪但保留数据HOTKEYS GET:返回跟踪结果与元数据HOTKEYS RESET:释放跟踪占用的资源
排查思路上,CPU 口径和网络口径指向的问题不同。CPU 占比高的键通常是大集合上的重计算命令(大范围 ZRANGE、大 HGETALL);网络字节占比高的键往往是值体积大或调用频次高。前者优化命令与数据结构,后者优化缓存粒度或加本地缓存。
用完记得 HOTKEYS RESET 释放资源,不要让跟踪结构长期驻留。
TLS 证书自动认证
8.6 新增了基于 TLS 证书的客户端自动认证,配置项是 tls-auth-clients-user,并新增了 acl_access_denied_tls_cert 指标统计失败的证书认证尝试。
价值在于把客户端身份从「共享密码」迁移到「每客户端一张证书」。密码轮换需要同时改服务端和所有客户端,证书则可以按客户端独立签发和吊销,出问题时能精确定位到具体客户端而不是「某个知道密码的人」。
上线前把 acl_access_denied_tls_cert 接进监控——这个指标突然抬头,通常意味着证书过期或者签发链配置有问题。
升级流程与回滚门禁
Redis 8.6 在 Ubuntu 22.04/24.04、Rocky Linux 8.10/9.5、AlmaLinux 8.10/9.5/10.1、Debian 12/13、macOS 14/15 上测试,二进制分发覆盖 Docker 镜像、snap、brew、RPM 和 Debian APT。
建议的推进顺序:
- 先定目标版本,至少 8.6.5。不要以 8.6.0 为目标再计划后续补丁——那等于主动引入已知的 RCE 风险。
- 在预发环境验证启动,尤其是 AArch64 环境,确认 8.6.4 修复的启动缺陷不再出现。
- 确认持久化配置。如果打算用
XADD幂等,先检查appendonly与aof-use-rdb-preamble的组合是否落在 8.6.0 那条已知限制上。 - 淘汰策略不要跟着大版本一起改。先只做版本升级,观察一个完整业务周期,再单独评估是否切到 LRM。两件事一起改会让命中率变化无法归因。
- 幂等分阶段接入。先在一个非关键流上启用
IDMP,用iids-duplicates和pids-tracked验证行为符合预期,再推广。 - 准备回滚判据。明确到什么指标退化就回滚:连接错误率、
evicted_keys异常增长、命中率下降幅度、p99 延迟阈值。 - 复核
RESTORE的调用面。既然多个高危漏洞集中在这里,顺手确认哪些身份能执行它。
关于命中率的基线,INFO stats 里的 keyspace_hits 与 keyspace_misses 可以直接算:keyspace_hits / (keyspace_hits + keyspace_misses) × 100。升级前后各采一次,比凭感觉判断可靠。
常见问题
8.6 需要改客户端代码吗? 用新能力才需要。IDMP/IDMPAUTO 是 XADD 的可选参数,不写就是原有行为;LRM 是配置项;HOTKEYS 是运维命令。不启用新特性的话,客户端无需改动。
幂等能保证「恰好一次」吗? 不能。官方措辞是「至多一次生产」(at-most-once production),解决的是生产端重发导致的重复写入。消费端的重复处理仍然需要你自己做幂等。
iid 必须全局唯一吗? 不必。只需要在单个 pid 内唯一——不同 pid 使用相同 iid 是被明确支持的。
IDMPAUTO 能处理带时间戳的消息吗? 不能可靠处理。它按字段值内容计算 iid,消息体里只要有一个随机或时间相关的字段,重发时内容就变了,去重会失效。这类消息应该用 IDMP 显式提供稳定 iid。
LRM 会取代 LRU 吗? 不会,它是一个新增选项而不是替代品。读多写少、且要淘汰陈旧数据的场景更适合 LRM;「热数据被反复访问」这类经典模式仍然是 LRU 更合适。
升级会不会丢掉已跟踪的幂等状态? 正常升级不会——RDB/AOF 会持久化 pid-iid 对。但运行期用 XCFGSET 改动 IDMP-DURATION 或 IDMP-MAXSIZE 会清空该键的映射,这一点要与升级本身区分开。