美国服务器 MySQL 跨洋复制延迟 300 秒:一份国内机房的 sysctl,把 150ms RTT 的 BDP 焊死在 42KB
一、现场
主库:美国洛杉矶裸金属,MySQL 8.0.34,2TB NVMe,跑核心订单库,平均 QPS 3000,日常 binlog 写入 1.5MB/s。
从库:新加坡,同版本同配置,纯只读 + 报表盘点。
链路:公网复制,mtr 常年 152~158ms RTT,0% 丢包,路径抖动小于 5ms。
现象很奇怪:Seconds_Behind_Master 平时老老实实贴着 0,只要主库那边跑一次归档批处理,就会一路飙到 240~300 秒,批处理结束后还得磨蹭十几分钟才回落到 0,然后下一个整点再来一遍。
这套从库跑在轻云互联的新加坡节点上,跟洛杉矶主库之间的公网 RTT 一天 24 小时画出来基本是一条直线。网络没问题,那就只能往自己身上找。
二、先切清楚是"拉"慢还是"放"慢
SHOW REPLICA STATUS\G
只看三个坐标,别的先不看:
Master_Log_File/Read_Master_Log_Pos→ I/O 线程拉到哪了Relay_Master_Log_File/Exec_Master_Log_Pos→ SQL 线程回放到哪了Relay_Log_Space→ relay log 里积压了多少
抓延迟最高那一刻的快照,结论非常干脆:Exec_Master_Log_Pos 几乎贴着 Read_Master_Log_Pos,两者只差几百字节;而 Read_Master_Log_Pos 距离主库当前的 binlog 位点差了整整 70MB。
也就是说 SQL 线程根本没压力,I/O 线程拉不动。relay log 里根本没积压,是无米下锅。
顺手排掉报错重连:
SELECT SERVICE_STATE, LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE,
SOURCE_LOG_FILE, READ_SOURCE_LOG_POS, COUNT_TRANSACTIONS_RETRIES
FROM performance_schema.replication_connection_status\G
没错误、没重试、没重连。I/O 线程一直在 ON,但就是慢。
三、往 socket 里看
复制连接是长连接,问题大概率在 TCP 层。在从库上对着主库端口抓:
ss -tinm dst <master_ip>:3306
ESTAB 0 0 10.10.2.31:49216 203.0.113.7:3306
skmem:(r40960,rb87380,t0,tb87040,f0,w0,o0,bl0,d0)
cubic wscale:7,7 rto:301 rtt:152.74/0.86 ato:40 mss:1448 pmtu:1500
rcvmss:1448 advmss:1448 cwnd:589 ssthresh:1023
retrans:0/0 rcv_space:1456
两个数字立刻扎眼:rb87380,接收缓冲区上限 85KB;cwnd:589,拥塞窗口已经涨到 589×1448≈852KB。
cwnd 想给的带宽是 852KB/0.1527s ≈ 5.5MB/s,但接收缓冲区只有 85KB,而且 tcp_adv_win_scale 默认是 1,意味着内核只会把其中一半用作通告窗口:
有效接收窗口 = 87380 × (1 - 1/2^1) = 42690 B 理论吞吐上限 = 42690 / 0.1527 ≈ 279 KB/s
实际跑的吞吐量也确实是 260~280KB/s 这个量级,跟 cwnd 一点关系都没有了——cwnd 再大也被通告窗口锁死。
查一下这个值是谁设的:
sysctl net.core.rmem_max net.ipv4.tcp_rmem net.ipv4.tcp_adv_win_scale net.ipv4.tcp_moderate_rcvbuf
net.core.rmem_max = 87380 net.ipv4.tcp_rmem = 4096 87380 87380 net.ipv4.tcp_adv_win_scale = 1 net.ipv4.tcp_moderate_rcvbuf = 1
破案了。net.core.rmem_max=87380 把自动调优的天花板直接焊死,tcp_rmem 第三位也是 87380。这套 sysctl 是从国内同城机房那套模板原封不动抄过来的——在那个 RTT 0.3ms 的环境里,85KB 窗口对应的吞吐是 280MB/s,绰绰有余;搬到了跨太平洋 152ms 的链路上,它直接把单连接吞吐压到了 279KB/s。
顺带算一下 BDP 有多离谱
BDP = 152.7ms × 100Mbps ÷ 8 = 1.9 MB (这才刚够 100Mbps) BDP = 152.7ms × 1Gbps ÷ 8 = 19.1 MB (想跑满 1Gbps 得 19MB 窗口)
我们给了 42KB。差了整整三个数量级。
过程中还顺手抓到一个次要问题
采样脚本跑着跑着发现,cwnd 在主库批量任务间歇期会莫名其妙掉回 10:
while true; do ss -tinm dst <master_ip>:3306 | grep -oP 'cwnd:\d+' | tail -1 sleep 2 done
net.ipv4.tcp_slow_start_after_idle 默认是 1,空闲超过一个 RTO(这里约 300ms)就把 cwnd 重置回 10,突发流量得重新慢启动。
但我要说清楚:这不是本次的主因。因为接收窗口卡在 42KB,慢启动两个 RTT 就撞到天花板了,重启慢启动的代价微乎其微。它只是个排错过程中的噪声,别被带偏。
四、算一下账,验证因果
- 批量归档在主库侧产生约 70MB 增量 binlog
- 从库以 279KB/s 拉:70 × 1024 ÷ 279 ≈ 257 秒
- 实测 SBM 峰值 248 秒,量级完全吻合
因果链闭合。这也解释了为什么"批处理结束十几分钟才恢复"——积压一直是靠网络慢慢啃掉的,跟 SQL 回放能力一点关系没有。
五、修复
1. 撕掉那份抄来的 sysctl
# /etc/sysctl.d/99-mysql-repl-net.conf # 按 1Gbps × 152ms 的 BDP 留出 2 倍余量 net.core.rmem_max = 33554432 net.core.wmem_max = 33554432 net.ipv4.tcp_rmem = 4096 1048576 33554432 net.ipv4.tcp_wmem = 4096 1048576 33554432 # 接收窗口不再打对折,rcvbuf 的 3/4 参与通告窗口 net.ipv4.tcp_adv_win_scale = 2 # 长肥管道上,空闲后别把 cwnd 拍回 10 net.ipv4.tcp_slow_start_after_idle = 0 net.ipv4.tcp_moderate_rcvbuf = 1
sysctl --system
改完必须把复制 I/O 线程重启一次,现有的 socket 不会自己吃到新参数:
STOP REPLICA IO_THREAD; START REPLICA IO_THREAD;
2. 顺手用压缩削掉一半字节
主库 8.0.20 以上,直接给 binlog 上 zstd 压缩,跨洋链路上省下的字节是真金白银:
SET PERSIST binlog_transaction_compression = ON; SET PERSIST binlog_transaction_compression_level_zstd = 3;
订单类文本日志压缩比普遍在 3~5 倍,等于把 70MB 的突发量直接砍到 20MB 上下。代价是主库多花一点 CPU,这笔账在跨洋场景下闭着眼都划算。
3. 复查
ss -tinm dst <master_ip>:3306
skmem:(r425984,rb6291456,t0,tb87040,...)
cubic wscale:7,7 rto:301 rtt:152.71/0.42 mss:1448
cwnd:2145 ssthresh:4096 retrans:0/0
rb 从 87380 涨到 6291456,接收窗口从 42KB 推到 4.7MB,理论吞吐上限干到 30MB/s 以上。同样的批量归档再跑一次,SBM 峰值 8 秒,5 秒内归零。
六、把这类坑做成监控
别等着下一次再翻车。复制 socket 的窗口和 cwnd 完全能采出来做曲线:
#!/bin/bash
# /usr/local/bin/repl_sock_sample.sh
MASTER_IP="$1"
TS=$(date +%s)
LINE=$(ss -tinm dst ${MASTER_IP}:3306 | tr -d '\n' | grep -oP 'rb\d+.*?cwnd:\d+')
echo "${TS} ${LINE}" >> /var/log/repl_sock.log
告警规则很简单:当 RTT > 80ms 且接收窗口计算出的吞吐上限低于主库当前 binlog 写入速率时,直接告警。这比盯着 Seconds_Behind_Master 事后救火强太多——延迟涨起来的时候,业务已经在读脏数据了。
七、最后说两句
国内机房那套 sysctl「一键优化」模板,在 RTT 0.3ms 的环境里跑十年都不会出事,因为那时候 85KB 窗口 ≈ 280MB/s,压根不是瓶颈。但美国服务器、新加坡节点这类跨洋部署,RTT 一旦上了 100ms,同一份配置就是一把钝刀子:它不报错、不丢包、不影响任何健康检查,只是把单连接吞吐悄悄削到 1/100。
跨洋机器上,任何从国内机房平移过来的内核参数,都值得按 BDP = RTT × 带宽 重新算一遍。这一步省不了。