前阵子我负责把一台跑了两年的 Redis 6.0.9 升级到 7.2.4,后来又把一套三主三从的集群也做了滚动升级。动手之前我以为这就是个替换二进制的活,真正做下来才发现,最花精力的不是编译,而是三件事:旧配置怎么翻译成新版本能认的格式、数据怎么备份才能保证升级失败还能回来、主从集群用什么样的顺序切换才不会丢请求。这篇文章就按我当时操作的完整路径来写,把你需要提前准备的信息、备份命令、编译步骤、配置迁移、滚动升级顺序、数据校验和回滚方案都列出来。如果你正在维护一台或多台 Linux 上的 Redis,准备做版本升级,这篇文章应该能帮你少踩几个坑。
1. 动手前先搞定三件事:需求判断、方案选型、基线信息
1.1 先算清楚这笔账:Redis 6.0 升 7.2 到底值不值
很多同学一听说 Redis 要升级,第一反应是“能用就行,别乱动它”。说实话,Redis 这种承担了缓存、队列、分布式锁的数据组件,升级确实有风险,但它不是没有理由。我这边升级的动机很直接:6.x 已经进入维护末期,安全补丁越来越少,而 7.2 是 LTS 版本,社区还在持续更新。对于承载核心业务缓存的组件来说,安全漏洞修复和长期维护支持本身就是硬需求,这个理由已经足够说服我了。
除了安全层面,Redis 7.x 相比 6.x 在生产运维上也有实打实的改进。7.0 开始 AOF 重写采用了 manifest + base + incr 三段式结构,大实例的磁盘压力明显下降;内存碎片整理、过期 Key 清理、主从同步这些机制也都做了优化。业务侧如果想要使用 Function 这样更完善的脚本机制,或者对 ACL 权限模型有更高要求,7.x 也是必经之路。我把两代版本拉了一张简单的对照表:
| 对比维度 | Redis 6.x | Redis 7.2 |
|---|---|---|
| 官方维护状态 | 维护末期,补丁频率下降 | LTS,仍在积极维护 |
| AOF 持久化结构 | 单一 AOF 文件 | manifest + base + incr 三段式 |
| 内存与 IO 优化 | 6.0 开始有多线程 IO | 碎片整理、过期清理更完善 |
| 脚本能力 | Lua 脚本 | 支持 Function 机制 |
| ACL 与安全 | 基础 ACL | 更细粒度、更稳定 |
不过我也见过为了升级而升级的团队,原本没有明确需求,反而把配置文件改错,上线后连接错误一堆。所以升级前先问自己三个问题:当前版本有没有无法绕过的 bug 或安全漏洞?业务需要的新特性是不是只有新版本支持?现有生态(客户端库、监控脚本、可视化工具、模块)能不能兼容新版本?如果三个答案都是“没有”,那就别动,稳定压倒一切。
1.2 升级方式选型:源码编译、二进制包、发行版仓库哪个靠谱
Linux 上装 Redis,来源无非就是发行版仓库、官方源码编译、第三方二进制分发这几类。发行版仓库最简单,apt install redis-server一条命令就装好,但版本往往滞后,而且升级路径不完全受自己控制。第三方二进制包虽然方便,安全审计时会比较麻烦,你很难确认这个包到底怎么构建的、有没有被塞过私货。
我自己在生产环境一直坚持官方源码包编译,核心原因是可控性和回滚便利性。源码包可以从官方地址下载,构建参数自己决定,还能装到独立目录,比如/usr/local/redis-7.2.4。这样旧版本的完整目录可以原样保留,升级只是切换 systemd 或启动脚本的指向,万一要回滚,把路径切回去就完成了。用包管理器升级的话,旧版本文件往往被覆盖,回滚就得重新下载旧包,麻烦不少。
当然了,源码编译也有它的门槛。编译机需要安装 gcc、make、pkg-config、tcl 这些基础工具,还要处理可能的MALLOC报错。如果生产服务器是一台非常老旧的 Linux 发行版,自带编译器版本太旧,源码编译可能过不了,这时候可以考虑在 Docker 里用较新的基础镜像编译好产物,再拷贝到目标机器。不过外部编译的二进制一定要做哈希校验和来源确认,别拿一个来路不明的文件替换生产组件。
1.3 升级前先给 Redis 拍一张体检照:基线信息不能凭感觉
升级不是把新版本装上就算完,你得能证明升级前后数据是一致的。所以动手之前必须先收集一套完整的基线信息。我习惯用一组 redis-cli 命令快速把实例的“体魄”记下来:
# 记录版本、运行模式、端口等 redis-cli -a '****' --no-auth-warning info server # 内存与碎片率 redis-cli -a '****' --no-auth-warning info memory # 命中率与每秒操作数 redis-cli -a '****' --no-auth-warning info stats # 持久化状态 redis-cli -a '****' --no-auth-warning info persistence # 主从复制状态 redis-cli -a '****' --no-auth-warning info replication # Key 总量 redis-cli -a '****' --no-auth-warning --scan --pattern '*' | wc -l # 大 Key 扫描,建议低峰期执行 redis-cli -a '****' --no-auth-warning --bigkeys # 慢查询记录 redis-cli -a '****' --no-auth-warning slowlog get 50这些命令输出里的关键信息,我通常会整理成一个小表:连接数、内存峰值、碎片率、Key 总量、最大 10 个 Key 的内存占用、慢查询条数、主从延迟、RDB 最近保存时间。升级后同样跑一遍,两边对比,这就是最朴素也最可靠的数据一致性证据。很多人升级完只确认“能连上、能 get”,却说不清有没有丢 Key,尤其在大实例上,抽样和总量核对必须做。
另外,别忘了把旧配置文件复制一份到备份目录,记录redis-server --version的输出。这一步看起来不起眼,但当你需要回滚的时候,它就是你的救命稻草。
2. 备份、编译与配置迁移:最容易翻车的三个环节
2.1 备份不是敲一条 BGSAVE 那么简单
备份这个环节,错误做法是直接在主库上执行 BGSAVE。BGSAVE 确实不阻塞主线程,但它要 fork 子进程,如果实例内存几十 GB,fork 的一瞬间主线程也可能出现明显的卡顿,对上游业务来说就是一次几十毫秒到几百毫秒的延迟毛刺。所以我的原则是:如果有从节点,备份操作一律打到从节点上执行,主库保持干净。
下面是当时用的完整备份序列:
# 在从节点上触发 RDB 后台保存 redis-cli -h 127.0.0.1 -p 6379 -a '****' --no-auth-warning BGSAVE # 轮询确认后台保存完成,rdb_bgsave_in_progress 会变为 0 redis-cli -h 127.0.0.1 -p 6379 -a '****' --no-auth-warning info persistence | grep rdb_bgsave_in_progress # 再次确认最近一次成功保存的时间戳 redis-cli -h 127.0.0.1 -p 6379 -a '****' --no-auth-warning LASTSAVE # 复制 RDB 文件到带日期的备份路径 cp /var/lib/redis/dump.rdb /backup/redis-dump-$(date +%F-%H%M).rdb # 如果 AOF 已开启,同时触发一次重写,保证文件干净 redis-cli -h 127.0.0.1 -p 6379 -a '****' --no-auth-warning BGREWRITEAOF文件复制出来后,不要直接扔在那里不管。用 Redis 自带的检测工具跑一遍:
redis-check-rdb /backup/redis-dump-YYYYMMDD-HHMM.rdb正常输出结尾会看到类似 RDB looks valid 的提示,这时候这份备份才算真正可信。还有一个细节,很多人会备份错路径,建议先用redis-cli config get dir确认 RDB 和 AOF 的真实存放目录,再去物理复制文件。同时估算磁盘空间:RDB 文件加临时文件加 AOF 重写需要的空间,至少留出两到三倍余量。
还有一点必须强调:RDB 格式在不同大版本之间并不保证向后兼容。Redis 7 能正常加载 Redis 6 产生的 RDB,但 Redis 7 一旦重新保存过数据文件,旧版本的 Redis 6 未必能读。这意味着升级前的那份 RDB 备份不能删,它是回滚路上唯一的“时光机”。
2.2 从源码编译 Redis 7.2 的完整过程
官方源码包下载和编译的步骤很标准化,熟练之后五分钟能跑完,但我还是建议一步步来,每一步都看一眼输出。以 Redis 7.2.4 为例:
# 安装编译依赖,Debian/Ubuntu 系 sudo apt update sudo apt install -y build-essential tcl pkg-config libsystemd-dev # 下载官方源码包 wget https://download.redis.io/releases/redis-7.2.4.tar.gz # 解压 tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 # 并行编译 make -j$(nproc) # 安装到独立目录,避免覆盖旧版本 make install PREFIX=/usr/local/redis-7.2.4如果需要启用 TLS 支持,编译时要追加make BUILD_TLS=yes,同时提前装好 libssl-dev。编译完成后,先确认版本号和二进制完整性:
/usr/local/redis-7.2.4/bin/redis-server --version如果 make 过程中报错,而且错误信息里能看到 malloc 或 jemalloc 相关字样,多半是当前环境的 jemalloc 源码编译不通过,可以改成系统自带的内存分配器:
make distclean make MALLOC=libc老旧 Linux 发行版自带 GCC 版本太低,也会让 Redis 7.x 编译失败,这种情况下我建议换成在 Docker 等较新的环境里编译好产物再拷过来。编译产物会被打进 Docker 镜像里,拷出时要保持目录结构完整。
安装完后,systemd 服务文件也需要更新。我当时的服务文件长这样:
[Unit] Description=Redis Server After=network.target [Service] Type=notify ExecStart=/usr/local/redis-7.2.4/bin/redis-server /etc/redis/redis.conf --supervised systemd ExecReload=/bin/kill -s HUP $MAINPID TimeoutStopSec=0 LimitNOFILE=65535 Restart=on-failure [Install] WantedBy=multi-user.target这里有一个很多人踩过的坑:如果使用了 systemd 的 Type=notify 模式,Redis 配置文件里的daemonize必须设置为 no,否则 systemd 会认为进程启动失败,陷入不断重启的循环。启动参数里加--supervised systemd就是为了让 Redis 以 systemd 通知模式运行。
2.3 配置迁移:旧参数哪些要删、哪些要改
升级过程中真正的隐形杀手是旧配置文件。我那次升级就撞上了一个典型问题:旧配置里写了aof-use-rdb-preamble no,结果新版本启动直接报Bad directive or wrong number of arguments。原因很简单,Redis 7.x 把 AOF 混合持久化固定为开启,这个开关已经被移除了,旧配置反而成了绊脚石。
所以配置迁移的正确姿势不是拿旧 conf 直接启动,而是先做兼容性检查。我把生产环境的旧配置和官方 7.2 示例配置做了一次 diff,把差异逐项过一遍,重点检查这几类:
| 检查项 | 升级前要确认的事 |
|---|---|
| daemonize / supervised | systemd 环境下必须 daemonize no |
| aof-use-rdb-preamble | 新版本已固定开启,配置里出现会报错 |
| rename-command | 确认命令名在新版本中没被移除或改名 |
| ACL 文件路径 | users.acl 文件权限和格式是否兼容 |
| TLS 配置 | 证书路径、协议参数有没有变更 |
| dir / logfile / pidfile | 目录是否存在且有写权限 |
检查完之后,我强烈建议先在前台启动一次:
redis-server /etc/redis/redis.conf --daemonize no前台启动时,所有配置错误、依赖问题都会直接打在日志里。等看到 Ready to accept connections 字样,再 Ctrl+C 停掉,然后交给 systemd 正式启动。这一步看似多花三十秒,实际能省下后面一晚上的排查时间。
3. 升级实施与验证:单机与集群的实操顺序
3.1 单机升级执行步骤,照着做不会乱
单机升级的路径相对简单,但每一步都有它的目的。我按当时操作的顺序整理如下:
- 通知业务方,确认升级窗口内没有写入流量,或者已经由网关层把写请求切到其他集群。Redis 升级窗口内最理想的状态是只读、零流量。
- 执行上一节讲到的备份流程,RDB 和 AOF 都备份,并且跑完 redis-check-rdb。这一步没有完成之前,绝对不要碰服务。
- 停止 Redis 服务:
sudo systemctl stop redis。如果服务器上同时存在 init.d 脚本和 systemd 服务,要确认你用的管理方式和实际启动方式一致,避免停了个寂寞。 - 备份原可执行文件目录:
sudo mv /usr/local/redis /usr/local/redis-6.0.9。保留原始目录是回滚的基础。 - 把新版本安装到 /usr/local/redis-7.2.4,修改 systemd 服务文件里的 ExecStart 路径。
- 前台试启动,确认没有配置报错,再通过 systemd 正式启动。
- 启动后立刻查看日志,确认 Ready to accept connections 出现,没有 ERROR 级别输出。
- 做数据校验和功能回归,确认通过后再切业务流量。
- 业务连接地址、密码、证书切换到新实例,先灰度一小部分请求,观察客户端日志没有异常再全量放量。
我见过有人在第 6 步偷懒,直接systemctl start。结果旧配置某个参数不兼容,新进程反复退出,systemd 也一直在重启,日志里全是 Bad directive,场面非常混乱。前台启动一遍能把这些报错一次性亮出来,处理起来从容很多。
3.2 主从与集群滚动升级:顺序决定了会不会丢数据
对于简单的主从复制架构,升级顺序的原则是先从后主。先把所有从节点升级到新版本,确认从节点的复制 offset 已经追上主节点,再挑一个从节点提升为主。以 Redis 官方命令来说,就是:
# 确认从节点追上主节点 redis-cli -a '****' --no-auth-warning info replication | grep -E 'role|master_repl_offset' # 手动触发一次无损故障转移 redis-cli -a '****' --no-auth-warning CLUSTER FAILOVER执行 CLUSTER FAILOVER 后,原来的从节点会变成主节点,旧主节点降级为从。此时把旧主节点也升级到新版本,整个主从架构就完成了滚动升级。这个顺序最重要的一点是:在切换过程中,始终有一个可用的主节点在承担写入,不会出现同时把两个节点都停掉的空窗期。
如果是完整的三主三从 Redis Cluster,操作逻辑类似但更讲究节奏。开始之前先跑一次:
redis-cli -a '****' --no-auth-warning --cluster check 127.0.0.1:7000确认cluster_state:ok,所有分片都健康。然后把每个分片的从节点逐个升级,每升级完一台,都要等它把数据同步追平,再继续下一台。最后逐个对主节点做 FAILOVER,再升级原主节点。整个过程中不要同时让两个主节点处于重启状态,避免集群内部触发选举抖动。过渡期间不同版本节点共存是集群滚动升级的正常状态,但别让新旧版本长期混跑,全部升级完成后才算真正稳定。
3.3 数据一致性校验与功能回归清单
升级完成后,最紧张的时刻来了:数据到底有没有丢。我一般分两层来验证。第一层是总量对比:
# 升级前记录过的 Key 总量,升级后再跑一次 redis-cli -a '****' --no-auth-warning --scan --pattern '*' | wc -l # 每个库的 Key 数分布 redis-cli -a '****' --no-auth-warning info keyspace如果升级前是 83 万个 Key,升级后变成了 82 万,那就说明有问题,哪怕只差一个也要查到底。第二层是抽样比对,挑几个业务核心 Key,对比类型、TTL、值是否一致:
for k in user:1001 user:1002 order:20240101:1001; do echo "== $k ==" redis-cli -a '****' --no-auth-warning type "$k" redis-cli -a '****' --no-auth-warning TTL "$k" redis-cli -a '****' --no-auth-warning get "$k" done数据验证通过之后,还要做一轮业务功能回归。很多同学只测 get/set,但 Redis 里还有 List、Hash、ZSet、Stream 一堆数据结构,老版本有一种编码叫 ziplist,新版本改成了 listpack,虽然数据能读出来,但接口行为、内存占用可能都不一样。我每次升级都会快速把各类型都操练一遍:
| 数据类型 | 验证操作 |
|---|---|
| String | set、get、incr、expire |
| Hash | hset、hgetall、hlen |
| List | lpush、lrange、llen |
| Set | sadd、smembers、scard |
| ZSet | zadd、zrangebyscore、zscore |
| Stream | xadd、xread、xlen |
| Bitmap | setbit、getbit |
| HyperLogLog | pfadd、pfcount |
| Geo | geoadd、geodist |
业务侧还要单独验证两个常见场景:一个是缓存治理相关的淘汰策略,我会设置一个很小的 maxmemory,触发allkeys-lru,确认 Redis 能正常选 Key 淘汰并且不报错;另一个是分布式锁,用SET lock:key uuid NX PX 10000模拟加锁,确认只有持锁方才能通过 Lua 脚本或事务完成 DEL,防止误删。这两类场景是生产里最常见的用法,漏测概率不高,但一旦出问题影响面很大。
最后,再用可视化客户端,比如 Redis Desktop Manager 或者 Another Redis Desktop Manager 连一次,看连接信息、慢日志、内存图表是否正常,确保运维团队日常使用的工具链也没断档。
4. 问题排查、回滚与升级后的日常养护
4.1 高频报错与排查实录
升级过程中我记忆里最深的几个报错,整理成了一张速查表,给后来人当参考:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| make 报错,包含 malloc/jemalloc 关键字 | 当前环境编译 jemalloc 失败 | make distclean后make MALLOC=libc |
| 启动报 Bad directive | 旧配置含新版本移除或改名的参数 | 对照 7.2 官方示例配置逐一修正 |
| 日志显示 Can't open the log file | logfile 目录不存在或权限不足 | 创建目录并 chown 给运行用户 |
| 本地连接被拒绝 | bind 或 protected-mode 限制 | 确认进程在跑,再按要求调整 bind 和 protected-mode |
| 客户端报 READONLY 错误 | 主从切换后客户端仍连着旧主节点 | 客户端或代理层按集群 slot 重定向 |
| 磁盘满导致 RDB 保存失败 | 备份文件或 AOF 重写占满磁盘 | 清理磁盘,修复持久化路径 |
还有一个小经验:升级后立刻关注日志里的 WARNING 和内存碎片率。新版 Redis 的内存碎片整理算法和 jemalloc 版本都有变化,RSS 出现短期波动是正常现象,先观察activedefrag是否开启,不要一看到内存上涨就急着回滚。客户端协议层面也要注意,Redis 7.x 默认仍是 RESP2,老的客户端库基本都兼容,千万不要为了尝鲜马上把连接切换到 RESP3,除非你已经确认客户端库完整支持 RESP3 的推送和类型系统,否则会看到一堆协议解析错误。
4.2 回滚预案:升级前不准备,出事时只能赌
升级这件事,我给自己立过一个硬性要求:升级方案和回滚方案必须同时准备,而且回滚方案要写成步骤,贴到交付文档里。操作前五分钟再读一遍回滚步骤,比临时翻历史命令靠谱得多。
单机环境的回滚其实不复杂:
- 停止新版本进程。
- 把二进制目录从 /usr/local/redis-7.2.4 切回原来的 /usr/local/redis,也就是恢复旧版本可执行文件。
- 恢复旧配置文件。
- 把新版 Redis 写入过的 dump.rdb 和 AOF 文件先移走,再复制回升级前备份的那份 RDB/AOF。
- 启动旧版本,立刻做数据校验。
关键点在第 4 步。很多人回滚只换了二进制,却忽略持久化文件已经被新版本动过。Redis 7 重新保存后的 RDB,Redis 6 未必能读,如果直接把旧二进制配上新生成的 RDB 启动,很可能加载失败或者加载出完全乱掉的数据。所以回滚时必须连数据文件一起回退。
对于集群环境,回滚的原则是“保存主战场”。如果在滚动升级中段发现异常,立即停止后续节点的升级,已经升级过的节点可以留在集群里继续当从节点,但核心数据节点保持在旧版本不动。这样即使后面要回滚,也只是把已升级的节点再切回来,不会影响主数据链路。要记住,回滚会丢失升级完成后产生的增量写入,因此升级窗口必须足够短,业务写入要能暂停或者切换。
4.3 升级后的运维习惯:版本升级不是终点
Redis 升级完成、业务验证通过,我通常还会做两件事:补一波日常运维规范,把这次的经验沉淀下来。第一件事是建立定期备份机制。光有一次升级前的备份不够,我建议每天定时对从节点执行 BGSAVE,保留最近 7 天的 RDB 文件,每周跑一次 redis-check-rdb。备份的存在意义不取决于文件在不在,而取决于能不能恢复,定期在临时环境做一次恢复演练,才能真正验证备份链路。
第二件事是大 Key 治理和缓存治理的常态化。升级后我重新跑了一次redis-cli --bigkeys,把超过阈值的大 Key 列出来,和业务方逐个确认是否可以拆分、压缩或者做冷热分层。Redis 是内存数据库,放任大 Key 膨胀就是在给自己埋雷。缓存治理上,我会重新审视 maxmemory 和淘汰策略,给每台实例留出内存应急缓冲,避免某个热点 Key 突发写入直接打满内存。
慢日志也不要忽略。升级后慢查询分布变了,用SLOWLOG GET 100看一遍耗时靠前的命令,把slowlog-log-slower-than设置成合适的阈值,保持对性能问题的敏感度。版本和安全公告那边,定期看一眼 Redis 官方 release notes 和 CVE 列表,不要把升级拖成一年一次的大工程,小版本有安全补丁就及时打上,大版本跨越时再走完整的升级演练。
我个人在实际操作里最深的体会是:升级这个动作本身不复杂,复杂的是你得知道每一步为什么这么做。备份为什么一定要在从节点做,启动为什么一定要先前台跑一遍,回滚为什么一定要连持久化文件一起处理,把这些“为什么”想清楚,版本升级就从一件让人紧张的事,变成了一个可以写进 SOP 的常规操作。后面我计划把整套流程整理成自动化脚本,以后再做 Redis 升级,就是一件不慌不忙、按部就班的事。