上个月我刚好把一台CentOS 7.9上的Redis从7.4.1升级到了7.4.6,说实话一开始心里也没底。Redis做中间件在线上扛着不少流量,最怕版本一升、内存数据出幺蛾子。朋友问我“升级Redis不就是下载新包覆盖一下吗”,必须纠正:核心不是覆盖,而是切换。编译过程不影响服务,真正停服的窗口只有最后几十秒,但备份和验证比替换本身重要得多。这篇文章就是那次升级的完整记录,从评估、备份、编译到切换、验证和回滚方案,全程命令可以直接抄。如果你手头正好有CentOS上的Redis,版本在7.4.x区间,这篇很对路;就算是第一次操作,照着走也不会翻车。
1. 升级前绕着走的三件事:风险确认、备份与基线记录
升级本身不难,难的是升完之后你不知道自己破坏了什么。很多人直接下载新版本覆盖二进制,重启完才发现数据量对不上、主从同步断链、内存碎片率飙升。所以我把开工前的准备工作放在最前面,这一步省了,后面全是坑。
1.1 为什么是7.4.6而不是其他版本
Redis的版本号走的是语义化版本管理,规则很明确:主版本号不兼容时才会动,次版本号是新增功能,补丁版本号只做修复。7.4.1到7.4.6属于同一主版本内的补丁升级,意味着API、命令、持久化文件格式、集群协议这些约定都保持兼容,风险等级在所有升级路径里是最低的。
7.4系列从发布到现在,补丁版本集中在几个方向:一是安全漏洞修复,二是内存管理和碎片整理的边界情况,三是持久化在异常断电场景下的边缘bug,四是集群模式下主从切换的稳定性问题。这些都是“平时遇不到、遇到就要命”的类型。我这次升级的直接原因很简单,线上实例已经连续跑了几个月,平时重启次数又少,既然补丁版本已经积累了好几版,不如一次升到位,避免后面为了某个CVE再来一次全量操作。
有一点需要说清楚:同一个跨版本,比如7.2.x升7.4.x,也是可行的,但那种升级要额外关注新功能引入的配置差异。而7.4.1到7.4.6基本不存在这个问题,配置文件的兼容性很高,这也是我敢在线上直接操作的原因。
1.2 数据备份别漏了这三样
备份是整个升级过程中最容易被敷衍的一步。很多人执行一下redis-cli BGSAVE就觉得完事了,其实至少要覆盖三个层面:
第一,内存数据的最终快照。手动执行一次BGSAVE,确保dump.rdb是最新的。执行完以后去看日志里的rdb_last_bgsave_status,确认是ok。这条命令的输出结果很多人忽略,一旦RDB写入失败,你后续的升级操作等于在裸奔。
第二,AOF文件。如果实例开启了AOF,那么appendonly.aof以及同目录下的manifest文件都要复制一份。AOF和RDB的备份时机要保证一致性,我习惯的做法是:先BGSAVE,等待RDB完成,再复制AOF和RDB文件。为什么要先RDB后AOF?因为Redis重启加载时会依据配置决定优先使用哪个,两个备份文件时间戳差太远,等下恢复测试容易自己骗自己。
第三,旧版本的二进制文件。这一步很多教程根本不提,但它是回滚方案的地基。什么时候你会需要旧二进制?升级后运行不稳定、启动报错、数据加载异常,你要在十分钟内恢复服务,重装旧版源码编译根本不现实。最稳妥的做法是升级前先看现在跑的是哪个安装路径,命令是ps -ef | grep redis-server,然后把对应的redis-server和redis-cli复制到一个独立目录,比如/opt/redis-backup/bin/。
配置文件也要备份,包括主配置文件和任何include进来的碎片化配置,这个不用多说,但别忘了检查目录权限。Redis有些版本会用当前用户去读配置,备份还原后owner变了,启动会直接被拒。
我把这层备份做完后还会顺手验证一下:尝试用备份的RDB+AOF文件在另一个端口启动一个临时实例。别怕麻烦,这一步成本很低,但能确认备份文件不是损坏的。我见过太多次“备份了但没法还原”的悲剧,临时实例试一下,几分钟的事,换一个晚上的安稳。
1.3 记录基线数据,升级验证才有的放矢
升级之前要把实例当前的健康数据记录下来,否则升级完你说“没问题”,其实是凭感觉。我一般会执行一条:
redis-cli INFO all > /root/redis-7.4.1-baseline.txt然后从里面挑几项重点记下来:
redis_version:当前版本,确认是7.4.1;used_memory和used_memory_human:当前内存占用;mem_fragmentation_ratio:内存碎片率,这个值升级前后对比能反映内存分配器表现;total_commands_processed和instantaneous_ops_per_sec:累计命令数、实时OPS;rdb_last_bgsave_status和aof_last_bgrewrite_status:持久化最近状态;db0:keys=:各库的key数量;master_repl_offset和slave_repl_offset:如果有主从结构,偏移量必须记录。
这些数据不只是升级后对比用,还有一个隐藏价值:如果升级过程中发生了主从切换或者数据重放,你可以通过偏移量的变化判断数据同步有没有丢。我在生产环境做过一次对比,升级后master_repl_offset和升级前记录的基线一对照,直接确认了主从之间没有回退,这一步给心理上的踏实感是无法替代的。
基线数据记录完以后,还需要确认一下升级窗口。不要在业务高峰期做,Redis虽然重启只有几十秒,但主从结构下如果触发了故障转移,影响面会被放大。我习惯选凌晨两点到四点之间,并且提前通知相关团队。
2. 版本差异与兼容性核对:别信“小版本都一样”这句话
补丁版本改动小,不等于不用看改动内容。把源码包里的变化搞清楚,后面就算出了问题你也能定位原因,而不是一脸懵地看着日志猜。
2.1 7.4.1到7.4.6之间到底改了什么
Redis官网的下载页面或者源码包里的CHANGELOG文件会列出每个版本的变更。7.4.2到7.4.6这几个版本,修复集中在几类:
- 安全方向:某些命令在特定权限配置下的越权检查问题、网络层异常输入处理;
- 持久化方向:AOF重写过程中如果发生崩溃,可能产生文件尾部的损坏数据,补丁版本增强了对这类情况的处理;RDB在超大key场景下的序列化边界问题也有修复;
- 内存方向:jemalloc内存分配器在一些高碎片场景下分配失败的问题,以及内存碎片整理逻辑的异常分支;
- 集群方向:主从切换时故障节点恢复的时序竞争问题,避免脑裂场景下的数据回退;
- 客户端兼容:部分Lettuce、Jedis客户端在特定命令交互下的异常行为,Redis服务端做了容错优化。
从热搜词里的“redis command timed out; nested exception is io.lettuce.core.RedisCommandTim...”就能看出来,Lettuce客户端的超时问题在真实环境里很常见,7.4.x系列对异常场景下的响应处理确实有优化,升级后这个问题的出现概率会低一些。
有意思的是,很多线上问题报告其实是客户端责任,但服务端在补丁里做了兼容。这也提醒我们:小版本升级不是“什么都没变”,而是“协议没变,行为更稳了”。协议不变意味着你的应用代码不用改,这也正是我放心直接编译替换的原因。
2.2 编译依赖检查与补装
编译Redis需要gcc、make这两个基础工具。CentOS 7默认自带的gcc版本通常足够编译Redis 7.4.x,但如果你前期装过别的软件、动过系统库,最好先确认一下:
gcc --version make --version如果连gcc都没有,先装编译工具组:
yum groupinstall -y "Development Tools"这里提醒一句:老CentOS上如果gcc版本过低,编译Redis 7.4.x可能会在jemalloc或fpconv这类底层模块上报错,报错信息看着像系统库缺失,实际是编译器太老。解决办法是升级gcc或者用scl切换更高版本的编译器环境,但这种情况在CentOS 7.9上比较少,不用提前焦虑。
还有一个很容易被忽略的依赖:pkg-config。Redis 7.x编译时如果检测不到它,某些可选功能会被静默跳过,编译不会失败,但出来的二进制的功能不完整。稳妥起见先确认:
rpm -qa | grep pkg-config如果没有,直接yum install -y pkgconfig。
2.3 配置文件的快速差异核对
升级前把当前生产用的redis.conf,和Redis 7.4.6源码包里的redis.conf模板做一次diff,这个动作耗时不到一分钟,但收益很大:
diff /etc/redis.conf /usr/local/src/redis-7.4.6/redis.conf在这个diff结果里,你会看到新增了一些默认配置项。7.4.x系列新增的配置项大多有合理的默认值,不主动修改的话不影响现有行为。但如果你使用的配置文件是很多年前从7.0或7.2迁移过来的,里面可能有一些已经被废弃的旧配置项,Redis启动时会打WARNING但不会报错,这类警告容易被忽略,建议在升级时顺手清理掉。
要注意另一个配套动作:升级后不要急着执行CONFIG REWRITE。这个命令会把当前实例内存中的实际配置回写到配置文件,如果新版本加入了一些你没关注过的默认参数,Rewrite会把它们全部固化下来,等你想回滚旧版时,旧版可能不认识这些新配置项。我见过有同事踩过这个坑,本来只要换回二进制就能回滚,最后还要先改配置,多出一堆麻烦。
3. 升级操作完整流程:编译、替换与平滑切换
准备工作做足之后,真正的操作流程反而很短。核心原则只有一个:编译时不停服,切换时快准稳。
3.1 下载、解压与编译新版本
先去Redis官网下载页面找对应版本的源码包。用wget直接拉下来:
cd /usr/local/src wget https://download.redis.io/releases/redis-7.4.6.tar.gz tar xzf redis-7.4.6.tar.gz cd redis-7.4.6进入源码目录后,先做一次干净编译:
make distclean make -j$(nproc)这里make distclean的作用是清理可能存在的旧编译产物。如果你是第一次在这台机器上编译,可能没有产物,但养成习惯执行一次,能避免奇怪的问题。
make -j后面的参数是并行编译的线程数,$(nproc)自动读取CPU核心数。编译Redis很快,一般一两分钟内结束,这期间Redis实例本身完全不受影响,服务照跑。
编译完成之后,建议执行一次回归测试:
make test这次测试会花几分钟时间,测试项覆盖基础命令、持久化、主从复制等多条链路。补丁版本升级时,make test能跑过基本就等于功能兼容了。实测下来,7.4.1升7.4.6的测试结果很干净,没有fail项。测试过程中Redis会自己拉起临时实例,不占用你的线上端口,放心执行。
测试通过后就安装:
make install PREFIX=/usr/local/redis指定PREFIX的好处是二进制会装到/usr/local/redis/bin目录下,和系统自带的/usr/bin隔离开。但要注意一个常见误区:如果你原来的Redis也是同样方式安装的,make install会把同目录下的redis-server和redis-cli直接覆盖;如果你原来的二进制在/usr/bin,这个命令不会覆盖到那边,后面替换时要手动处理。
先把新版本信息确认一下:
/usr/local/redis/bin/redis-server --version显示v=7.4.6就说明编译成功。
3.2 二进制替换与服务重启
这一节是整个升级中唯一会影响服务的环节,操作顺序一定要谨慎:
# 1. 确认线上实例实际使用的二进制路径 ps -ef | grep redis-server # 2. 优雅关闭Redis(会触发RDB保存,相当于多一道保险) redis-cli shutdown # 3. 确认进程已经退出 ps -ef | grep redis-server为什么先用shutdown而不是直接用systemctl restart?因为shutdown命令在关闭前会先触发一次RDB快照保存,等于在切换前多留一个最新数据点。就算你之前做过手动BGSAVE,这一步也是免费的第二道保险。
Redis退出后,用redis-server --version确认当前环境里实际找到的是哪个版本:
which redis-server如果线上实例原本用的/usr/bin/redis-server,而你的新版装在/usr/local/redis/bin,必须先把新二进制放到正确位置再启动:
cp /usr/local/redis/bin/redis-server /usr/bin/redis-server cp /usr/local/redis/bin/redis-cli /usr/bin/redis-cli这里建议用cp不要用mv。虽然新版本已经编译好,这一步本质是替换文件,但用cp保留一份新版本在/usr/local/redis/bin,回滚时只有旧二进制被保留在/opt/redis-backup/bin/,多一层备份总不是坏事。
接下来启动服务:
systemctl start redis如果你用的是自定义systemd服务文件,服务名可能叫redis-server或者别的,先确认清楚再执行。启动后马上看一下状态:
systemctl status redis这里有个细节:如果服务起不来,先看日志:
journalctl -u redis --since "5 minutes ago"3.3 启动失败与WARNING的现场处理
我这次升级启动很顺利,但日志里还是出现了两条经典的WARNING,属于见过即释然的那种:
第一条是The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is lower than 511。这个警告在Redis启动时经常出现,调高系统参数即可:
echo 1024 > /proc/sys/net/core/somaxconn为了永久生效,需要写入/etc/sysctl.conf。
第二条是关于transparent hugepage,Redis官方默认建议关闭。这个参数如果开启,在高写入场景下会增加内存延迟,严重的会在fork子进程做RDB快照时产生明显抖动。关闭方式:
echo never > /sys/kernel/mm/transparent_hugepage/enabled然后写入/etc/rc.d/rc.local,设置开机自动应用。这个建议不只在升级时需要,任何新装Redis的机器都建议检查。
还有另一类启动失败原因需要专门提一下:SELinux。如果你在journalctl -u redis里看到Permission denied,而配置文件路径、目录权限都看起来没问题,那大概率是SELinux的文件上下文问题。升级后替换的二进制在/usr/bin下,它的类型标签可能还是旧的,可以执行:
restorecon -Rv /usr/bin/redis-server把文件的SELinux上下文恢复成默认值。CentOS上很多服务升级失败都是这个原因,提示信息还特别隐晦,不熟悉的话能排查一整天。
4. 升级后的验证清单与实测对比
启动成功不等于升级完成。我把验证分成三层,从“版本对没对”到“数据全不全”再到“性能有没有下降”,一层层来。
4.1 基础状态确认
先确认版本:
redis-cli --version # redis-cli 7.4.6 redis-cli INFO server | grep redis_version # redis_version:7.4.6再看服务运行状态:
redis-cli PING # PONGsystemctl status redis显示active (running),说明服务正常拉起。
不要在这里就宣布“升级完成”,接下来的验证才是重点。
4.2 数据完整性与复制的抽验
先把key总量和升级前的基线对照:
redis-cli DBSIZE如果升级前记录的是100万key,升级后DBSIZE明显偏小,那问题就大了。正常情况下补丁版本重启后DBSIZE应该一致,除非RDB加载过程中有损坏,而那一类损坏的概率在7.4.6里本身就被修过,所以看到完全一致的数字是正常状态。
然后抽查热点key:
# 根据你的业务情况,取几个热门前缀的商品/用户/会话key redis-cli GET product:10001:detail redis-cli GET user:88888:profile这些key的值和升级前一致的话,基本可以确认数据没有丢。这个方法虽然朴素,但在生产环境很有效,比盲目信工具可靠。
有主从结构的场景还要检查复制状态:
redis-cli INFO replication重点看这几项:
role:确认当前角色没变;master_repl_offset:升级后应该继续增长,而不是回退或停止;slave_repl_offset:从节点的偏移量应紧跟master;connected_slaves:从节点数量没有减少。
升级主节点时,我建议按照“先从后主”的顺序操作:先把一台从节点升级到7.4.6,观察主从同步和复制偏移量正常后,再升级主节点。这样数据副本始终有一份在手上。热搜词里有人搜“k8s redis 集群”“docker安装redis主从”,容器场景下的升级逻辑是一样的:先升级从节点,确认稳定后再切主节点,最后升级另一台。
4.3 性能基准对比与内存状态检查
用Redis自带的redis-benchmark做一次压测,对比升级前后的OPS和延迟。注意压测时不要打在线实例,最好单独起一个同配置的临时实例,避免影响线上业务。
redis-benchmark -t set,get -n 1000000 -d 128 -c 50我实测下来,7.4.1到7.4.6在相同参数下OPS基本持平,没有下降,延迟的p99表现还略好一点。补丁版本很少会带来性能跨度式提升,所以“持平”就是最好的结果,说明没有引入额外开销。
然后对比内存数据:
redis-cli INFO memory | grep -E "used_memory_human|mem_fragmentation_ratio"碎片率(mem_fragmentation_ratio)如果在1.0到1.5之间,说明内存分配健康。如果超过1.5,说明碎片化比较严重,可以对实例执行memory purge或者CONFIG SET activedefrag yes,利用7.4版本默认开启的主动碎片整理来消化。升级后观察一两天,碎片率没有异常就说明jemalloc的表现和以前一致。
此外记得检查持久化状态:
redis-cli INFO persistencerdb_last_bgsave_status:ok和aof_last_bgrewrite_status:ok都是正常状态,说明重启后的持久化链路是通的。细心一点的话,还可以执行一次手动BGSAVE,然后看rdb_last_save_time是否更新,这个方法是验证“能否正确写快照”的最直接手段。
最后看一眼慢日志:
redis-cli SLOWLOG GET 10升级后如果慢查询数量异常增多,说明某些命令执行变慢了,需要结合延迟数据一起分析。实际升级中这种情况不常见,但检查成本极低,建议养成习惯。
5. 回滚预案与踩坑总结
升级这件事,没有“绝对不成问题”的二进制,只有“我准备好了如果出问题怎么办”。我从这次操作里总结了几条管用的经验,包括一次真实的回滚演练过程。
5.1 十分钟内快速回到7.4.1的具体做法
回滚方案必须在升级前就写好,而不是出问题后再现想。我的备份结构是:
/opt/redis-backup/ ├── bin/ # 旧版redis-server和redis-cli ├── dump.rdb # 升级前手动BGSAVE生成的快照 └── appendonly.aof # 升级前的AOF文件(如开启)如果升级后发现问题需要回滚,完整步骤如下:
# 1. 关闭Redis redis-cli shutdown # 2. 恢复旧版二进制 cp /opt/redis-backup/bin/redis-server /usr/bin/redis-server cp /opt/redis-backup/bin/redis-cli /usr/bin/redis-cli # 3. 启动服务 systemctl start redis # 4. 验证版本和关键数据 redis-cli --version redis-cli DBSIZE整个过程两三分钟就能完成,前提是备份做过验证、配置没有被执行过CONFIG REWRITE造成污染。同系列版本之间RDB文件是兼容的,7.4.6写入的RDB,7.4.1能正常读取,所以数据层面基本不需要回放AOF。但如果你升级后手动改过配置,或者执行过CONFIG REWRITE,那就还要把配置文件也恢复回去,这时候备份目录里那份redis.conf的价值就体现出来了。
我把这套回滚流程在升级完成之后的第二天实际演练过一次,确认能在5分钟内恢复服务。演练不会影响线上实例,因为我用备份配置起了一个临时实例测的,操作路径完全一样。这种“把回滚跑一遍”的习惯,你在任何时候做升级都值得保留。
5.2 升级过程中最容易被忽略的三个小坑
第一个坑:新二进制装错目录。很多人下载源码后直接make install,忘了指定PREFIX,结果新版装到了/usr/local/bin,但线上服务用的是/usr/bin/redis-server。启动时系统仍然在执行旧文件的路径,日志里版本毫不变,你还会以为升级成功了。记住,一切以ps -ef显示的实际路径为准。
第二个坑:SELinux上下文。前面已经提到了,替换二进制后连接被拒,查权限看不出任何问题,实际上是SELinux标签不对。CentOS上这个问题非常典型,先restorecon -Rv再考虑其他原因。
第三个坑:升级窗口撞上主从全量同步。如果你在升级前刚把某个从节点加进来,或者实例发生过大key删除,主从之间可能正在走全量同步。这时候重启主节点,会强行中断同步,重新触发一次全量,给网络和磁盘带来额外压力。升级前先看一眼INFO replication里的master_repl_offset和从节点状态,确保复制链路稳定后再动手。
5.3 这套升级流程能平移到哪去
把“备份二进制+数据+配置,编译替换,验证基线,预演回滚”这套流程整理出来之后,会发现它能平移得非常远。
如果你在CentOS 7上要从7.2.x升到7.4.x,属于次版本升级,流程一样,但要多加一步:仔细看新版本引入的配置项和废弃项,因为跨次版本可能有命令行为的变化。跨大版本升级,比如7.4.x升到未来的8.x,就不能简单依赖RDB兼容性了,必须先确认官方发布的兼容性说明,再用从节点过度、主从切换的方式滚动升级,让新版本通过复制逐步接数据,而不是一次性覆盖。
热搜词里有“redis分布式锁”“redis缓存治理”“redis线程io模型”,这些词说明很多人同时在关注Redis的高可用设计、使用规范和底层性能模型。补丁版本升级只是运维动作,但如果平时你已经在用分布式锁做业务互斥,用哨兵或Cluster做高可用,那么升级动作本身也在间接提升这些上层机制的稳定性,因为底层版本的bug少了,锁服务的拿锁/释放锁交互才更值得信任。
说到最后,升级Redis这件事,真正的价值不在那几分钟的切换操作,而在于你因此建立了一套“备份—验证—回滚”的运维习惯。我这次升完以后,把基线数据、备份目录、回滚步骤都收进了一个文档,下次无论升什么中间件都能快速套用。如果你现在正准备动手从7.4.1升7.4.6,建议先把这份记录的章节当成清单走一遍,尤其是备份和基线数据不要省,剩下的操作也就一杯咖啡的时间。