尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Linux环境Redis升级全流程:从安全排查到平滑回滚

Linux环境Redis升级全流程:从安全排查到平滑回滚 老版本Redis的实例在Linux机器上跑得好好的平时也没人动它。但等到线上出了故障、被迫做紧急处理的时候才发现升级这件事拖得太久代价比想象中大得多。这篇文章我会把在Linux环境里做Redis升级的完整经验写出来升级前怎么排查、两条主流升级路线怎么选、现场最容易翻车的坑、升级后怎么验证、以及回滚预案怎么设计。不管你是刚接手Redis的初级运维还是正在为老版本发愁的开发者这套流程应该都能直接用。1. 为什么老版本Redis的稳其实是假象很多团队对Redis的态度是能用就不动尤其实例运行两三年没出过大事之后更倾向于保持现状。但我处理过几个被老版本拖累的现场之后看法完全变了版本落后不是稳是另一种形式的技术债。1.1 版本落后的隐性成本安全、性能、生态先从安全说起。Redis每隔一段时间就会公开一批高危漏洞有些直接涉及未授权访问、特定命令组合导致的崩溃。这些漏洞的修复版本基本都集中在6.2和7.0如果你手里还跑着3.x、4.x或者早期的5.x等于在网上裸奔。我不建议去记每一个漏洞编号最好的做法是强制自己定一条规矩线上Redis至少保持在大版本的两个更新代次以内。性能差距也很明显。Redis 6.0开始引入io-threads网络IO由单线程变成可配置多线程连接数高、小包多的场景下提升非常可观。Redis 7.0又把单一大文件AOF改成了multi-part AOF重写过程对主线程的阻塞明显减小。老版本在QPS很高的时候主线程既要做网络IO又要执行命令还要写持久化文件瓶颈很容易被打满。生态这一点往往被忽视。新出的客户端库、可视化工具、监控插件都在朝新版本特性靠拢。像Redis 6开始支持的ACL权限体系、Redis 7的Function能力新工具链默认按新协议工作。如果实例永远停在老版本很多运维和开发侧的提效手段都用不上团队的技术积累也会被卡住。1.2 先回答三个问题实例形态、数据量、停机容忍度在动手之前先别急着下载新版。我习惯先盘一下实例的基本面三个问题直接决定升级路径怎么选。第一个问题是实例形态。单机、主从、哨兵、Cluster四种形态升级策略完全不同。单机只能停机升级或接受数据迁移主从和哨兵架构可以做到平滑滚动Cluster更复杂需要按分片逐个处理。第二个问题是数据量。几十GB的实例和几百GB的实例备份时间、RDB加载时间、全量同步时间完全不是一个量级。第三个问题是停机容忍度。如果业务允许凌晨断几分钟原地升级最快如果一分钟都不能断那必须走滚动升级。把这三个问题写在纸上再去看后面的方案就不会走弯路。2. 升级前必须做透的三类排查版本路径、文件格式、生态依赖升级前最怕的不是操作步骤复杂而是忽略了一些看起来不重要但对结果影响巨大的细节。我在这一步吃了不少亏所以每次都会按下面三类排查走一遍。2.1 RDB与AOF的跨版本兼容关系先说原则新版本能加载旧版本的数据文件但旧版本加载新版本的数据文件可能会失败。Redis的RDB文件有内部格式版本号AOF也有自己的格式演进。跨大版本升级后只要实例正常运行等到下一次自动或手动持久化写出的文件就是新格式这时候就回不去了。从5.x升6.x数据文件格式变化不大一般直接加载没问题。但从6.x升7.xRDB和AOF的格式都有调整Redis会自动完成加载可一旦写入新格式用老版本起实例就会直接报错。如果是跨多个大版本升级比如3.x直接跳7.x我建议分段走先升到中间版本验证一次再跳到目标版本每一步都留好后路。我在升级前的固定动作是先看redis.conf里的dir和dbfilename配置确认数据目录里RDB和AOF文件的位置和大小然后把文件原样拷贝一份到独立备份目录。这里有个很关键的小细节停服时如果用redis-cli shutdown且没有加nosave参数Redis会在退出前把当前数据写一次RDB这个文件可能是新版本格式。所以为了保住回滚现场升级停机时我会明确用shutdown nosave再手动备份旧文件。2.2 配置文件与脚本兼容容易被忽略的隐藏雷区配置文件是升级翻车的高发区。旧配置里某些参数在新版本中被改名或废弃启动时可能直接报unknown directive实例起不来。比如早期版本里slave开头的很多配置项在6.0之后逐步统一为replica前缀7.x里旧写法虽然还兼容但部分组合会告警。我的做法是升级前把现有redis.conf复制一份到测试环境用新版本二进制启动看它输出哪些warning和 error逐条处理后再真正切换。Lua脚本是另一个雷区。Redis 7.0开始限制了脚本对全局变量的访问老脚本如果里写了未声明就使用的变量升级后调用会直接报错。还有脚本复制方式的调整Redis 7.0默认用效果复制脚本执行结果不一致时主从复制可能出现数据不一致的风险。这属于行为层面的变化不跑一遍线上脚本根本发现不了。2.3 生态依赖盘点模块、客户端、监控与备份工具实例本身不是孤立的。如果加载了RediSearch、RedisJSON、RedisBloom等模块升级前必须先确认模块版本支持目标Redis版本否则新版启动时会报模块加载失败。客户端方面大部分老客户端走RESP2协议在新版本上可以继续用但如果客户端已经切到RESP3回滚到不支持RESP3的旧版本就会出问题。监控方面Prometheus的redis_exporter、云监控插件、日志采集服务都要确认对新版本的兼容性。还有备份和迁移工具链。比如用redis-shake做数据迁移它本身更新很快但不同版本对源实例的适配能力有差异升级前最好确认备份恢复工具能同时对接新旧两个版本。这些细节排查完后面操作才能顺畅。3. 两条主流升级路线原地升级与滚动切换的完整操作排查做完就该选路线了。这一节我把原地升级和滚动升级的完整操作都写出来每一步都附了实际命令和操作意图。3.1 原地升级适合能接受短停机的实例原地升级的核心思路是新版二进制直接接管旧数据目录。先做备份再停服替换最后启动验证。第一步备份数据文件。先看数据目录位置和关键配置redis-cli -h 127.0.0.1 -p 6379 -a 旧密码 --no-auth-warning info server | grep redis_version redis-cli -h 127.0.0.1 -p 6379 -a 旧密码 --no-auth-warning config get dir redis-cli -h 127.0.0.1 -p 6379 -a 旧密码 --no-auth-warning config get dbfilename确认后把整个数据目录复制到独立备份位置再用redis-cli的--rdb参数导出一份最新的RDB快照mkdir -p /data/backup/redis_upgrade_$(date %F) cp -r /data/redis /data/backup/redis_upgrade_$(date %F)/ redis-cli -h 127.0.0.1 -p 6379 -a 旧密码 --no-auth-warning --rdb /data/backup/redis_upgrade_$(date %F)/dump_manual.rdb这里解释一下为什么两步都要做直接拷贝dump.rdb和AOF文件保住的是实例的实时状态再用--rdb导出一份等于给数据文件做了一次呼吸确保备份文件和Redis内存状态一致。两份备份在回滚时能互相印证。第二步编译安装新版Redis。我习惯在独立目录安装不覆盖系统默认路径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) BUILD_TLSyes make install PREFIX/usr/local/redis-7.2.4 ln -sfn /usr/local/redis-7.2.4/bin/redis-server /usr/local/bin/redis-server ln -sfn /usr/local/redis-7.2.4/bin/redis-cli /usr/local/bin/redis-cli编译前确认系统装好了gcc和make老一点的CentOS还要装tcl用于执行测试。BUILD_TLSyes是给后续可能启用的TLS连接留后路生产环境建议加上。第三步停服并替换二进制。这里重点提醒停服用shutdown nosave避免退出时写入新格式的RDB覆盖掉旧文件现场。redis-cli -h 127.0.0.1 -p 6379 -a 旧密码 --no-auth-warning shutdown nosave停服后确认进程退出然后备份旧二进制再把新版本的二进制放过去cp /usr/local/bin/redis-server /usr/local/bin/redis-server.bak.old cp /usr/local/bin/redis-cli /usr/local/bin/redis-cli.bak.old第四步启动前先做一轮配置预检。直接启动怕被配置文件里废弃参数卡住我习惯先在前台启动一次把错误日志看清楚redis-server /etc/redis/redis.conf --port 6380如果配置有问题日志会直接显示第几行报错。--port 6380是测试端口确认正常后用CtrlC停掉再正式启动。正式启动命令根据Linux发行版来用systemd管理的systemctl start redis不看一眼日志不要认为服务起来了。启动后立刻看redis-cli -h 127.0.0.1 -p 6379 ping redis-cli -h 127.0.0.1 -p 6379 info server | grep -E redis_version|redis_mode|uptime_in_seconds redis-cli -h 127.0.0.1 -p 6379 dbsize原地升级最大的优点是简单直接缺点也很明显从停服到恢复服务窗口是分钟级别的且升级后的启动依赖旧数据文件和旧配置的兼容性。测试环境先演练一遍会稳妥得多。3.2 滚动升级主从架构下的平滑切换如果实例是主从或哨兵架构滚动升级是更体面的做法。核心逻辑是先升级从库再把从库提升为主库最后把老主库降级为从库完成升级。第一步在从库上安装新版本方式与原地升级的编译安装一致。从库替换完二进制用原配置启动。它会自动向主库重新发起同步请求继续担任从库角色。这时候要盯同步状态确认它追平主库redis-cli -h 从库IP -p 从库端口 -a 密码 --no-auth-warning info replication关注两个字段master_link_status必须是upslave_repl_offset要和主库的master_repl_offset一致或者非常接近。追平的标准是主库没有新写入时两边offset完全相等有新写入时差值极小且在持续追赶。第二步确认从库追平后执行主从切换。在从库上执行redis-cli -h 从库IP -p 从库端口 -a 密码 --no-auth-warning replicaof no one这会让从库解除复制关系变成独立主库。命令执行后立刻确认新主库的可写状态和offsetredis-cli -h 从库IP -p 从库端口 -a 密码 --no-auth-warning info replication | grep role第三步把客户端流量切到新主库。这里的前提是前面架构上做了统一入口比如VIP、DNS、接入层转发如果客户端把IP写死这一步操作规模会大很多。切换后观察新主库的连接数和QPS确认流量正常。第四步升级老主库。把老主库的二进制替换为新版本然后用replicaof让它重新挂到新主库下面redis-cli -h 老主库IP -p 老主库端口 -a 密码 --no-auth-warning replicaof 新主库IP 新主库端口老主库会做一次全量同步把这个窗口期新产生的数据拉过来。等同步完成整个实例组就是新版本一主一从的架构了。这里有个哨兵环境下的实战提醒如果架构里带了Sentinel手动切换时Sentinel可能因为检测到主库异常而自动发起failover打乱你的节奏。我的做法是维护窗口内先停掉Sentinel进程完成切换后再启动或者直接用SENTINEL FAILOVER命令让哨兵做一次受控切换。不要一边手动切一边让Sentinel自动切那样会出双主或多轮切换的乱局。Cluster模式也是同样的思路只是要把每个分片的主从都按这个流程过一遍一次只能做一个分片确保集群整体可用性。3.3 两种路线怎么选对比维度原地升级滚动升级停机窗口分钟级停服期间完全不可用秒级到分钟级取决于切换速度操作复杂度低适合单机或测试环境中高需要主从同步、角色切换配合数据安全依赖备份和回滚预案依赖同步追平切换本身可逆回滚难度高要恢复备份文件低再切换一次角色即可适用场景可接受短停机的生产实例、测试环境高可用要求的主从/哨兵架构一个常见误区是实例是单机但业务其实可以停两分钟结果非要去折腾哨兵反而把架构搞复杂。反过来如果业务一分钟都不能断那原地升级的方案直接排除别抱侥幸心理。4. 升级现场最容易翻车的三个坑以及我的完整排查链路升级的过程中最怕的是命令敲完了服务起不来或者起来之后业务报错。下面三个坑很典型我把排查思路也一起写出来方便大家遇到类似问题时照着走一遍。4.1 坑一启动时报配置项错误直接起不来现象新版本启动时日志里出现Unknown directive或Bad argument实例反复重启失败。这个在我处理过的升级案例里出现频率很高尤其是配置文件积累了很多年、里面塞了各种历史参数的实例。排查链路先不要慌找出问题配置项。直接在命令行前台启动redis-server /etc/redis/redis.conf日志会输出具体的行号和报错内容。定位到之后打开配置文件找到对应行对照新版官方redis.conf的注释确认是被改名了还是彻底废弃了。如果是被改名替换成新名字如果彻底废弃注释掉让默认值接管。处理完再启动一轮直到日志里没有error级别信息。我的经验是不要一次性改多行。每次只改一个坏项启动验证一次这样即使引入新问题也能立刻知道是哪次改动引起的。4.2 坑二升级后客户端连接数飙高、命令超时现象版本切换后原本稳定的客户端连接大面积重建服务端connected_clients曲线猛涨同时慢日志里出现一批原本几毫秒就返回的SET/GET命令。排查链路第一步先看基础指标redis-cli info clients redis-cli slowlog get 50第二步检查客户端连接空闲超时。新版Redis默认配置了tcp-keepalive默认值通常为300秒而老版本可能没开启。这意味着空闲连接在服务端被更快回收客户端连接池如果没有完善的空闲检测就会反复重建连接。我遇到过一次类似案例最终解法是把客户端连接池的空闲检测时间调到小于服务端keepalive周期同时把服务端tcp-keepalive调到一个和业务访问规律匹配的值。如果升级后的实例已经开启了io-threads还要观察多线程配置是否合理线程数一般不要超过机器CPU核数的一半乱开反而会增加锁竞争。这种问题最忌讳一上来就回滚。先确认是不是连接管理层面的参数差异很多情况下调整一下配置就能稳定。4.3 坑三老Lua脚本疯狂报错现象升级后业务反馈原来正常的原子操作频繁报错错误信息类似Script attempted to access nonexistent global variable。排查链路这是Redis 7.0对Lua脚本行为收紧导致的。老脚本里如果用了未声明的全局变量或者依赖了Lua环境中的隐式变量新版本直接拒绝执行。这类问题靠拍脑袋改脚本风险很大正确做法是把线上脚本从redis-cli script dump导出来逐个在测试环境用新版本执行一遍确认报错后把脚本里的变量全部local化消除隐式全局变量依赖对7.0开始启用效果复制的情况比对主从两边的脚本执行结果确认一致。排查这类问题的核心法则是现象发生在升级之后先假设所有行为差异都和版本有关用二分法把差异点定位到具体命令或脚本而不是在业务代码里瞎找原因。5. 升级后的体检清单验证新版本真正接管业务升级完成只是开始真正的验证在服务起来之后。我把体检分成两轮第一轮看进程和基础指标第二轮做数据完整性对比。5.1 第一轮体检进程、日志、基本指标服务起来后至少要把下面这张表过一遍检查项命令关注点版本redis-cli info serverredis_version是否为目标版本连通性redis-cli ping返回PONG连接数redis-cli info clientsconnected_clients与升级前基线对比内存redis-cli info memoryused_memory是否正常mem_fragmentation_ratio是否在1到2之间持久化redis-cli info persistencerdb_last_bgsave_status和aof_last_bgrewrite_status均为ok主从状态redis-cli info replicationrole正确master_link_status:up慢日志redis-cli slowlog get 50没有出现大量新增慢命令同时Linux系统层面的两项参数也建议再确认一次。第一个是透明大页THPRedis官方强烈建议关闭查看方式cat /sys/kernel/mm/transparent_hugepage/enabled如果显示[always]建议改为never。升级换上新版本之后内存分配行为可能变化THP开启状态下碎片问题更容易暴露。第二个是vm.overcommit_memory需要时设为1避免持久化fork时内存分配失败。这些参数不直接属于Redis升级但升级窗口内顺手检查最合适。5.2 第二轮体检数据完整性对比基础指标没问题再看数据。最简单的是对比dbsize但只对比key总量远远不够。我习惯做两轮校验第一轮是抽样校验。选业务核心的几个前缀升级前和升级后分别扫描抽一批key记录每个key的TYPE、TTL、STRLEN或LLEN然后做diff。命令思路如下redis-cli -a 密码 --no-auth-warning --scan --pattern user:* | head -n 200拿到key名单后逐个取字段类型和TTL。抽样校验不必覆盖全量但每个业务核心前缀至少抽200个能发现大部分类型错乱、TTL丢失的问题。第二轮是主从offset校验。如果有从库检查主库的master_repl_offset和从库的slave_repl_offset是否持续追平。对于数据量特别大的实例还可以借助redis-full-check这类开源工具做一次源端和目标端的一致性对比不过它有额外的资源开销建议在业务低峰期跑。5.3 观察期怎么设升级后不要立刻宣布完事。我的习惯是设置至少半天的观察期重点盯四类信号日志里有没有新的error或warning、慢日志数量是否增长、连接数和内存曲线是否平稳、客户端有没有超时上报。观察期间如果出现异常先按下文回滚预案的判断标准走不要抱着再等等看的心态让事故扩大。6. 回滚预案宁可不用不可不备升级失败不是小概率事件所以回滚预案必须在动手前写好。真出问题时照着预案执行比临时查文档靠谱得多。6.1 回滚触发条件我给自己定的三条硬性触发条件是新版本连续重启失败或无法进入可用状态数据加载后抽样校验发现异常业务大面积报错且半小时内无法定位根因。任何一条命中直接启动回滚流程。6.2 原地升级的回滚操作前提是升级前按前面步骤做了完整备份且备份文件没有被动过。回滚步骤redis-cli shutdown nosave停掉新版本后把新版运行期间生成的数据文件先移走不要直接删除留作事后分析。然后把备份的dump.rdb和AOF文件恢复到原数据目录换回备份的旧二进制cp /usr/local/bin/redis-server.bak.old /usr/local/bin/redis-server cp /usr/local/bin/redis-cli.bak.old /usr/local/bin/redis-cli最后启动旧版本验证dbsize和抽样key。这里要有个觉悟如果新版已经运行了一段时间期间有业务写入那么回滚到旧版本会丢失这段时间的新数据。这个取舍必须在升级前就和业务方对齐写进预案里。6.3 滚动升级的回滚操作滚动升级的回滚要轻松很多因为本质上只是再做一次角色切换。如果新版主库出现问题让老主库执行replicaof no one恢复成主库客户端流量切回老主库即可。操作前同样要确认老主库数据没有被清理二进制路径还在。但滚动升级有个前提切换后的新主库如果已经承接了大量写入回滚一样会丢新主库期间的数据。所以即便方案可逆也需要业务方确认可以接受这个窗口期。我在实际操作中的体会是回滚脚本一定要先在测试环境完整演练一遍真到紧急的时候练过的脚本比临场敲命令可靠得多。7. 升级完成之后顺手把Redis治理往前推一步升级到新版本不只是把版本号变一下更是一次整理Redis治理的好机会。下面这几件事每次升级完我都会顺手做掉收益很大。7.1 配置现代化从requirepass到ACL老版本Redis很多还在用全局requirepass一个密码管所有权限。到了6.0以上可以启用ACL按用户分配权限和key空间。比如给应用账号限定只能访问特定前缀的keyredis-cli -a 密码 --no-auth-warning ACL SETUSER app_user on AppPassw0rd ~app:* read write -admin给监控账号只读权限给管理账号保留完整权限。权限细化之后即使某个账号泄露影响面也有限。7.2 让持久化策略跟上版本特性升级到7.x后AOF已经支持multi-part格式重写期间对主进程的阻塞更小。这时候重新审视一下appendfsync策略和AOF重写触发条件和业务对数据安全的要求对齐。如果业务允许秒级丢失everysec依然是性价比最高的选择如果完全不能丢那就要考虑同步写并配合硬件能力做容量规划。别盲目把所有参数调到最安全性能代价往往超出预期。7.3 把部署和备份沉淀成工具链升级一次Redis最烦的是很多手动操作。我现在的习惯是升级完立刻把部署流程沉淀下来用systemd统一管理实例生命周期避免裸进程没人管把配置文件整理成标准化模板纳入配置管理备份脚本定期执行并定期演练恢复。一个可以照抄的systemd unit示例[Unit] DescriptionRedis Server Afternetwork.target [Service] ExecStart/usr/local/bin/redis-server /etc/redis/redis.conf ExecStop/usr/local/bin/redis-cli shutdown nosave Restarton-failure Userredis Groupredis [Install] WantedBymulti-user.target这些工作看起来和升级关系不大但正是它们决定了下一次升级时你是从容地走流程还是又变成一次惊险的现场救火。我在实际运维中体会最深的一点是升级这事做完不是终点能够在下次升级时把流程跑得更顺才是真正的收益。每次升级后我会把检查清单和踩过的坑都存进团队文档下次直接照着做。最后提醒一句任何升级方案都别忘了先在自己的测试环境完整过一遍。
返回列表