在 Linux 上做 Redis 升级,表面看只是把二进制换新,实际牵涉数据安全、配置兼容、主从复制甚至回滚预案。我这些年帮线上环境处理过不少 redis 版本升级,从 3.x 往 4.x、5.x 再往 6.x、7.x 都碰到过,真正出问题的往往不是替换二进制那一步,而是加载旧数据、重放 AOF、主从重新握手这些后续环节。准备做 linux 上 redis 升级的同学,别急着下载和覆盖,先把下面这套判断逻辑和操作流程过一遍。
1. 升级前必须想清楚的三件事
1.1 升级目标到底是什么:补丁升级还是大版本跨越
很多人上来就问“怎么升级”,但这里要先分清楚升级的类型。
补丁升级是指同一个大版本内的小版本前进,比如 6.2.7 升到 6.2.14,往往是为了修复安全漏洞、解决特定 bug。这类升级风险很低,配置和数据格式基本不变,最核心的动作就是替换二进制、平滑重启。
大版本升级则是跨代前进,比如 5.0 升到 6.2,或者 6.2 升到 7.0。这类升级会带来新特性(ACL 访问控制、多线程 IO、Redis Functions、更合理的集群管理),代价是部分配置项语义变化,某些旧命令或参数被标记废弃,模块兼容性也要重新验证。所以我不建议看到新版本就闭眼升,先回答三个问题:业务用到了哪些 Redis 特性?这些特性在目标版本里是否有变化?客户端连接库是否兼容新版协议?
如果只是一个内部缓存服务,升级追求的是稳定,那补丁升级足够。如果是因为新业务需要 Redis Functions、需要更细粒度的权限控制,才值得做一次大版本升级。目标决定方案,这一步想清楚,后面才不会白忙。
1.2 数据安全:不只是备份文件,还要考虑复制拓扑
Redis 升级唯一不能接受的就是丢数据。很多同学备份时只知道复制 dump.rdb,其实这里有很多细节。
先检查当前持久化配置。执行redis-cli info persistence看rdb_bgsave_in_progress、rdb_last_bgsave_status,以及aof_enabled这个字段。如果只开了 RDB,那么备份前最好手动执行一次BGSAVE,等LASTSAVE时间戳刷新后再复制文件。如果开了 AOF,我建议顺手做一次BGREWRITEAOF,让 AOF 文件体积变小,升级加载时更快,也能降低文件损坏的概率。
然后是备份范围。至少要把这几样东西完整复制到独立目录或另一台机器:
- RDB 文件(默认 dump.rdb)
- AOF 文件(默认 appendonly.aof)
- 当前 redis.conf 和 ACL 用户文件
- 启动脚本、systemd unit 文件
- 如果启用了 TLS,证书和私钥也要备份
另一个容易忽略的是复制拓扑。如果 Redis 是主从架构,升级顺序不应该是一台一台同时动,而应该先升级从库,确认复制稳定后再做切换。记录当前主从关系的run_id、master_repl_offset、connected_slaves数量,这些数据在升级后用来对照,能快速判断是否出现了数据回退。
1.3 配置兼容性:新版可能直接不认旧配置
这是大版本升级里最隐蔽的坑。Redis 不同版本之间某些配置项会改名、改变默认值甚至被删除。
举个例子,Redis 7.0 里 ziplist 编码全面被 listpack 取代,所以旧配置里hash-max-ziplist-entries、list-max-ziplist-size、zset-max-ziplist-entries这些指令在新版本直接报错。我见过有人把 6.x 的 redis.conf 原封不动给 7.x 用,启动日志第一行就报 “Bad directive or wrong number of arguments”,还以为下载的包有问题。
标准的做法是:用新版自带的 redis.conf 作为基础,把旧配置里的自定义项逐条挪过去。比较关键的几个配置是requirepass、masterauth、rename-command、maxmemory-policy、appendonly、save、cluster-enabled。如果启用了 ACL,一定要检查默认用户和业务用户是否在目标版本中仍然合法。比如 Redis 7 对 ACL 规则做了更严格校验,老版本能通过的规则,新版本可能直接拒绝启动。
模块也是一样。很多企业会加载布隆过滤器、JSON、搜索引擎这类扩展模块,模块编译时绑定的 Redis 版本 API 和内部数据结构,升级后很可能不兼容。升级前先确认模块是否有支持目标版本的版本,没有就别急着升级主库,先在测试环境验证。
2. 升级方案选型:二进制替换、源码编译还是系统包管理器
2.1 三种方式分别适合什么场景
Redis 在 Linux 上常见升级方式有三条路:解压官方二进制直接替换、源码编译、用系统包管理器安装。
官方二进制适合绝大多数 x86_64 和 ARM64 的云服务器,优点是部署快、参数和官方一致,不用在机器上装编译工具链。缺点是如果你内网环境比较封闭,需要提前把安装包和校验值传进去。
源码编译适合追求自定义的场景,比如要打开 TLS 支持、要链接特定版本的 jemalloc,或者系统 glibc 版本比较老导致官方二进制跑不起来。缺点很明显,需要 gcc、make、OpenSSL 开发头文件,编译时间也长,生产环境上如果没有充分的自动化流程,我不太推荐现场临时编译。
系统包管理器适合对 rpm/deb 包统一管理的团队,升级命令简单,但版本更新往往滞后于官方,而且某些发行版仓库里的 Redis 并不包含全部特性,比如缺少 TLS。用包管理器还有一个隐患,它会强制管理配置文件,你之前手工改过的配置可能被覆盖。综合来看,我习惯在生产环境用官方发布包或源码编译,把版本目录固定下来,好处是可控、可回滚。
2.2 准备工作里的细节:架构、校验、依赖检查
下载前先确认系统架构,执行uname -m,x86_64 和 aarch64 的包不能混用。拿到安装包后别急着解压,先做 SHA256 校验,防止传输过程中文件损坏或被人篡改。这一步看着麻烦,但真能救你一次。
解压后不必直接 make install,先把新版本目录放到一个规范位置,比如/opt/redis-7.0.15。然后检查动态库依赖:
ldd /opt/redis-7.0.15/redis-server如果输出里有not found,说明系统缺失某些运行库,最常缺的是 OpenSSL 相关库。这种情况要么补装依赖,要么重新编译一个不带 TLS 的版本。
还有一个细节我吃过亏:不要盲目用make install把新版本覆盖到/usr/local/bin/redis-server这样约定俗成的路径。旧服务可能还指着它运行,一旦覆盖,回滚就很被动。正确做法是保持旧目录不动,新版本放新目录,用 systemd unit 或者启动脚本里的绝对路径去切换。
2.3 保留旧启动脚本和旧目录的价值
我这里强烈建议保留旧版本的完整目录。很多升级事故不是因为新版本不好,而是回滚的时候找不到干净的旧包。旧目录不删除,旧配置文件不覆盖,万一升级后出现诡异问题,把启动脚本指回旧目录就能在几分钟内恢复服务。
有人觉得保留两份 Redis 目录占空间,其实一个压缩过的 Redis 二进制就几十 MB,比起数据安全性这点占用完全值得。我会在升级变更单里记录三个路径:旧版本目录、新版本目录、备份文件存放目录。出现问题时,顺着记录就能快速还原现场。
3. 完整实操:一次不丢数据的 Redis 升级
3.1 步骤一:收集现场信息和完整备份
我把它称为“动手前拍照”。升级前需要记录一组基线数据,将来对照用:
# 查看当前版本和运行时信息 redis-cli -p 6379 info server # 查看持久化状态 redis-cli -p 6379 info persistence # 查看主从复制状态 redis-cli -p 6379 info replication # 查看配置文件中真正修改过的项 redis-cli -p 6379 config get maxmemory maxmemory-policy appendonly save注意,不要直接在命令行用-a传密码,命令会被 shell history 记录。推荐设置环境变量REDISCLI_AUTH=你的密码,或者使用交互式认证。
拿到基线数据后,手动触发一次持久化并等待完成。RDB 场景执行BGSAVE,然后用LASTSAVE确认快照时间已经是刚才;AOF 场景执行BGREWRITEAOF,再用info persistence里的aof_rewrite_in_progress判断是否结束。最后进入配置里的dir指定目录,把 dump.rdb、appendonly.aof 复制到备份位置。
如果机器上运行着多个 Redis 实例,要记录端口和持久化文件的对应关系。我遇到过有人备份了 AOF 文件,恢复时才发现拿成了另一个实例的,数据完全对不上。
3.2 步骤二:解压新版本并做冷启动验证
这一步很多人会跳过,但我强烈建议不要跳。把新版本解压到独立目录后,用一个临时端口、临时目录和配置文件副本启动一次,纯粹为了验证新版本能不能正常加载持久化文件。
mkdir -p /tmp/redis-test cp /your-backup/dump.rdb /tmp/redis-test/ cp /your-backup/appendonly.aof /tmp/redis-test/ /opt/redis-7.0.15/redis-server --port 16379 --dir /tmp/redis-test --save ""注意这里我把save设置成空,避免冷启动验证过程中把临时实例的数据写盘。启动后观察日志,看 RDB 加载和 AOF 重放是否正常。然后用这个临时端口执行扫描,抽查几个业务 key 是否存在:
redis-cli -p 16379 --scan --pattern "user:*" | head -50 redis-cli -p 16379 dbsize这个冷启动验证的成本很低,但它能把“持久化文件损坏”“新老版本数据格式不兼容”“配置文件参数错误”这类问题提前暴露在正式停机之前。我在一次升级里就是靠这一步发现 AOF 文件尾端有少量损坏,提前用redis-check-aof --fix修复,避免了一次线上事故。
3.3 步骤三:正式切换并平滑重启
完成冷启动验证后,进入正式切换流程。这里我区分单实例和主从两种情况。
单实例升级,即使有备份,也要选在业务低峰期。执行redis-cli shutdown save优雅关闭,确认进程退出后,替换启动脚本中的二进制路径,或者直接重命名新版本目录下的 redis-server 到服务期望的位置。然后启动服务并观察日志。如果服务是 systemd 管理,改完 unit 文件后先systemctl daemon-reload再启动。
主从架构的升级,我倾向按这个顺序走:
- 先选一个从库,停掉实例,替换二进制,启动恢复复制。
- 确认这个从库的
master_link_status:up且复制偏移量和主库一致。 - 在业务允许的情况下,让这个从库执行
REPLICAOF NO ONE,提升为新主库。 - 把业务连接切到新主库,把原主库执行
REPLICAOF 新主库IP 新端口,让它降级为从库。 - 最后再升级原主库二进制,恢复完整主从关系。
这样做的好处是,每个环节都有旧节点兜底,即使某一步失败,也能把流量切回原主库。缺点是需要业务配合切换连接,但如果只是滚动升级从库,也可以不做角色切换,逐个从库升级即可。
3.4 步骤四:启动后的核心验证清单
启动不等于成功,验证至少要持续一段时间。我给自己定的验证清单是这样:
先确认进程和版本:
ps -ef | grep redis-server redis-server --version redis-cli -p 6379 ping再看运行时状态是否和基线一致:
redis-cli -p 6379 info persistence redis-cli -p 6379 info replication redis-cli -p 6379 dbsizerdb_last_bgsave_status要是 ok,aof_last_write_status要是 ok,主从实例的master_repl_offset要持续增长而不断开。用几个业务常用命令做冒烟测试,比如写入一个临时 key、读取、设置过期、删除。如果启用了集群,执行cluster info看cluster_state:ok。
我还会在升级后的半小时内多观察几次内存变化。新版 Redis 对某些数据结构的编码方式不同,可能出现内存上涨或下降,如果内存上涨幅度超过预期,要警惕是不是发生了持久化 fork 或碎片问题,必要时用redis-cli memory doctor和info memory分析。
3.5 步骤五:回滚方案要提前写明白
很多人觉得做好备份就万事大吉,但回滚不是简单把旧二进制放回去。回滚的关键是“写入轨迹”要可控。
回滚操作一般是这样:
- 先停止业务写入,执行
redis-cli shutdown nosave关闭新版本。 - 把旧版本的二进制放回服务路径,使用旧配置文件启动。
- 启动后加载的持久化文件来自哪一份,取决于备份策略。如果升级期间业务还在写入且没有重新备份,回滚会丢失这段时间写入的数据。所以升级窗口越短越好,必要时升级前要明确告知业务方停止写操作。
- 如果新版本运行期间没有异常,只是你怀疑有隐患,回滚后要立刻对比
dbsize和主从偏移。
回滚不可怕,可怕的是回滚后丢数据。所以我的经验是,升级窗口内不允许业务写入,升级完成验证通过后再恢复写入;如果升级窗口内无法停写,就必须依赖主从架构成对地做故障切换,而不是单节点回滚。
4. 常见问题与排查技巧实录
4.1 启动报错 “Bad directive or wrong number of arguments”
这个报错 90% 是配置文件里写了目标版本不认识的指令。新版 Redis 对配置文件的解析很严格,一行都不放过。排查方法很简单,看日志里报错的行号,用新版本默认配置文件逐行对照。如果是大版本升级,直接把旧配置里 ziplist 相关项、已废弃项删掉,然后重新测试。
在正式切换前做冷启动验证的原因就在这里,如果新版本读不了旧配置,冷启动阶段就会发现,不会弄到线上停机才发现。如果线上已经改了配置,临时逃生手段是先用最简参数启动服务,再用CONFIG SET逐项写入,最后用CONFIG REWRITE落盘。
4.2 加载 RDB 或 AOF 时间太长
大实例升级时,最让人焦虑的就是日志一直停留在 “Loading RDB” 或 “Reading the append only file”。这个过程不能强行中断,否则文件可能损坏。如果你的实例特别大,要提前规划停机窗口,RDB 加载速度取决于磁盘读速度和数据结构复杂度,几 GB 的文件耗时可长可短。
想减少加载时间,平时就要控制持久化文件体积。升级前做一次BGREWRITEAOF非常有效,AOF 重写会把历史操作合并成最小的命令集合。另外,升级期间关闭持久化文件的自动重写,避免外部 IO 干扰加载过程。
如果文件已经损坏,先用工具修复。官方源码目录里有redis-check-rdb和redis-check-aof,不要直接在原文件上操作,先复制一份再修。
4.3 主从升级后复制中断
最常见的原因有三个:认证信息没配、ACL 权限不对、版本差异导致同步参数变化。检查要点是:
- 新从库配置文件里有没有正确的
masterauth,对应主库的requirepass。 - ACL 用户是否允许
replication相关权限,很多升级后 redis 的默认用户被严格限制,从库同步直接被拒绝。 - 新版本主库的
repl-diskless-sync行为发生变化时,从库可能拿不到数据。
排查手段是看从库日志,找MASTER <-> REPLICA sync相关记录。如果复制偏移量不再增长,对比主从的info replication字段。必要的时候,最土但有效的方法是重新执行一遍REPLICAOF 主库IP 端口,强制触发全量同步。但要注意,全量同步对主库磁盘带宽有压力,低峰期再做。
4.4 升级后内存策略和淘汰行为不一样
Redis 7.0 之后,内存编码和内部结构调整,同一个 key 在内存中的占用可能比 6.x 略小或略大,这本来是正常现象。真正危险的是 maxmemory 设置得很满,升级后内存稍微上涨就触发逐出策略,缓存命中率断崖式下降。
我在升级前都会看maxmemory和实际 used_memory 的差距,如果差距小于 20%,会临时调高上限,或者提前清理一批不重要的缓存,给新版本一个缓冲空间。升级稳定后再把 maxmemory 回调到正常值。注意调整 maxmemory 时,如果当前maxmemory-policy是noeviction,内存满了之后写入会直接报错,这类实例要特别小心。
4.5 命令行为变化和客户端兼容
大版本升级后,某些命令对相同输入的响应会不同。比如老版本 Redis 对过期 key 的某些操作可能返回错误,新版本可能返回 nil 或者抛出更明确的异常。如果客户端代码里做了很细的错误处理,升级后可能触发意想不到的分支。
我建议升级前把客户端依赖库也升级到支持目标版本的版本,至少在测试环境跑一遍常用功能的自动化用例。不要只看 Redis 服务端没问题,客户端和服务器版本差距太大的组合,经常会出现协议层面的小问题。
5. 一些我踩过的坑和最终建议
5.1 最容易出问题的时间点
根据我的经验,整个升级过程中,风险最高的不是替换二进制那几秒钟,而是新版本加载持久化文件后的前五分钟。加载完成后,后台可能在执行过期 key 清理、AOF 重写、主从全量同步这些任务,它们会同时抢占 CPU 和磁盘资源。如果此时业务流量已经切过来,极端情况下会出现 latency 飙升。
所以升级后不要急着把全部流量切过来,先让新实例在没有写入压力的情况下运行一段时间,观察一轮完整的后台任务执行,比如主动触发一次BGSAVE看耗时是否正常。
5.2 建议做一个临时实例验证数据文件
这个方法我前面提过,但值得单独再说一次。在正式切换前,用临时端口加载备份出来的数据文件,做完整的数据验证,是性价比最高的安全手段。它不需要额外机器,一台服务器就能完成,却能把绝大多数数据层问题隔离在正式变更之外。数据文件没问题,剩下的才是配置和代码兼容问题,排查范围和难度会小很多。
5.3 升级流程要能反复执行
最后分享一个思路:不要把升级当成一次性的手工操作,而要把流程固化下来。版本目录固定、配置文件模板化、备份路径固定、回滚步骤文档化,这样才能保证下次升级时不会因为某个细节遗漏而出问题。如果条件允许,先在测试环境把整个流程跑一遍,记录实际耗时,再在线上实施。一次成功不算真稳,能够重复成功才算稳定。每次升级后花十分钟把变更记录整理好,下一次遇到类似问题,你会感谢自己当时的习惯。