跳至正文

4 台服务器迁移,熬了 3 个通宵,我差点把公司数据库删了

上个月老板说要把所有业务迁到新机房,理由是“老机房太贵了,新机房便宜一半”。我当时脑子里只有一个念头:又要熬夜了。

结果这趟浑水趟完,我发现网上那些“平滑迁移”的教程全是骗人的。真实情况是:你永远猜不到下一个坑在哪。

先说背景。我们公司跑的是典型的 LAMP 架构,4 台物理机:两台 Web 服务器跑 Nginx 和 PHP,一台 MySQL 主库,一台从库。老板要求周末两天内完成迁移,周一早上用户打开网站不能有任何异常。

我当时觉得两天够了,毕竟数据量不大,总共才 200GB 左右。现在回想起来,这个想法本身就是第一个坑。

周五晚上 10 点,我登录老机房开始打包数据。Web 服务器倒好说,直接 rsync 同步代码和静态资源就行,十几分钟搞定。但 MySQL 那边我犯了第一个错误——我直接在业务高峰期跑了 mysqldump。

当时主库的 QPS 大概 3000 左右,dump 命令一执行,监控面板上的慢查询曲线直接起飞。有个同事在群里喊:“后台订单列表加载不出来了!” 我赶紧 Ctrl+C 停了 dump,然后发现那个 dump 文件才跑了三分之一,已经 80GB 了,而且因为强行中断,文件是坏的。

后来我学乖了,等到凌晨 2 点流量最低的时候,先用 `SHOW SLAVE STATUS\G` 确认从库没有延迟,然后在从库上执行 dump。命令是这样的:

“`
mysqldump –single-transaction –routines –events –triggers –master-data=2 -u root -p database_name > backup.sql
“`

关键参数是 `–single-transaction`,它会开启一个事务,保证导出数据的一致性,同时不会锁表。但注意,这个参数只对 InnoDB 有效,如果你表里有 MyISAM,该锁还是锁。

数据导出花了大概 40 分钟,200GB 的 SQL 文件。然后我 scp 到新机房的机器上,结果发现带宽只有 100Mbps,传了将近 5 个小时。这又是一个坑——我提前没测试新机房的网络带宽。

等文件传完,已经是周六早上 8 点了。我开始在新机器上导入数据。source 命令跑起来之后,我出去买了杯咖啡,回来发现控制台报错了:

“`
ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled
“`

这个错误是因为我们的数据库里有一些自定义函数,而新机房的 MySQL 版本是从 5.7 升到 8.0 的。8.0 对二进制日志的检查更严格了。解决办法是在导入前先设置一下变量:

“`
SET GLOBAL log_bin_trust_function_creators = 1;
“`

或者更稳妥的做法是在 dump 文件里把这个参数写进去。我选择直接 set global,然后重新 source。这次没报错了,但导入速度慢得离谱——200GB 的 SQL 文件,导了 6 个小时还没完。

我查了一下,是因为默认的 `max_allowed_packet` 太小,导致每个 insert 语句都要拆成很多个小包。我把这个值改大,同时关掉了 binlog 和双写缓冲,速度才提上来。具体操作:

“`
SET GLOBAL max_allowed_packet = 256M;
SET GLOBAL binlog_format = ‘ROW’;
SET GLOBAL innodb_doublewrite = 0; — 导入完记得改回来
“`

到周六晚上 9 点,数据总算导完了。我开始切域名,把 DNS 解析从老机房的 IP 改成新机房的。这里我犯了一个低级错误——我忘了 A 记录有 TTL 缓存。老机房设置的 TTL 是 600 秒,也就是 10 分钟,但很多运营商 DNS 根本不管你设置,缓存个半小时是常事。

结果就是,有一部分用户访问的还是老机房,另一部分用户已经解析到新机房了。而新机房的 Web 服务器连的是新数据库,老机房的 Web 服务器连的也是新数据库——因为我把代码里的数据库连接配置改了,但老机房的代码没有更新。

这就导致了数据不一致:用户在老机房写的评论,存到了新数据库,但他在新机房读取的时候,因为某些缓存机制,可能看不到。更严重的是,有用户在老机房下单成功,但新机房那边订单号冲突,直接报了主键重复错误。

我赶紧把老机房的 Nginx 停掉,然后在 Nginx 里配了一个 302 重定向,把所有老机房的请求都转发到新机房。但这样又导致了一个新问题——老机房那边的 PHP 进程还没退出,有些长连接还在跑,导致数据库连接数飙升,直接打到了 MySQL 的 max_connections 上限。

监控告警疯狂弹出,我手忙脚乱地 `kill` 掉老机房的所有 PHP 进程,然后重启了新机房的 Nginx 和 PHP-FPM。这时候已经是周日凌晨 3 点了,网站总算能正常访问了。

但还有最后一个坑等着我——SSL 证书。我们用的 Let’s Encrypt 证书,新机器上忘记配置自动续期了。结果周一早上 9 点,老板给我打电话:“网站打不开了,显示证书错误。” 我打开浏览器一看,证书正好在当天凌晨过期了。我赶紧手动续期,然后重启 Nginx,但谷歌浏览器有 OCSP 缓存,部分用户直到中午才恢复正常。

总结一下这次迁移踩的坑,给兄弟们当个参考:

第一个,永远不要在生产高峰期做数据导出。凌晨 2 点到 6 点是最佳时间窗口,提前跟运维确认好流量低谷时段。

第二个,新机房的网络带宽一定要提前测试。用 iperf 跑一下,确认传输速度能接受,不然光传数据就能把你耗死。

第三个,MySQL 版本升级要小心。5.7 到 8.0 之间的坑特别多,除了我说的函数创建权限问题,还有字符集默认值变了、GROUP BY 语法更严格了等等。建议先在测试环境跑一遍。

第四个,DNS 切流量一定要提前把 TTL 改小。至少提前两天把 TTL 改成 60 秒,这样切换的时候生效快。而且最好保留一个回退方案——万一新机房挂了,还能快速切回老机房。

第五个,SSL 证书续期要写进 cron 或者 systemd timer。Let’s Encrypt 的证书有效期只有 90 天,忘了续就是事故。我现在都在新机器上配了自动续期脚本,每个月检查一次。

这次迁移前前后后折腾了 3 天,我睡了不到 10 个小时。后来老板问我为什么周末没回消息,我说在机房修仙呢。他笑了笑,说下个月所有服务器还要再升级一次硬件。

我当场就想把辞职信拍他桌上。


📎 延伸阅读

看完这篇,如果你想:

  • 直接拿工具 → 回复”13“,我把跨境获客工具包发给你
  • 系统学习 → 点击菜单”AI训练营”,从0开始跑通AI变现

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注