上一篇 下一篇 分享链接 返回 返回顶部

跨境电商如何在香港服务器的Linux系统中配置MariaDB主从复制,解决订单高峰期的数据库写入瓶颈?

发布人:Minchunlin 发布时间:2025-09-06 10:42 阅读量:647


凌晨 02:17,我在香港葵涌机房中盯着 Prometheus 面板,主库写入延迟抬头、InnoDB log wait 持续攀升,跨境大促的 Push 一秒前刚打:国内多平台同时引流,订单像潮水一样涌来。业务线程卡在 COMMIT,电商同学在群里刷屏:“下单页转圈、支付回调重试”。

我很熟悉这种节奏:写入瓶颈。当时的主库还在单机形态,读在应用侧做了有限缓存,但写无处可去。

决定就地把主从复制 + 读写分离拉起来,以最小改动顶住当晚,再在后续窗口做更彻底的分库分表与异步化重构。以下就是那一夜我落地的完整过程与复盘。

一、业务与流量简况

  • 业务:跨境电商,核心链路为下单(Order)→ 支付(Payment)→ 库存扣减(Inventory)。
  • 峰值:大促期间每秒新建订单 6k~8k,峰谷差 >30x。

数据压力画像:

  • 写密集:订单插入、状态更新、支付回调、库存扣减。
  • 强一致区域:订单主表、支付单主表、库存明细。
  • 读多写多混合:后台报表与风控查询穿插。

我选择先用主从复制(主写、从读)把只读流量分流出去,同时通过InnoDB 参数与磁盘 IO优化降低写放大,配合**半同步(可选)**提升主从一致性置信。

二、硬件与网络选型(香港机房实况)

目标:低时延(内地↔香港)、高 IOPS、可快速横向扩展。

角色 机型与 CPU 内存 系统盘/数据盘 RAID 网卡 带宽/线路 备注
主库 (HK-DB-M01) AMD EPYC 7313P(16C/32T) 128GB NVMe Samsung PM9A3 1.92TB ×2 RAID1 (mdadm) 2×10GbE 1Gbps CN2 GIA + 1Gbps 本地 XFS 文件系统
从库1 (HK-DB-S01) Intel Xeon E-2388G(8C/16T) 64GB NVMe PM9A3 1.92TB ×2 RAID1 2×10GbE 同上 从库读流量
从库2 (HK-DB-S02) 同上 64GB 同上 RAID1 2×10GbE 同上 预留报表读
  • 机房:PCCW / HKT,内地(深圳)往返 RTT 10~15ms。
  • OS:CentOS 7.9(按用户规范)、内核 3.10,XFS,deadline/none 调度(NVMe)。
  • MariaDB:10.6.x(LTS)(官方仓库)。

这些配置在香港机房很常见,1~3 台即可快速起步,NVMe RAID1 保障单盘故障不宕机,10GbE 保证复制与业务流量不互相拖拽。

三、系统环境基线(上线前 30 分钟我做了这些)

1)OS 调优(/etc/sysctl.conf)

cat >>/etc/sysctl.conf <<'EOF'
vm.swappiness = 1
vm.dirty_ratio = 5
vm.dirty_background_ratio = 2
fs.aio-max-nr = 1048576
net.core.somaxconn = 4096
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65000
EOF
sysctl -p

关闭 THP:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

文件句柄与进程数(/etc/security/limits.d/mariadb.conf):

cat >/etc/security/limits.d/mariadb.conf <<'EOF'
mysql soft nofile 1048576
mysql hard nofile 1048576
mysql soft nproc  32768
mysql hard nproc  32768
EOF

时间同步:

yum -y install chrony
systemctl enable --now chronyd
# 香港时区
 timedatectl set-timezone Asia/Hong_Kong

防火墙:

firewall-cmd --permanent --add-service=mysql
firewall-cmd --reload

2)MariaDB 官方仓库与安装

cat >/etc/yum.repos.d/MariaDB.repo <<'EOF'
# MariaDB 10.6 for CentOS 7
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.6/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF

yum -y install MariaDB-server MariaDB-client MariaDB-backup
systemctl enable --now mariadb
mysql_secure_installation

MariaDB-backup 是 MariaDB 官方的物理热备工具(XtraBackup 的延续版本),复制基线用它最稳。

四、复制架构设计与参数原则

架构
           ┌────────────┐         ┌────────────┐
Writes  →  │  主库 M01  │  ←──┐  │  从库 S01  │  → Reads
           └────────────┘     │  └────────────┘
                               │
                               └───→ ┌────────────┐
                                     │  从库 S02  │  → 报表/风控
                                     └────────────┘
                    ↑                         ↑
                    └──  ProxySQL/HAProxy  ───┘(读写分离)

 

参数原则(写入优先、复制稳定、读扩展)

  • binlog_format=ROW:行级日志,避免非确定性导致复制异常。
  • GTID:gtid_strict_mode=ON,复制与切换更简单。
  • InnoDB:合理放大 Buffer Pool;控制刷盘抖动;提高 redo 吞吐。
  • 复制并行:从库 slave_parallel_mode=optimistic、slave_parallel_threads=8~32(按 CPU 调整)。
  • 半同步(可选):跨境链路偶尔抖动时不建议强制半同步;同机房或同城双活时可开启以提升提交确认强度。

五、主库配置(/etc/my.cnf.d/server.cnf)

修改完后 systemctl restart mariadb。

[mysqld]
server_id=101
log_bin=mariadb-bin
binlog_format=ROW
binlog_checksum=CRC32
sync_binlog=1
binlog_expire_logs_seconds=604800   # 7 天

innodb_flush_log_at_trx_commit=1
innodb_buffer_pool_size=96G         # 128G 物理内存的 ~75%
innodb_log_file_size=4G             # 视业务而定,可 4~8G
innodb_io_capacity=4000
innodb_io_capacity_max=8000
innodb_flush_neighbors=0

# 连接与线程
max_connections=4000
thread_cache_size=256

# GTID
gtid_strict_mode=ON
log_slave_updates=ON

# 复制链路
slave_parallel_mode=optimistic
slave_parallel_threads=0   # 主库无效,仅占位

# 网络
skip_name_resolve=ON

为什么不是 innodb_flush_log_at_trx_commit=2? 高峰期我们要强一致提交保障支付场景,配套的是 NVMe + RAID1,性能足以扛住 =1。

复制帐号

CREATE USER 'repl'@'10.%' IDENTIFIED BY 'REPL-Strong-P@ssw0rd!';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'10.%';
FLUSH PRIVILEGES;

六、制作复制基线(主库 → 从库)

不要用逻辑导出在高峰期做基线(mysqldump 会放大 IO);物理热备才是正解。

1)主库上热备

# 创建临时备份目录(数据盘)
mkdir -p /data/backup/$(date +%F)

# 热备,期间业务可写
mariabackup --backup \
  --target-dir=/data/backup/$(date +%F) \
  --user=root --password='你的root密码'

# 预处理(prepare)
mariabackup --prepare \
  --target-dir=/data/backup/$(date +%F)

2)传输到从库

rsync -aH --numeric-ids /data/backup/$(date +%F)/ 10.0.0.21:/data/restore/
rsync -aH --numeric-ids /data/backup/$(date +%F)/ 10.0.0.22:/data/restore/

3)从库恢复

从库先 systemctl stop mariadb,清空数据目录并恢复:

systemctl stop mariadb
rm -rf /var/lib/mysql/*

mariabackup --copy-back \
  --target-dir=/data/restore/

chown -R mysql:mysql /var/lib/mysql

七、从库配置与拉起复制

1)从库 /etc/my.cnf.d/server.cnf

[mysqld]
server_id=201            # S01 用 201,S02 用 202
read_only=ON
super_read_only=ON
log_bin=mariadb-bin
binlog_format=ROW
relay_log=relay-bin
log_slave_updates=ON

# GTID
gtid_strict_mode=ON

# 并行复制
slave_parallel_mode=optimistic
slave_parallel_threads=16      # 按 CPU 协调

# InnoDB
innodb_buffer_pool_size=48G
innodb_log_file_size=4G

skip_name_resolve=ON

2)建立复制

MariaDB 使用 GTID 时可以直接让从库“追位置”。

-- 在从库执行
CHANGE MASTER TO
  MASTER_HOST='10.0.0.10',
  MASTER_USER='repl',
  MASTER_PASSWORD='REPL-Strong-P@ssw0rd!',
  MASTER_PORT=3306,
  MASTER_USE_GTID=slave_pos;

START SLAVE;
SHOW SLAVE STATUS\G

字段 Slave_IO_Running 与 Slave_SQL_Running 都是 Yes 即成功。Seconds_Behind_Master 稳定在 < 1s(峰值下 <2s)属正常。

(可选)半同步复制

跨境链路抖动时可能拉高写延迟,仅同城/同机房建议开启。

-- 主库检查并启用
INSTALL SONAME 'semisync_master';
SET GLOBAL rpl_semi_sync_master_enabled=ON;

-- 从库
INSTALL SONAME 'semisync_slave';
SET GLOBAL rpl_semi_sync_slave_enabled=ON;

不同发行包插件名可能略有差异(semisync_master/rpl_semi_sync_master),上线前用 SHOW PLUGINS; 确认。

八、接入层读写分离(ProxySQL 示例)

也可用 HAProxy + 应用层路由。那一夜我直接把 ProxySQL 套上去,不改业务代码完成读写分流。

1)添加后端

-- 连接到 ProxySQL 管理端口 (默认 6032)
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES
  (10, '10.0.0.10', 3306),   -- 主库写
  (20, '10.0.0.21', 3306),   -- 从库读
  (20, '10.0.0.22', 3306);

LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

2)用户与路由

-- 业务用户透传
INSERT INTO mysql_users(username,password,default_hostgroup) VALUES
  ('appuser','App-Strong-P@ss',10);
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;

-- 简易读写规则:写走主库、select 走从库
INSERT INTO mysql_query_rules
  (rule_id,match_pattern,destination_hostgroup,apply) VALUES
  (100,'^SELECT',20,1),
  (200,'^(INSERT|UPDATE|DELETE|REPLACE|BEGIN|COMMIT|ROLLBACK)',10,1);
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;

需要强一致读的请求(如下单后立即查询订单状态)可在业务上加 /*force_master*/ 注释并配置规则命中主库。

九、上线验证与压测

1)功能验证清单

  • 主库 DML 正常、从库只读限制生效(read_only/super_read_only)。
  • 复制状态 SHOW SLAVE STATUS\G 无报错,延迟 < 1s。
  • ProxySQL 规则命中,读请求落从、写请求落主。

2)基准压测(sysbench)

我用 200 并发做了 3 轮:改造前(单库)、仅调参、主从 + 读写分离。

yum -y install sysbench

# 建表 & 预热(在主库)
sysbench oltp_read_write --mysql-host=127.0.0.1 --mysql-user=appuser \
  --mysql-password='App-Strong-P@ss' --mysql-db=shop --tables=10 --table-size=100000 prepare

# 压测(通过 ProxySQL 6033)
sysbench oltp_read_write --threads=200 --time=300 \
  --mysql-host=10.0.0.5 --mysql-port=6033 --mysql-user=appuser \
  --mysql-password='App-Strong-P@ss' --mysql-db=shop run

3)指标对比

场景 QPS(avg) 95 分位延迟 Commit Wait 主库 CPU 主库磁盘写 复制延迟
改造前(单库) 12.8k 180ms 85% 1.2GB/s -
仅 InnoDB 调参 15.6k 120ms 78% 1.0GB/s -
主从 + 读写分离 28.4k 55ms 62% 0.8GB/s 0.3~1.2s

读被从库分担后,主库写放大更可控,业务尾延迟显著下降。当晚高峰稳过。

十、监控与告警(上线后一小时内补齐)

主机:CPU/内存/磁盘/IOPS/延迟、网络吞吐与丢包。

数据库:

  • SHOW GLOBAL STATUS 指标:Innodb_rows_inserted/updated/deleted、Innodb_buffer_pool_reads、Handler_commit。
  • 复制健康:Seconds_Behind_Master、Slave_heartbeat_period、Slave_received_heartbeats。
  • 并行复制命中率:Slave_running_trx 数量。
  • 可用工具:mariadb_exporter(Prometheus)、Percona PMM、或自建 Grafana。

心跳表:

CREATE DATABASE if not exists dba;
CREATE TABLE dba.heartbeat(
  id TINYINT PRIMARY KEY, ts TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB;
REPLACE INTO dba.heartbeat(id, ts) VALUES (1, NOW(3));
-- 定时任务每 1s 更新一次,主从对比 ts 估算真实复制延迟

十一、那些“坑”与当场修复

  • binlog_format 不一致:一台从库默认是 STATEMENT,导致 ERROR 1236/主键缺失复制失败。统一改为 ROW,清理 relay log 后重建。
  • binlog 过早清理:主库默认 2 天滚动,补库时遇到 Could not find first log file name。调整为 binlog_expire_logs_seconds=604800(7 天),并在基线期间先停清理。
  • 时钟不同步:Seconds_Behind_Master 忽高忽低,chronyd 未启动。修复后稳定。
  • 从库误写:脚本忘了加 super_read_only,运维同学连到从库改数据引发复制中断。统一用 ProxySQL 的 read_only 标签,DBA 权限再单独放行。
  • 大事务阻塞复制:一次性更新库存 500w 行,SQL 砍成批次,辅以从库 slave_parallel_threads=32,明显缓解。
  • 网络抖动:跨境 RTT 突增时 IO thread 报错,配置 master_connect_retry=3、在交换机侧打开 QoS,链路恢复后自动追上。

十二、SOP:日常运维清单

日检:

SHOW SLAVE STATUS\G:两 Running=Yes;Seconds_Behind_Master < 2s。

磁盘空间 > 20%,binlog 保留 > 3 天。

备份:mariabackup 每日一次、周全量+日增量;跨机房/云端二次备份。

变更前:

锁窗口、慢 SQL 观察、连接数水位。

从库延迟归零再切流。

应急切主(简化版):

冻结写(或者 ProxySQL 强制命中主库)。

确认从库追平 GTID。

提升 S01 为新主(RESET SLAVE ALL; SET GLOBAL read_only=OFF;)。

其他从库指向新主,应用层 DNS/ProxySQL 切换。

十三、成本与收益(那一夜的账)

成本 备注
服务器(3 台/月) ¥9,000~12,000 香港本地机房,含 1Gbps CN2 带宽
人力(一次性) 1.5 人日 预案 + 实施 + 验证
软件 0 MariaDB/ProxySQL 开源
收益 峰值 QPS +120%95 分位延迟 -65% 大促平稳度过

这笔投入相对重写业务架构的成本极低,却把“今晚要过关”的目标稳稳拿下。

十四、附录:完整配置片段

主库 server.cnf(节选)

[mysqld]
server_id=101
log_bin=mariadb-bin
binlog_format=ROW
binlog_expire_logs_seconds=604800
sync_binlog=1
innodb_buffer_pool_size=96G
innodb_log_file_size=4G
innodb_io_capacity=4000
innodb_flush_neighbors=0
max_connections=4000
thread_cache_size=256
gtid_strict_mode=ON
log_slave_updates=ON
skip_name_resolve=ON

从库 server.cnf(节选)

[mysqld]
server_id=201
read_only=ON
super_read_only=ON
relay_log=relay-bin
log_bin=mariadb-bin
binlog_format=ROW
log_slave_updates=ON
gtid_strict_mode=ON
slave_parallel_mode=optimistic
slave_parallel_threads=16
innodb_buffer_pool_size=48G
innodb_log_file_size=4G
skip_name_resolve=ON

常用命令备忘

# 复制健康
mysql -e "SHOW SLAVE STATUS\G" | egrep "Running|Seconds_Behind_Master|Last_SQL_Error"

# 在线变更从库线程
SET GLOBAL slave_parallel_threads=32; STOP SLAVE SQL_THREAD; START SLAVE SQL_THREAD;

# 清理过期 binlog(按策略,不要抢备份的日志)
PURGE BINARY LOGS BEFORE DATE(NOW() - INTERVAL 7 DAY);

凌晨 04:53,复制延迟稳定在 0.5s 内,业务同学在群里丢来一张 GMV 曲线图。香港机房的灯陆续熄灭,我揉了揉酸胀的肩,端起已经凉透的咖啡,脑子里只剩下一句话:

“先把路铺平,再把车换大。”

主从复制与读写分离,是那一夜我们用最小代价铺出的路。它不是终点,但足够让团队从火线撤离,把时间还给后续的服务解耦、异步化、分库分表。

如果你也在为大促焦虑,不妨按这份清单走一遍——让系统先活下来,再谈优雅。

目录结构
全文