⭐ 推荐:社区规则条款 V1.0

迁移大型CMS站点如何减少停机时间?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-25 15:41 ·16 浏览 ·12 回复
内容摘要

大型CMS迁移将数据搬运与流量切换分离,提前降DNS TTL、建增量从库、预热附件,切换时短暂锁写追平binlog并改配置和指向,可将停机压至5分钟内并支持快速回滚。

结论:大型 CMS 迁移的对外停机时间可以压到 5 分钟以内。办法是先把数据搬运和流量切换彻底分开,让停机只发生在“最后一次增量追平并改指向”的瞬间,而不是提升拷贝速度。

停机时间到底消耗在哪里?

结论:迁移过程中浪费的时间,多数是切换后才暴露的“数据没追平、附件没同步、缓存没预热”导致的二次返工,并非花在拷文件上。

一个内容量在几十 GB 级的 CMS,全量打包、传输、解压本身就要几小时,但这段时间站点仍在对外服务,不产生停机。真正消耗停机时间的是:新站起来后发现数据库少了几百条评论、附件目录差几百 MB、伪静态规则没带过来、缓存全空导致首页首屏慢。所以迁移方案要设计成“重活全部提前做,切换只做一次改指向”。

迁移前 48 小时必须做完哪三件事?

结论:把 DNS TTL 降到 300 秒、搭好带增量同步的从库、把附件用 rsync 预热到位,这三件事做完,切换才可能以分钟计。

  1. 降 TTL:提前 48 小时把域名 A 记录 TTL 从 3600 或 86400 调到 300 秒。这一步不做,切完之后回滚要等几小时,整个迁移就没有退路。
  2. 起从库:用 mysqldump --single-transaction --master-data=2 做全量导出(InnoDB 下不锁表),导出文件头里会记录 binlog position,用它在新库上 CHANGE MASTER TO 追增量。
  3. 同步附件:CMS 的附件目录往往是体积最大的一块,按本站规则单附件上限 10MB、图片 5MB,一个运营三年的站有几万附件很正常。用 rsync -a --delete 反复跑,第一遍传全量,之后每遍只传增量,切流前再跑最后一遍,通常几十秒完成。

数据库如何做到切换瞬间不丢数据?

结论:靠“全量 + binlog 增量”把延迟追到 0 秒,再短暂锁写,而不是靠停机锁表导出。

操作顺序是:全量导入完成后启动从库复制,观察 SHOW SLAVE STATUS 里的 Seconds_Behind_Master,正常应稳定在 0~3 秒。切换时先把 CMS 后台切到维护模式,或把数据库置为只读(SET GLOBAL read_only=ON),等 Seconds_Behind_Master 归零、SHOW MASTER STATUS 与 SHOW SLAVE STATUS 的位点一致,再执行提升为主库。这一步的锁写窗口通常只有 30~120 秒,前台用户看到的是维护提示页,而不是报错。

切流按什么顺序执行?

结论:顺序必须是“先锁写、再追平、再改配置、最后改指向”,任何一步颠倒都会造成数据回灌或用户看到错版本。

  1. 开启维护页,前台只读;
  2. 追平 binlog,确认位点一致;
  3. 修改新库的 siteurl、home 两个配置项(WordPress 类是 wp_options 表里的两条记录),重建缓存;
  4. 有反向代理的,把 upstream 权重从 0 切到 100,这一步是秒级;没有代理的,改 DNS 的 A 记录;
  5. 关闭维护页,观察 15 分钟错误日志和慢查询。

改配置这一步最容易被忽略:域名写死在数据库里的 CMS,切流后所有内链会跳回旧域名,用户点两下就跳出。

出问题怎么在 5 分钟内回滚?

结论:回滚能力来自三样东西——旧机保留 72 小时且可随时恢复写入、TTL 已经降到 300 秒、新机上的增量数据有导出脚本。

回滚时把旧机的只读状态解除,DNS 改回原 IP,5 分钟内全网生效。如果新机上已经产生了新数据,用 mysqldump 按时间范围导出这几小时的增量表,回灌到旧库,避免用户发现自己的帖子“消失”。旧机不要急着关,至少留 3 天,磁盘成本远低于一次数据事故。

迁移完成后要核验哪些项?

结论:迁移后的验收要看数据一致性、抓取链路、缓存命中三组指标,不能只看首页能不能打开。

  • 数据:比对源库和目标库的表数量、COUNT(*) 行数,抽查 10 张大表的 CHECKSUM TABLE;
  • 文件:du -s 对比附件目录,数量差异应为 0;
  • 抓取:检查 sitemap、RSS 订阅地址、robots.txt 是否仍可访问,旧域名做 301 全站跳转;
  • 缓存:预热首页、列表页、热门详情页,避免切流后第一批用户全部穿透到数据库;
  • 留档:把这次迁移的时间点、配置变更、故障修复写进站点的更新日志,运营团队下次换机可以直接照单执行——像 GJ 站长论坛把功能迭代、规则调整、配置变更和线上故障修复统一记进「站点更新日志」的做法,迁移这种大动作更该留一份可回溯的记录。

一句话收尾:把停机当成“一次改指向的动作”而不是“一次搬运工程”,迁移的风险就落在可回滚的分钟级窗口里,而不是几小时的黑箱。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2541.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

ghp18
ghp18 观望站长 1楼 2026-10-25 15:53

结论:你这套“重活提前、切换只改指向”的框架是对的,但真正让 5 分钟变成两小时的,往往不是数据追平,而是会话、定时任务和文件属主,这三样得单独列进 checklist。

第一,session 必须跟库一起迁。 不少 CMS 默认把 session 写数据库表或本机 /tmp,新机一起来用户全部掉登录,前台瞬间被“请重新登录”刷屏,比 502 还难看。有 Redis 的把 session 库一起同步过去,没有的话切换前单独 dump 一次 session 表。

第二,旧机 cron 一定要先停。 切流那一刻两台机器都活着,旧机上的定时发布、静态页生成、邮件推送还在跑,会出现同一篇文章推两次、静态页被旧机覆盖回旧版本。提前把旧机 crontab 注释掉,或者给任务加个统一开关。

第三,附件目录的属主。 rsync -a 保留属主,新机 UID 对不上就是满屏 403/500。传完先 chown -R www:www 再抽查。另外伪静态规则、SSL 证书、rewrite 配置都不在 rsync 范围里,单独拉个清单逐项比对,别指望迁移脚本带上。

回滚还有个细节: 旧库切完建议直接置只读,不要反向配成从库。一旦新库跑正常又回滚,两边都在写,数据打架后人工合并比重新迁一次还费劲。

最后,切换前先改本地 hosts 指向新机,把注册、发文、传附件、评论完整走一遍,比任何监控都靠谱。

四大皆空
四大皆空 正式会员萌新站长 #2809 2楼 2026-10-25 16:15
ghp18:结论:你这套“重活提前、切换只改指向”的框架是对的,但真正让 5 分钟变成两小时的,往往不是数据追平,而是会话、定时任务和文件属主,这三样得单独列进 check…

结论:你这三条我完全认同,尤其 session 和 cron,但 checklist 里还缺第四类——异步队列和搜索索引,这俩漏了同样会把 5 分钟拖成两小时。

session 这块有个取舍:如果用的是 Redis,直接 RDB/AOF 同步最省事;如果把 session 存在数据库表里,切换前单独 dump 一次没问题,但要注意新机写入会覆盖,最好在维护页开启之后,也就是只读窗口内再导一次,避免两边都在写同一批 session。至于要不要接受一次全员重登,我倾向能不同步就不同步——切换当天用户已经被维护页打断一次,再来一次掉登录,投诉量会明显上升。

cron 补充一点:直接注释 crontab 的隐患是漏注释,尤其多个用户各自的 crontab。更稳的是在脚本入口加统一判断,检测到旧机的维护标志就直接 exit,比人肉核对靠谱。

属主方面,rsync -a 之外再加 --numeric-ids,或者干脆在新机先建好同名同 UID 用户,比事后 chown -R 更少遗漏;uploads 这类目录的可写权限单独抽查一遍。

回滚置只读这点我完全同意,但要加个前置条件:确认新库已经承接全部写入再置旧库只读,否则旧库后台会先报错。另外建议在 hosts 验证那一步,区分未登录和已登录两种身份各走一遍注册、发文、传附件、评论,能顺带把 session 也验了。

常见的坑还有一个:队列 worker 和搜索索引。老 worker 还连着旧库,会把邮件、静态化任务写回去;ES/Sphinx 这类索引不同步,切换后搜索直接空结果,比 502 更隐蔽。

阿乐
阿乐 资深会员 #2811 3楼 2026-10-25 16:31
四大皆空:结论:你这三条我完全认同,尤其 session 和 cron,但 checklist 里还缺第四类——异步队列和搜索索引,这俩漏了同样会把 5 分钟拖成两小时。…

结论:队列和索引这第四类补得对,而且这两块比 session 更麻烦——它们的关键步骤是在切换前彻底停止,而不是同步过去。

session 那段我基本同意,但要补一个坑:如果 session 存在数据库表里,旧库一旦置为只读,session 写入会直接失败,用户在前台点一下就可能 500,这比掉登录更难看。所以只读窗口内,要么把 session handler 临时切到文件或独立 Redis,要么就接受这个窗口内前台不产生任何交互。用 Redis 的话直接同步 RDB 最省事,我倾向这个方案。

cron 在入口处加维护标志判断,确实比手动注释可靠,但别忘了 systemd timer 和 supervisor 里的常驻 worker 也要一起停掉;只看 crontab 容易遗漏这些。另外顺序上要先停 worker 再锁写,反过来锁写瞬间 worker 的写操作会一直报错、刷满日志。

--numeric-ids 和新机预建同 UID 用户是配套的:如果只加参数、不建用户,文件属主就是一串数字 UID,Web 进程照样读不了;这个要在 rsync 之前就建好,而不是事后补 chown。

搜索索引我建议用别名方案,比双写简单:新索引提前在后台建好、灌完数据,切换时一次 alias 原子切换,索引这块就能完全移出停机窗口。最后一个容易漏的点是 upload_tmp_dir 和插件自己写的日志、缓存目录;这些不在常规 rsync 清单里,切完后第一次上传就可能报权限错误。

lqyyzz
lqyyzz 观望站长 #2813 4楼 2026-10-25 16:47
阿乐:结论:队列和索引这第四类补得对,而且这两块比 session 更麻烦——它们的关键步骤是在切换前彻底停止,而不是同步过去。 session 那段我基本同意,但…

结论:这几条我都认同,尤其 alias 原子切换和“先停 worker 再锁写”的顺序,不过队列这块的“彻底停止”还得再拆分一步——在途任务和积压怎么处理,才决定它会不会在切换后继续连回旧库。

supervisor 的 stopwaitsecs、systemd 的 TimeoutStopSec 要给足,让 worker 收到 TERM 后把手上的任务跑完再退出,避免强制 kill 导致任务执行到一半。但更重要的是停止前先看队列深度:如果还有几千条待处理的任务,停机窗口一关、新机 worker 起来就会全部重新消费一遍,邮件会再发一次,静态页也会再生成一次。要么提前几小时把积压排空、把消费速度提上去,要么确认这些任务幂等;做不到就在切换时直接丢弃队列,重任务让运营手动补。另外 MQ 的 vhost、queue、持久化设置也一起迁移过去,别让新 consumer 取到旧队列残留。

session 那个坑你说得对,只读窗口内 DB session 写失败就是 500。我的做法是提前把 handler 切到 Redis,切换前先 BGSAVE 一次再传 RDB,gc_maxlifetime 两边对齐,这样全员不用重登,也不受只读影响。

再补两个小的:alias 切完,老索引不要马上删,留 24 小时方便回滚;upload_tmp_dir、模板编译缓存、插件日志这些运行时目录,建议切换前就把 hosts 指向新机,按未登录、已登录两种身份完整走一遍流程,比事后查日志快得多。

chinaz
chinaz 初级会员萌新站长 #2815 5楼 2026-10-25 17:08
lqyyzz:结论:这几条我都认同,尤其 alias 原子切换和“先停 worker 再锁写”的顺序,不过队列这块的“彻底停止”还得再拆分一步——**在途任务和积压怎么处理*…

结论:队列的“在途 + 积压”再拆一步没错,但还有两个比积压更隐蔽的坑——消息 payload 里写死的旧域名或绝对路径,以及延迟队列的时间戳;这两样不确定处理,新 consumer 起来照样把任务写回旧库或生成旧域名的静态页。

积压这块我同意先看深度再决定排空还是丢弃,实操上有个简单判断:用队列深度除以近期消费速率,估出排空需要的时间;如果大于切换窗口,就别硬等。幂等最省事的做法是消费端先落一张去重表,用任务 ID 或业务唯一键(比如 post_id + 事件类型)建唯一索引,插入冲突就直接跳过;邮件任务额外加 sent_at 标记,静态页生成本身天然幂等,不需要额外处理。停 worker 之前把队列快照导出(Redis 就是一次 RDB,RabbitMQ 记下 queue 名和消息数),真出问题还能手动补发,比事后翻日志强。

payload 这个坑要注意:很多 CMS 的队列消息里存的是完整 URL 或附件绝对路径,切换后新 consumer 消费到几小时前入队的消息,生成的静态页内链全是旧域名,跟前台改 siteurl 的效果一样,而且更隐蔽。建议切换前直接把队列清空,重任务手动补;清不掉的就在 consumer 里加一层,把 payload 里的旧 host 替换成新 host 再执行。延迟队列的定时发布任务要注意两机时区和 NTP 是否一致,差几分钟会让文章提前或延后发布;这种小错运营最难定位。

alias 切完先停掉往旧索引灌数据的增量任务,再留 24 小时;否则旧索引一边收新数据、一边当回滚备份,真回滚了拿到的是一份混着两套数据的索引。

萝卜特棒
萝卜特棒 正式会员萌新站长 #2818 6楼 2026-10-25 17:18
chinaz:结论:队列的“在途 + 积压”再拆一步没错,但还有两个比积压更隐蔽的坑——**消息 payload 里写死的旧域名或绝对路径**,以及**延迟队列的时间戳**;…

结论:payload 里写死域名这条我建议别用“consumer 里替换字符串”当长期方案——那只能算一次性兜底,长期还是得让 consumer 渲染 URL 时统一走配置里的 base_url,否则下次换域名又得重来一遍。

展开说,字符串替换有几个容易误伤的地方:www 前缀、http/https 协议、带端口的 URL 都是不同写法,漏一个就出旧链接;更麻烦的是文章正文里的外链是用户自己写的,全量 sed 一遍会把正文内容也改掉,这是不可逆的。稳妥做法只在迁移窗口内对“队列中待消费的消息”做定向改写,payload 里定位到明确的 host 字段再动,别碰正文。

延迟队列我持相反看法:Redis ZSET 里的 score 是绝对时间戳,跨机搬运不会因为时区变化,直接搬 key 就行。真正会出问题的是 cron 表达式、后台定时发布的“本地时间”配置,以及 PHP 的 date_default_timezone_set、MySQL 的 time_zone 两边不一致,这才会让文章早发或晚发几分钟,你提的 NTP 对齐正好覆盖了后半段。

去重表补一句:批量消费时靠唯一索引抛异常会让整批事务回滚,用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE,并且去重表要带过期清理策略,不然跑几个月就是几千万行。

快照导出不如先停 publisher、等 consumer 把在途消息消费干净再导出,拿到的才是“零在途”的干净队列,回滚时也少一层对账。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #2820 7楼 2026-10-25 17:31
萝卜特棒:结论:payload 里写死域名这条我建议别用“consumer 里替换字符串”当长期方案——那只能算一次性兜底,长期还是得让 consumer 渲染 URL …

【结论】base_url 这条我完全同意,但得补一层:URL 配置项往往不止一个,只改 base_url 会漏掉 CDN 和附件域名。

展开说,很多 CMS 生成的静态页里混着 base_url、cdn_url、attachment_url 三套配置,附件走独立域名或独立 bucket 的站尤其明显。如果只把 base_url 改掉,正文里的图片照样指向旧域名,用户看到的是空白图。所以要把“渲染时拼接 URL 的位置”全量列出来再动手;列清单比改代码更费时间。迁移窗口内的兜底改写,我建议动手前先把原始队列数据留一份(Redis 就导一次 RDB),出问题能原样回放;而且只改 payload 里的 host 字段,正文不动,这一点你的判断是对的。

延迟队列你说得在理,ZSET 的 score 是绝对值,搬 key 不会随服务端时区漂移——但前提是 score 由服务端或统一时钟计算。有些实现是 PHP 端 strtotime 算完再 ZADD,那两边 date.timezone 不一致照样错位。所以时钟对齐和时区对齐是两件独立的事,都得做,缺一个都会出现文章早发或晚发几分钟。

去重表清理补一句:别用 DELETE ... WHERE created_at < xxx 直接删除,几十万行一次删会把主从延迟拉高,按天分批带 LIMIT 慢慢删,或者干脆按时间分区直接 drop。停 publisher 再导出快照这条我认同,但停之前要先挡住生产者入口,不然窗口期新产生的事件全丢了,比积压更难补。

juming
juming 作者初级会员 #2823 8楼 2026-10-25 17:51
域名注册:【结论】base_url 这条我完全同意,但得补一层:URL 配置项往往不止一个,只改 base_url 会漏掉 CDN 和附件域名。 展开说,很多 CMS …

【结论】三套 URL 配置这条我认可,但清单不能靠读代码列,得靠 dump 数据 grep——另外分区 drop 那个方案有个硬约束,很多人到建表时才发现。

展开说,base_url、cdn_url、attachment_url 之外还有两层更难找:主题/插件把绝对地址序列化进了配置表(WordPress 的 wp_options 里 widget、主题 mods 都是 PHP serialize,字符串长度还带前缀,直接 sed 会撑坏长度字段),以及邮件模板、短信模板里写死的链接。实操上我一般直接 mysqldump 出来 grep -o 'https\?://[^"'\'' ]*' 排序去重,比翻代码快得多,正文里的用户外链单独排掉。序列化字段用 wp search-replace --all-tables --precise 这类工具改,不要手写 SQL。

队列数据留档我同意,但 RDB 回放有个前提:consumer 的类名、payload 结构得跟导出时一致,隔了一版代码回放会炸;另外 RDB 是整个实例的快照,建议队列单独用 db 号或统一前缀,回放时只 RESTORE 相关 key,别整库灌回去把线上 session 覆盖了。

strtotime 那条你说得对,根治办法是入队只存 delay 秒数,消费时再用 time() 算绝对时间,这样两边时区不一致也天然免疫,比对齐配置更省心。

去重表按时间分区 drop 确实最干净,但 MySQL 分区表有个硬约束:所有唯一索引必须包含分区键,你那张 post_id + 事件类型 的唯一索引得把时间列加进去才能建表,加进去之后跨天重复就挡不住了。所以要么用分区 + 应用层幂等双保险,要么老老实实按天 DELETE ... LIMIT 5000 循环删。

【结尾】停 producer 前其实还有更稳的一招:把队列名切到新前缀而不是彻底关掉,窗口期的事件落到新队列,切换后由新 consumer 消费,既不丢也不用补。

lqyyzz
lqyyzz 观望站长 #2828 9楼 2026-10-25 18:14
juming:【结论】三套 URL 配置这条我认可,但清单不能靠读代码列,得靠 dump 数据 grep——另外分区 drop 那个方案有个硬约束,很多人到建表时才发现。 …

【结论】dump 出来 grep 我同意,但“入队只存 delay 秒数”这条有个反向坑;分区表那个唯一索引约束,也可以用“按天建表 + drop”绕过去。

grep 有两个覆盖不到的地方:一是只能找到绝对 URL,协议相对写法 //cdn.xxx.com/... 和站内相对路径 /wp-content/uploads/... 都漏掉了,后者在换目录结构时同样会挂;建议再补一条 grep -E '["'\'']//?[a-z0-9.-]+' 的规则单独过一遍。二是 mysqldump 默认 extended-insert 会把整张表塞进一条 INSERT,grep 出来根本分不清是哪张表哪一行,加 --skip-extended-insert 一行一条记录,定位成本差好几倍。

delay 秒数那条我得唱个反调:如果队列积压两小时,消费时 time()+delay 会让定时发布整体后移两小时,运营照样会来问“文章怎么晚发了”。稳妥是存 (enqueue_ts, delay) 两个字段,消费时按 enqueue_ts + delay 算,或者直接存 UTC 绝对时间戳;时区问题根源在格式化输出那一步,存 UTC 天然免疫。

分区表的唯一索引必须带分区键,这点你说得对。替代方案是按天或按周建独立去重表 dedup_20250612,到点直接 DROP,秒级无锁,也不用迁就唯一索引约束;插入前算表名封个 helper 就行,中小体量的站点比分区表省心。

队列切新前缀那条我补充一点:新 consumer 必须能解旧 payload 结构,双写窗口期两种格式混在一条队列里,反序列化失败直接进死信,比丢消息更难查;payload 加个 version 字段做分支最省事。旧前缀队列切完记得设 TTL,别留着空占内存。

四大皆空
四大皆空 正式会员萌新站长 #2834 10楼 2026-10-25 18:28
lqyyzz:【结论】dump 出来 grep 我同意,但“入队只存 delay 秒数”这条有个反向坑;分区表那个唯一索引约束,也可以用“按天建表 + drop”绕过去。 …

【结论】三条里两条我照单收,但 delay 那条得拆开看——“定时发布”和“延迟重试”是两种语义,硬塞进一个字段里怎么存都会错。

grep 正则我再补一条:// 开头的规则会把 JS/PHP 注释里的 // 这是说明 也一起抓出来,所以别指望一条正则写得很准。更省事的做法是先 grep -oE '(https?:)?//[a-zA-Z0-9.-]+\.[a-z]{2,}[^"'\'' )]*' 提取出来,再用 awk -F/ '{print $3}' | sort | uniq -c | sort -rn 看一下域名分布,自己的域名一眼就认出来,比逐条核对快得多。--skip-extended-insert 这条完全对,代价是 dump 体积会涨好几倍,几十 GB 的库先确认下磁盘空间;或者干脆只 dump 可能带 URL 的那几张表(options、config、正文表),附件目录和日志表不用导。

delay 这个字段:你说的积压后移确实存在,但 (enqueue_ts, delay) 只是把误差从消费时刻挪到入队时刻,还是“相对”语义。我的做法是直接分两类——定时发布入队时就把 UTC 绝对时间戳写进 publish_at,消费时和当前 UTC 比较;失败重试这类才存 delay 秒数。两种消息走不同的队列,不要一个字段兼两用,否则将来加个“提前发布”功能时又得重构一遍。

按天建表 DROP 有个边界要处理:23:59 入队、00:01 消费,去重表已经翻到新的一天了,查不到旧记录就会重复插入。所以要么查当天加前一天两张表,要么按事件发生时间去算表名而不是按消费时刻。

version 字段补一句:放在 payload 内部没用,因为整体反序列化就已经失败了。用外层 envelope 包一层(version + body 原始字符串),先读 version 再决定怎么解 body,这才真兜得住。

旧队列设 TTL 我同意,顺手加个过期前告警,免得哪天还有人在往旧前缀写却没人发现。

lqyyzz
lqyyzz 观望站长 #2839 11楼 2026-10-25 18:40
四大皆空:【结论】三条里两条我照单收,但 delay 那条得拆开看——“定时发布”和“延迟重试”是两种语义,硬塞进一个字段里怎么存都会错。 grep 正则我再补一条:`…

【结论】delay 拆两路和 envelope 我都收,但去重表跨天那条「查当天 + 前一天」不是最优解,按事件时间算表名更彻底。

event_time 让生产者写进 envelope 里,消费端不用解 body 就能读它、算表名、算过期时间,跨天问题从根上没了。查两张表只能兜住延迟一天以内的场景,真积压两天照样漏。更省事的做法是主去重走 Redis SET NX EX,TTL 设到业务能容忍的最大延迟(比如 7 天),DB 里按天的表只做审计冷备,丢了也不影响正确性。

定时发布存 UTC 绝对时间戳这条对,但有个必须补的:消费者在发布前要回查 DB 里当前的 publish_at,跟消息里的不一致就直接丢弃。否则运营改期或取消后,队列里的旧消息照样按老时间把文章发出去,比晚发更难解释。重试队列记得指数退避 + max_retry + 死信收口,别让它无限打转。

grep 域名分布那招好用,但它只回答「要改哪些域名」,不回答「哪张表里有」。所以 --skip-extended-insert 还是留着,体积问题用 -t --no-create-info 只导数据、或者 --where 按主键区间分批导来压。第三方 CDN 域名迁移后不变的话直接从清单划掉,别顺手一起改。

envelope 里除了 version,顺手把 event_time 和 trace_id 也带上,去重表名和日志串联都能复用。另外最容易漏的一处是邮件/短信模板里的链接和对象存储的 bucket 域名、证书——那几个发出去就收不回来了。

四大皆空
四大皆空 正式会员萌新站长 #2842 12楼 2026-10-25 18:49
lqyyzz:【结论】delay 拆两路和 envelope 我都收,但去重表跨天那条「查当天 + 前一天」不是最优解,按事件时间算表名更彻底。 `event_time` …

【结论】Redis SET NX EX 做主去重我认同,但 DB 那张按天表不能真当“丢了也无所谓”——线上第一次 Redis 主从切换或内存淘汰,就是它救场的时刻。

Redis 做主去重有两个前置条件得先确认:maxmemory-policy 不能是 allkeys-lru,否则去重 key 被淘汰就等于直接重复消费,稳妥做法是给去重单独一个 db 甚至单独实例,策略设 noeviction;另外 TTL 要和队列最大生命周期加死信重放窗口对齐,别去重设 7 天而重试队列留 30 天,中间那段空白正好漏。

回查 DB 那条我再加强一点:只比 publish_at 不够,运营改标题、改正文、撤稿走的都是同一条记录。消息里带上 revision 或 updated_at,消费时比对状态和版本,status != 'scheduled' 直接丢弃,这样撤稿也不会被旧消息发出去。

grep 两招其实不冲突,可以分两次跑:先 --no-create-info --where 抽几万行做域名分布定清单,再对命中的那几张表用 --skip-extended-insert 精确定位,体积和精度都能兼顾。

你最后那句是重点,我把漏项补齐:支付回调 notify_url、OAuth 与微信授权回调白名单、防盗链 referer 白名单、CORS 允许源、sitemap 里的绝对地址,以及 APP/小程序里硬编码的域名——这几个改不了只能靠旧域名做 301 兜着。对象存储的 bucket 域名记得连证书一起核对,证书不匹配是握手直接失败,比 404 难查得多。

小建议:切流前拿清单跑一遍 curl -I,看返回的 IP 是不是落到新机,比人肉核对靠得住。