1. 先弄清楚:同是 Redis,为什么非要升级不可?
做运维这些年,我见过太多“能跑就不动”的服务器,Redis 更是重灾区。很多团队从某个 LTS 版本装上之后就再也没碰过,直到遇到内存碎片率高得离谱、主从切换卡顿、或者发现线上被扫描到某个高危漏洞,才想起来问一句“Redis 要不要升级”。
先说结论:绝大多数在 Linux 上运行的 Redis 实例,都值得升级到 6.x 或 7.x 主线版本,前提是你先把下面这些事想清楚。
Redis 的版本演进不是挤牙膏。3.x 时代引入集群,4.x 引入模块系统与混合持久化,5.x 解决了大量 AOF 重写阻塞问题,6.x 带来多线程 IO、ACL 权限控制、RESP3 协议,7.x 则在性能、内存效率、集群管理上又做了一轮大手术。每一代升级都不是单纯“修复了若干 bug”,而是直接影响你在生产环境里能使用哪些能力。
举个例子:6.x 之前的 Redis 没有 ACL,所有客户端共用一条权限通道,一旦某个应用的 key 被误删,你根本查不出是哪个连接干的。升级到 6.0 之后,每条业务线独立账号、独立命令白名单,故障定位直接从“猜”变成了“看”。这类提升,不升级是体会不到的。
还有一个更现实的理由:安全补丁。Redis 历史上出过好几轮严重的远程命令执行漏洞,官方往往只对最近两个大版本提供修复。你还在用 5.0 当宝,等于把大门敞着。
所以,这篇我直接把我自己在 Linux 上升级 Redis 的完整做法、踩过的坑、以及翻车后怎么回滚都写出来。适合三类人:线上 Redis 已老旧、正准备升级的运维同学;在个人服务器上折腾、想尝鲜新版特性的开发者;以及在云主机上部署过 Redis、但从来没考虑过版本管理的朋友。
2. 升级前的体检:别一上来就动数据
我第一次给生产环境升级 Redis 时吃过亏。当时一看新版本发布了,直接编译安装、替换二进制、重启服务,结果老配置加载报错,旧 RDB 文件版本不认,线上缓存全部重建,把数据库打出了慢查询告警。从那之后我总结出一句话:Redis 升级,真正的难点不是装新版,而是让旧数据无缝适配新版。
2.1 摸清家底:版本、数据量、架构一个都不能漏
先上一组命令,把当前环境完整扫描一遍:
# 查看当前 Redis 版本 redis-cli info server | grep redis_version # 查看持久化配置 redis-cli config get save redis-cli config get appendonly redis-cli config get dir redis-cli config get dbfilename # 查看当前内存与数据集规模 redis-cli info memory | grep used_memory_human redis-cli info keyspace这里有个很容易忽略的点:dir配置决定了 RDB/AOF 文件落盘路径。升级过程中如果新版本的启动脚本或 systemd 服务里 WorkingDirectory 变化了,可能导致 Redis 在启动时找不到旧数据文件,直接当空实例初始化。所以判断数据是否真的“迁移成功”,别只看服务起来了,还得用keyspace里的 key 总数和dbsize做对比。
架构信息同样重要:
- 单机 Redis:重点考虑备份、回滚、服务停机窗口
- 主从复制:先升级从库,再手动触发主从切换,最后升级原主库
- Redis Cluster:需要逐个节点滚动升级,且要保证槽位数据完整
- Sentinel 高可用运维:涉及选主逻辑和配置兼容
我见过有人单机 Redis 也先备份、再直接杀进程,等新版本起来后keyspace只有预期的一半,半天才发现是因为dbfilename不一致导致的。这类低级错误,体检阶段就能发现。
2.2 数据备份的两种思路:物理备份与逻辑重放
升级前备份,我一般做双重保障。
第一层:RDB 文件物理复制。找到dir目录,把 dump.rdb 拷贝一份到独立目录,并且确认文件大小大于 0:
mkdir -p /data/redis_backup_$(date +%F) cp /data/redis/dump.rdb /data/redis_backup_$(date +%F)/注意,直接复制运行中的 RDB 文件可能抓到一个不完整快照。最稳妥的方式是先通过redis-cli触发一次 BGSAVE,等SAVE状态变为正常、RDB 文件时间戳更新后再复制:
redis-cli bgsave redis-cli info persistence | grep rdb_last_bgsave_status第二层:AOF 文件 + 增量指令。如果appendonly yes,最好把 AOF 文件也一起备份。AOF 文件可能比 RDB 大得多,但恢复时更精准,能覆盖到最后一次写操作。升级前的最后一个小时如果有高频率写入,哪怕 RDB 丢了最近一段增量,AOF 也能帮忙补上。
我还要多说一句:如果条件允许,主从架构下更优雅的数据保障方式是“起一个临时从库”。让临时从库同步到最新位点后,把 RDB/AOF 从临时从库拷贝出来。主库全程不参与备份,完全不影响线上写入。这里的关键是注意从库要和主库用同一个 bind 网段,临时从库的 repl-timeout 设置合理,避免同步中断。
2.3 配置兼容性体检:新版启动不了,九成是配置问题
升级前把现有配置文件和新版本默认配置做一次 diff,非常有必要。Redis 各个版本之间配置项变动不小:
- 5.x → 6.x:新增
aclfile、io-threads、tls-port等配置,旧配置基本能跑 - 6.x → 7.x:去掉了
protected-mode的部分默认行为,maxmemory-policy的某些选项语义有调整,老配置里有save ""这种写法需要特别确认 - 模块相关:如果加载了第三方模块(如 ReJSON、BloomFilter),必须确认该模块是否兼容新版 Redis 的模块 API
有个笨但有效的办法:在新服务器上解压新版,把旧配置复制过去,启动一遍看报错。没有新服务器就用源码目录里自带的 redis.conf 做对照。我通常直接跑一次redis-server /path/to/old.conf,如果控制台没有 ERROR 级别日志,基本就成功了一半。
不过我不建议直接沿用旧配置,建议按新版本语义重新过一遍几个关键项:
protected-mode是否保持 yesappendonly是否要开启(7.x 建议开,多任务写场景收益明显)save策略是否匹配你的数据量maxmemory是否设置,避免升级后内存超卖导致 OOMlogfile是否和 systemd 日志监控冲突
2.4 客户端兼容性:升级后连接不上,先别怀疑 Redis
很多人升级完 Redis 才发现,应用层炸了。这锅通常是客户端的。
如果业务用的是老版本 Jedis(2.x 早期)、Lettuce 5.x 之前、或redis-py3.x 以前版本,连接新版 Redis 6 以上时可能出现协议、密码、失败重连等问题。最典型的是:新版 Redis 默认开 RESP2 兼容,但你显式切到 RESP3,老客户端直接无法处理返回格式。
我建议升级前把项目里的 Redis 客户端依赖统一向最新稳定版靠拢,和 Redis 服务端升级放在同一个发布窗口内。分布式锁这种强依赖命令语义的场景,更要做一次完整回归测试。比如SET key value NX EX在 6.x 之后的返回语义更严格,某些老客户端会把返回OK和返回nil的判定弄反,甚至把成功写成失败。
3. 两种升级路线:二进制替换和编译安装怎么选
有基础的读者可以直接跳到 3.2 的实操流程,拿不准的先把 3.1 看完。
3.1 我为什么不推荐发行版源安装
Ubuntu 的 apt 和 CentOS 的 yum 源里确实有 Redis,但发行版仓库普遍滞后。比如某段时间 Ubuntu 20.04 的官方源里 Redis 还停留在 5.0.x,而官方早已发到 7.x。除非你用的是 Redis 官方维护的ppa:redis/redis或第三方维护较快的源,否则通过系统源升级很容易“升了个寂寞”。
另外,用系统包管理器安装 Redis,你很难控制编译参数。比如在 aarch64 架构上想开启某类指令级优化,发行版二进制不支持;或者你想把 Redis 编译成静态链接版,降低对系统库的依赖,也只有源码编译能做到。
我个人经验是:Redis 升级,优先用官方 release 的二进制包或源码编译,两者都靠谱,看你的环境更信哪一种。编译只多花几分钟,但换来的是完全可控。
从安全和可控角度,我还建议在官方发布页面下载时校验 sha256 校验和。这一步极其重要但常被忽略。哪怕是官方包,也建议养成校验习惯,避免在下载后被篡改。命令如下:
curl -L -o redis-7.2.5.tar.gz https://download.redis.io/releases/redis-7.2.5.tar.gz echo "xxxxxxxxxxxxxxxxx redis-7.2.5.tar.gz" | sha256sum -c -如果你所在服务器访问外网慢,可以提前把 tar.gz 包下载到内网文件服务器,再批量分发。批量升级的场景我很推荐这种方式,配合同步工具(如 rsync)能很快完成多机替换。
3.2 编译安装完整流程(以 7.2.x 为例)
这套流程我在 CentOS 7/8、Ubuntu 20.04/22.04 上都实测过,直接照抄基本不会翻车。
第一步:安装编译依赖
# Debian/Ubuntu apt update && apt install -y build-essential pkg-config libjemalloc-dev libssl-dev # CentOS/RHEL 系列 yum install -y gcc make tcl jemalloc-devel openssl-devel这里重点说下 libjemalloc。Redis 官方默认使用 jemalloc 作为内存分配器,编译时如果找不到会在 config 阶段自动回退 libc malloc。这个回退平时看不出问题,但高并发、大内存场景下碎片率和性能差异很明显。所以我总是手工指定MALLOC=jemalloc编译参数。
第二步:编译安装到独立目录,不覆盖老版本
我不建议直接把新版二进制覆盖到/usr/local/bin/redis-server。虽然版本管理上看不太出来,但一旦需要回滚,你就懵了。推荐安装到一个版本号目录里,比如:
mkdir -p /usr/local/redis/redis-7.2.5/src cp src/redis-server /usr/local/redis/redis-7.2.5/src/ cp src/redis-cli /usr/local/redis/redis-7.2.5/src/ cp src/redis-sentinel /usr/local/redis/redis-7.2.5/src/ cp src/redis-benchmark /usr/local/redis/redis-7.2.5/src/再用软链接把默认路径指向新版:
ln -sfn /usr/local/redis/redis-7.2.5/src/redis-server /usr/local/bin/redis-server ln -sfn /usr/local/redis/redis-7.2.5/src/redis-cli /usr/local/bin/redis-cli这样一来,既有新版本可用,又在/usr/local/redis/下保留了老版本目录,回滚时只需要改软链接指向。
第三步:启动前做一次状态检查
启动前先确认redis-server -v能正常输出版本号。再确认老版本数据还在:
redis-cli ping # 老实例仍在线 ls -hl /data/redis/dump.rdb这里我习惯先把新实例用一个独立端口测试启动一次(比如 6380),用旧配置启动,观察日志 10 秒,确认没有加载错误后再进行真正的切换。这一步能拦截绝大多数配置兼容性问题。
第四步:正式切换服务
如果你的 Redis 是 systemd 管着的,改好软链接后直接重启服务:
systemctl restart redis redis-cli info server | grep redis_version如果新版本启动失败,systemd 会返回失败状态,此时先去日志里找原因,不要急着反复重启:
journalctl -u redis -n 50 --no-pager3.3 systemd 场景下的特别注意点
很多发行版自带的 Redis systemd 单元文件里会写死ExecStart和PIDFile。比如:
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf升级后软链接指向新版没问题,但如果你的系统里/usr/local/bin/redis-server是旧版实文件,且新二进制单独放在某个路径,服线务启动的其实还是老版本。肉眼不一定能立刻发现,只能看到redis_version没变。所以升级完记得再执行一次重启,然后立刻看版本号。
如果之前是用 SysV 方式(/etc/init.d/redis)管理的,升级前最好提前切换到 systemd,不要在生产环境里冒这个险。
我遇到过更隐蔽的情况:redis.conf里用daemonize yes,并发起了 systemd 托管。结果 systemd 以为服务卡在启动阶段,不断标记超时,最后直接杀掉进程。新版 Redis 在 systemd 环境下应保持daemonize no,让 systemd 直接接管进程生命周期,配合Type=forking或Type=simple按实际配置选择。这里贴一个我在生产用的单元文件片段:
[Service] Type=simple ExecStart=/usr/local/redis/redis-7.2.5/src/redis-server /etc/redis/redis.conf --daemonize no ExecStop=/usr/local/redis/redis-7.2.5/src/redis-cli -p 6379 shutdown nosave Restart=always User=redis Group=redis注意shutdown nosave参数,意思是停止前不强制保存快照,避免停机时多一次 RDB 重写。如果你期望停机时保留最新数据,那直接用shutdown save或干脆执行redis-cli shutdown。这个细节在自动化发布脚本里容易踩坑。
3.4 主从架构的升级顺序:别在主库上先动手
主从架构下,我最推荐的顺序是:
- 先在从库上升级,确保从库能同步主库数据、能承载只读流量
- 将从库数据一致性检查完成后,利用
replicaof切换或 Sentinel 触发主从切换,原主库降级为新主库的从库 - 再按同样步骤升级原主库
这里面最关键的一步是升完从库后检查复制偏移量:
redis-cli -p 6380 info replication | grep master_link_status echo "master_repl_offset: $(redis-cli -p 6380 info replication | grep master_repl_offset)" echo "master offset: $(redis-cli -p 6379 info replication | grep master_repl_offset)"两个偏移量一致,且master_link_status:up才说明数据同步正常。如果偏移量长期不一致,先排查网络、持久化策略,别急着切换。
Redis Cluster 集群的滚动升级思路类似,但还多一个要求:升级完一个节点后必须等该节点的槽位数据同步正常,再升级下一个节点。集群中有一个节点的版本和大多数不一致时,CLUSTER INFO会显示cluster_state:ok,但个别命令行为可能有差异。安全起见,我都是按 master→slave 顺序逐对升级,每对升级完跑一遍完整的读写探测再继续。
4. 升级过程中的翻车现场与排查方法
这部分我攒了不少真实案例,都是我和同行在生产环境里踩过的。每一类我都尽量把现象、原因、对策写全。
4.1 新版起不来:报错集中在哪几类
Bad directive or wrong number of arguments:配置文件里有老版本支持的参数,新版删掉了或语法变了。最常见的是save多参数写法和requirepass位置问题。对策:把报错行注释掉,一条条试,但更推荐用新版本默认配置做底,把你要覆盖的项手工填进去。Can't open the append-only file:AOF 文件路径不对或权限不对。升级后如果改了dir,记得把 AOF 文件也拷贝过去,或者配置保持原路径。Can't load the RDB file:RDB 文件是旧版本格式,而新版本在加载时选择了不向后兼容。这种多见于大版本跨度较大的情况,比如 2.x 直升 7.x。对策是多跨几步,先升级到中间版本,等数据加载完成后在中间版本上再做一次BGSAVE,生成能被新版识别的 RDB。jemalloc: Unrecognized value:编译时指定了错误的 jemalloc 配置,比如MALLOC=jemalloc但实际系统没有安装 jemalloc-devel。对策:要么装上开发包后重新编译,要么退回默认MALLOC=libc,生产环境建议优先补齐 jemalloc。no such file or directory:systemd 单元文件里ExecStart指向的二进制路径不对,或符号链接没生效。我在多机发布时遇到过,分发脚本漏掉软链接,导致只有部分节点升级成功。
4.2 升级后内存暴涨、碎片率下不来
常见原因是新版本默认内存分配策略变了,尤其是 7.x 对maxmemory-policy的语义调整。如果老配置里maxmemory设置得很保守,而升级后业务访问量不变,内存占用可能上涨 10%~20%。
遇到这种情况不要慌,分两步看:
第一步,确认used_memory_human和used_memory_rss_human的差值。如果 RSS 远大于 used_memory,说明碎片率偏高。执行memory purge或重启后再观察。如果重启后仍偶发,考虑调整 jemalloc 的background_thread相关配置。
第二步,检查是否因为大量 key 过期策略变化,导致惰性删除和定时删除的节奏不同。7.x 在active_expire上做了很多优化,对服务器 CPU 是友好的,但旧版本调的hz参数如果设置得太高,升级后可能让 CPU 毛刺更明显。实验性地把hz调到 10 再观察。
4.3 主从复制中断:升级后大量断连
我遇到过一次比较头疼的:从库升级后,master_link_status反复在up和down之间横跳。排查后是如下原因叠加:
- 主库
client-output-buffer-limit replica设置得太低,从库升级后同步大 key 或者全量同步时直接把输出缓冲区打爆 - 网络带宽不够,同步积压导致超时
- 新版本默认
repl-timeout比老版本短,导致复制判断超时的阈值变敏感
对策比较简单:先检查主从间的master_repl_offset是否长期变化正常;再看主库info clients里有没有连接被拒绝的日志。必要时可以临时调大repl-timeout,同时在从库逐步接入业务读流量,观察缓冲区的压力。
还有一个很隐蔽的点:从库升级前如果要保留旧数据,建议打开replica-serve-stale-data yes(旧版本叫slave-serve-stale-data)。否则升级后主从第一次同步时,从库会拒绝所有请求,业务直接失败。这个问题在从库承载读流量时特别危险,务必在配置里先设置好。
4.4 回滚如果不彻底,数据会二次受损
回滚不是把软链接切回去重启就完事。新版 Redis 在运行期间可能已经改写了 RDB/AOF 文件格式,直接切回老版本,老版本可能不认识新版生成的文件。所以:
- 升级前备份的 RDB/AOF 文件,回滚时优先用备份文件恢复
- 如果回滚前新版已经运行较长时间,备份文件比新文件更“老”,用备份文件恢复会丢失升级期间的写入,这个和回滚目标匹配,正常业务发布周期可接受
- 回滚完成后,要让旧版本先加载一次 RDB 验证数据完整性,再接入流量
我在回滚演练中总结了一条:保留升级前原目录不动,升级时用全新目录,回滚时不仅改软链接,还要把数据文件恢复到升级前位置。这样才能保证旧版二进制和自己“熟悉”的数据文件配合,避免格式问题。
5. 升级后的验证与收尾:怎么知道这次升级真成功了
服务启动、版本号变了,不代表升级成功。我有一张自检清单,升级后逐项对照,省得后面再返工。
5.1 功能与数据完整性检查
# 写读验证 redis-cli set upgrade:probe:$(date +%s) value_ok ex 300 redis-cli get upgrade:probe:* # key 数量对比(升级前记录的 keyspace) redis-cli info keyspace # 持久化状态 redis-cli info persistence | grep rdb_last_bgsave_status redis-cli info persistence | grep aof_last_bgrewrite_status这里要注意:如果升级前 old keyspace 有上万 key,而升级后keyspace显示为 0,先别慌,很可能是你配置里maxmemory策略或者save策略没对,导致 AOF/RDB 没加载。我建议升级前把dbsize打印到发布日志里,升级后对比差值。如果 diff 在正常波动范围内(比如 5% 以内),说明数据基本完整。
5.2 性能与长稳观察
升级后的头 1 小时、头 24 小时是两个关键窗口。我会至少观察这几个指标:
latency:用redis-cli --latency持续压测 1000 次,观察平均值是否有明显劣化- CPU 使用率:
top里 redis 进程 CPU 是否持续飙高,尤其是开启 io-threads 后会不会出现某个 CPU 核打满 - 内存碎片率:
info memory里mem_fragmentation_ratio,长期在 1.5 以上要警惕 - 慢日志:
slowlog get 50看是否有异常慢命令
7.x 引入的debug leak和info keyspace数据统计能让定位问题更快,但前提是你对新版的监控指标足够熟悉。我建议升级后先跑一轮redis-benchmark做一个简单的压力测试,对比升级前后的 QPS 基线,而不是直接把线上流量放过去。
redis-benchmark -c 50 -n 100000 -d 128 -t set,get -P 16如果压测结果比升级前低 5% 以上,优先检查配置里save策略、appendfsync频率、maxmemory是否带来额外负担。有时候是appendonly yes从关到开导致的性能下降,这属于预期内的成本,不一定是升级本身的错。
5.3 清理与文档:这些杂事别人看不到但很有用
- 升级完成后删除临时 RDB 备份目录里的中间文件,避免占用磁盘
- 更新 systemd 单元文件里的版本注释
- 在运维文档或公司内部知识库里留下升级记录:升级时间、原版本、目标版本、配置差异、验证结果、回滚预案
- 把升级前的
redis.conf打一个带时间戳的副本,放到独立的备份目录里,方便以后 diff
我常年在服务器目录里放一个UPGRADE.md,每次升级前先把当前版本和配置 diff 记录下来,升级后再把结果写进去。半年后回看,效率提升非常明显。这也算是我给新人的一个“偷懒”建议。
5.4 万一还有问题,要不要再回滚?
如果升级后一切正常,那自然不需要回滚。但如果出现运行时崩溃、数据持续不一致、性能严重劣化,且排查半天无解,那就执行回滚,而不是硬扛。
回滚的正确姿势是:
# 停掉新版服务 systemctl stop redis # 切回旧版软链接 ln -sfn /usr/local/redis/redis-6.2.x/src/redis-server /usr/local/bin/redis-server # 用升级前备份的数据启动旧版 systemctl start redis注意:如果新版运行期间已经发生过写操作,且备份文件是升级前生成的,回滚后必然丢失这段时间的数据。这个小窗口损失需要和业务方确认。所以我不建议在任何高写入场景下做“先升级靠回滚兜底”的冒险,升级前必须有清晰的数据备份。
6. 过程中那些“网上不会教你”的细节
最后把一些散点经验聚在一起,方便你直接收藏,至少能省半天排查时间。
6.1 别把 Redis 编译放太低配的服务器上
Redis 的make阶段其实不吃资源,但make test(如果跑完整测试)会比较吃 CPU。如果服务器规格只有 1C2G,编译时建议直接跳过make test,用make -j2限制并行度,避免编译期间 CPU 飙高影响线上业务。
6.2 升级后立刻调整hz与动态参数
新版 Redis 支持不少动态可调参数,不用改配置就能在线调整。升级后我习惯顺手把latency-monitor-threshold打开:
redis-cli config set latency-monitor-threshold 100这样就能在latency命令里看到微秒级延迟事件,比事后看慢日志快得多。更妙的是这个参数可以随时关掉,不用重启。
6.3 如果业务里有自定义模块,升级前先确认模块热度
Redis Modules 的 API 在每个大版本都会有小变动。比如 RedisTimeSeries 在 6.x 和 7.x 下的兼容性就有差异,老版本模块直接加载到 7.x 里可能出现Module API mismatch。升级前一定要把模块先升级到与目标 Redis 版本匹配的最新版,并且加载顺序要放在数据加载之后、对外提供服务之前。
6.4 定时任务与发布窗口错峰
我见过有团队在白天高峰期升级 Redis,主从切换顿时让慢查询数量翻了三倍。虽然 Redis 本身快,但主从切换时客户端重连带来的并发抖动是必然的。所以升级窗口最好选在凌晨低峰期,且在任何“大促”“压测”“报表日”之前完成验证。
6.5 写脚本要小心的一个坑:redis-cli版本必须与redis-server匹配
升级后如果只替换了redis-server、忘了替换redis-cli,老版本的redis-cli连接新版 Redis 时可能因为 RESP3 或新命令而解析错乱。轻则info里缺字段,重则某些命令直接报语法错误。所以我软链接里永远同时替换这两个二进制,这一点最容易被忽略。
最后说说我的体会
我最早折腾 Redis 升级,是因为线上 4.0 实例的 AOF 重写经常把主进程阻塞到几十毫秒,业务侧一直报超时。那时我花了两个晚上做备份、搭临时从库、编译 6.2、灰度切换,最后整个过程平稳得超乎我预期。反而是后来第二次升级时,因为没做配置 diff,新版直接把save策略解释错了,差点把数据目录写爆。
所以我现在给人讲 Redis 升级,每次都强调一句话:升级的核心难点从来不是新版好不好用,而是你有没有把旧版本的环境、配置、数据摸透。把勘察和备份做到位,切换随随便便就能完成;这两个环节偷懒,后面一定会用报警把你叫醒。
如果你也准备给自己服务器上的 Redis 动刀,建议先拿这套流程在测试环境完整走一遍,记录时间节点、备份大小、验证结果。确认万无一失后,再去生产环境操作。另外,我强烈建议你升级完成后,顺手跑一遍redis-cli config rewrite,把动态调整的参数固化进配置文件里,免得下次重启时参数又丢了。
升级这事,稳妥比速度重要。希望这篇能让你少踩几个坑,少熬两个夜。