
“今晚必须把香港仓的订单实时对上来,早上 8 点 CFO 要看报表。”
洛杉矶机房 02:10,我盯着 NOC 面板上从 LA 到 HK 稳定在 125–145ms 的 RTT,深吸了一口气,拉上外套拉链,开始了这次跨境“零容忍丢单”的数据库改造。
本文是我在洛杉矶机房给跨境电商站做 MariaDB Galera 实战部署与优化的完整手记:不仅有“怎么装”,更有“为什么这么装”。细节都是真实踩过的坑和当场解决的过程,包含硬件参数、配置样例、表结构设计、SST/IST、WAN 优化、监控与故障处理。目标是在 高延迟跨洋链路 上也尽可能接近“实时一致”的订单数据同步,并保证业务在下单高峰期间不掉单、不卡单。
一、现场架构与边界条件
1)业务与一致性目标
- 电商核心:下单(orders)、支付回调(payments)、国际仓出库(fulfillments)。
- 一致性要求:下单与支付强一致;库存与 BI可接受秒级延迟。
- 可用性:允许单节点或单地域故障,不允许写入停摆。
2)物理与网络拓扑
- 我最后选了 多主 Galera 集群 + 分段(Segment) 的拓扑,避免把所有写都扔到跨洋环路上。
- LA 段(Segment 0):2 台(主承载写流量 + 网站同城机房内网 10GbE)
- HK 段(Segment 1):1 台(就近服务亚洲仓、客服工具,承担读与容灾写)
- (可选)新加坡/法兰克福:按需再加读副本或下游异步复制做 BI 报表
Galera 在 WAN 上能跑,但要小心流控、认证冲突和 SST。Segment 能减少跨段流量;LA 段 2 台 + HK 段 1 台既保障仲裁,又把主要写留在 LA。
3)硬件与系统基线(真实参数)
| 角色 | 机房 | CPU | 内存 | 系统盘 | 数据盘 | 网卡 | OS |
|---|---|---|---|---|---|---|---|
| g1 | LA | Xeon Silver 4314(16c) | 64GB | SATA SSD 240G | 2× NVMe 1.92T (mdadm RAID1) | 2×10GbE LACP | CentOS 7(EOL,业务要求仍使用) |
| g2 | LA | Xeon Silver 4314(16c) | 64GB | 同上 | 同上 | 同上 | CentOS 7 |
| g3 | HK | EPYC 7302P(16c) | 64GB | 同上 | 2× NVMe 1.92T (RAID1) | 10GbE | CentOS 7 |
注:CentOS 7 已 EOL,但当时合规环境锁定了版本。若条件允许,强烈建议上 AlmaLinux/Rocky 8+,并用 MariaDB 10.11 LTS。
二、部署准备:把基础打牢
1)时钟与内核/文件句柄
# 时钟:跨洋集群 NTP 漂移会放大问题
yum install -y chrony && systemctl enable --now chronyd
# 文件句柄与队列
cat >> /etc/security/limits.conf <<'EOF'
mysql soft nofile 1048576
mysql hard nofile 1048576
EOF
cat >> /etc/sysctl.conf <<'EOF'
fs.file-max = 2000000
net.core.somaxconn = 65535
net.ipv4.tcp_tw_reuse = 1
vm.swappiness = 1
EOF
sysctl -p
坑 1:SST 挂死
第一次做 SST(mariabackup)时,Too many open files,直接卡在 donor 端。上面两步能救你一命。
2)防火墙与端口
Galera 用到:
- 3306(客户端)
- 4567/tcp,udp(组通信)
- 4568/tcp(IST)
- 4444/tcp(SST)
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --permanent --add-port=4567/tcp
firewall-cmd --permanent --add-port=4567/udp
firewall-cmd --permanent --add-port=4568/tcp
firewall-cmd --permanent --add-port=4444/tcp
firewall-cmd --reload
坑 2:跨段只开了 TCP
HK 侧 4567/udp 没放行,evs 心跳异常,流控疯狂触发。务必同时放行 UDP。
3)软件仓库(MariaDB 10.5 on el7)
cat > /etc/yum.repos.d/MariaDB.repo <<'EOF'
# MariaDB 10.5 for CentOS 7
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF
yum clean all && yum makecache
yum install -y MariaDB-server MariaDB-client galera-4 mariadb-backup socat pigz jq
三、Galera 集群配置(含 WAN Segment 与 TLS)
1)统一的基础配置 /etc/my.cnf.d/server.cnf
[server]
user = mysql
bind-address = 0.0.0.0
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default_storage_engine = InnoDB
skip_name_resolve = 1
# InnoDB
innodb_buffer_pool_size = 40G # 约 60-65% 内存
innodb_log_file_size = 2G
innodb_flush_log_at_trx_commit = 1 # 强一致写;如追求吞吐可评估 2
innodb_flush_method = O_DIRECT
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
# 连接
max_connections = 2000
table_open_cache = 8000
open_files_limit = 1000000
# Binlog(用于下游异步/审计)
server-id = 1 # 每节点不同
log_bin = /var/lib/mysql/mariadb-bin
binlog_format = ROW
sync_binlog = 1
expire_logs_days = 7
2)Galera 专属 /etc/my.cnf.d/galera.cnf
下面以 g1(LA)、g2(LA)、g3(HK)为例,记得替换 IP。
[galera]
wsrep_on = 1
wsrep_provider = /usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name = crossborder-orders
wsrep_cluster_address = gcomm://10.10.0.11,10.10.0.12,10.20.0.31
# 当前节点身份
wsrep_node_name = g1
wsrep_node_address = 10.10.0.11 # g2:10.10.0.12 g3:10.20.0.31
# SST/IST
wsrep_sst_method = mariabackup
wsrep_sst_auth = sstuser:sstpass
wsrep_sst_receive_address = 10.10.0.11
# 避免自增冲突(Galera 会自动管理,也建议显式设置)
wsrep_auto_increment_control = 1
# 流控与 WAN 参数(按延迟调优)
wsrep_slave_threads = 8
wsrep_provider_options = "
gcache.size=4G;
gcache.page_size=1G;
gmcast.segment=0; # g1/g2=0, g3=1
evs.keepalive_period=PT1S;
evs.inactive_timeout=PT10S;
evs.suspect_timeout=PT5S;
evs.send_window=512;
evs.user_send_window=512;
gcs.fc_limit=256;
gcs.fc_factor=0.8;
ist.recv_addr=10.10.0.11
"
# TLS(强烈建议开启)
wsrep_provider_options = "
socket.ssl_key=/etc/galera/ssl/server-key.pem;
socket.ssl_cert=/etc/galera/ssl/server-cert.pem;
socket.ssl_ca=/etc/galera/ssl/ca.pem
"
坑 3:wsrep_provider_options 被后写的配置覆盖
同一个键多次写会覆盖,把相关选项合并到同一段,或用多行但保持在同一个 = 之后的引号内。
3)生成 SST 用户与 TLS 证书
-- 初次启动后在任一节点执行
CREATE USER 'sstuser'@'%' IDENTIFIED BY 'sstpass';
GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON *.* TO 'sstuser'@'%';
FLUSH PRIVILEGES;
# TLS(自签,生产建议内网 CA)
mkdir -p /etc/galera/ssl && cd /etc/galera/ssl
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca.pem -subj "/CN=galera-ca"
openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem -out server.csr -subj "/CN=g1"
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 3650
chown -R mysql:mysql /etc/galera/ssl && chmod 600 /etc/galera/ssl/*
# 将 ca.pem、server-key/cert 分发到 g2/g3(分别用对应 CN 生成)
四、集群引导与节点加入(零停启动顺序)
1)仅在第一个节点(g1)引导新集群
# MariaDB 自带脚本(CentOS 7 包里有)
galera_new_cluster
# 或:mysqld --wsrep-new-cluster &
确认:
SHOW STATUS LIKE 'wsrep_cluster_size'; -- 1
SHOW STATUS LIKE 'wsrep_cluster_status'; -- Primary
2)第二节点(g2,LA 同城)
systemctl start mariadb
# 日志里应看到 IST 优先(比 SST 快),完成后 cluster_size=2
3)第三节点(g3,香港段)
systemctl start mariadb
# 跨洋大概率走 IST;若 gcache 不足或新装则走 SST(mariabackup)
坑 4:SST 被 SELinux 拦截
当晚我看到 donor 端 avc: denied,SST 卡住。
临时解法:setenforce 0;永久:/etc/selinux/config 改成 SELINUX=permissive 并配合 semanage fcontext 正确标注数据目录。更优雅是在预演期把 SELinux policy 补齐。
五、订单表的“Galera 友好”建模与 DDL
跨洋多主写入最怕两件事:主键冲突与认证失败(certification failed)。我的做法:
- 订单号用全局唯一 ID(雪花或 UUIDv4),避免自增冲突与热点 PK。
- 把“业务幂等”前置:关键唯一索引(如 unique(order_no)、unique(payment_txn_id))确保重放/并发回调不会重复入库。
- 减少跨段写同一行(避免热点行)。库存扣减走 LA 段,HK 只读或异步补偿。
示例(MariaDB 10.5):
CREATE TABLE orders (
id BINARY(16) NOT NULL, -- UUID_TO_BIN(UUID())
order_no VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
total_amount DECIMAL(18,2) NOT NULL,
currency CHAR(3) NOT NULL,
status TINYINT NOT NULL DEFAULT 0, -- 0:created,1:paid,2:fulfilled
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no),
KEY idx_user_ct (user_id, created_at)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
-- 回调表幂等键
CREATE TABLE payments (
id BINARY(16) NOT NULL,
order_id BINARY(16) NOT NULL,
provider VARCHAR(16) NOT NULL,
txn_id VARCHAR(64) NOT NULL,
amount DECIMAL(18,2) NOT NULL,
status TINYINT NOT NULL, -- 0:pending,1:success,2:failed
paid_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_provider_txn (provider, txn_id),
KEY idx_order (order_id),
CONSTRAINT fk_pay_order FOREIGN KEY (order_id) REFERENCES orders(id)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
小技巧:在应用层使用 UUID_TO_BIN(UUID(), 1)(time-ordered)减少主键随机写带来的 B+ 树分裂。
六、应用访问与写路由策略
写策略:LA 段优先(g1/g2),HK 节点仅在 LA 段不可用时短时承接写入(开关由 HAProxy/ProxySQL 控制)。
读策略:同城优先读、跨段只读低优先级业务(客服、BI 快照)。
HAProxy(示例):
backend mariadb_writers
balance roundrobin
server g1 10.10.0.11:3306 check
server g2 10.10.0.12:3306 check
# g3 仅在 LA 坏掉时接管
server g3 10.20.0.31:3306 check backup
backend mariadb_readers
balance leastconn
server g1 10.10.0.11:3306 check
server g2 10.10.0.12:3306 check
server g3 10.20.0.31:3306 check
应用侧重试:捕获 ER_LOCK_DEADLOCK (1213) / 事务冲突错误,指数退避重试(最多 3 次),幂等写入配合唯一键即可。
七、跨洋延迟下的 Galera 调优
| 参数 | 我线上值 | 作用 & 注释 |
|---|---|---|
gcache.size |
4G | 足够覆盖高峰 20–40 分钟的写增量,优先保证 IST,降低 SST 频率 |
evs.keepalive_period |
1s | 心跳频率 |
evs.inactive_timeout |
10s | 节点失联判定,跨洋别设太激进 |
gcs.fc_limit |
256 | 流控阈值(队列过长触发 FLOW CONTROL) |
wsrep_slave_threads |
8 | 并行 Apply 线程;根据 CPU/IO 调整 |
innodb_flush_log_at_trx_commit |
1 | 严格持久化;吞吐不够时可评估 2(权衡一致性) |
innodb_log_file_size |
2G | 减少 checkpoint 频率,稳态更平滑 |
观测与阈值:
SHOW STATUS LIKE 'wsrep_flow_control_paused%'; -- < 0.1 理想
SHOW STATUS LIKE 'wsrep_cert_deps_distance'; -- 越高并行度越好,> 5 稳定
SHOW GLOBAL STATUS LIKE 'wsrep_local_recv_queue_avg'; -- < 4
SHOW STATUS LIKE 'wsrep_evs_repl_latency'; -- P50/P99 关注
当晚高峰时,我看到 wsrep_flow_control_paused 曾冲到 0.22,LA -> HK 认证堆积。临时措施:提高 gcs.fc_limit 到 384,同时把支付写流量明确只打 LA 段,指标马上回落到 0.04。
八、SST/IST、备份与恢复(演练过才是真安全)
1)优先 IST,减少 SST
- 放大 gcache 并保证磁盘快速(NVMe)。
- 节点短暂重启或网络抖动时优先走 IST,不打断大盘。
2)SST(mariabackup)过程观察
- donor 侧 IOPS 飙升,避开业务高峰。
- 压力大时把 nice/ionice 拉高,别拖垮线上延迟。
3)热备策略
每日全量 + 每小时增量:
# 全量
mariabackup --backup --target-dir=/backup/full-$(date +%F)
# 增量
mariabackup --backup --target-dir=/backup/inc-$(date +%F-%H) \
--incremental-basedir=/backup/full-$(date +%F)
# 校验/打包
mariabackup --prepare --target-dir=...
恢复(演练摘要):
systemctl stop mariadb
rm -rf /var/lib/mysql/*
mariabackup --prepare --target-dir=/backup/full-2025-09-10
mariabackup --copy-back --target-dir=/backup/full-2025-09-10
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb
九、监控面板与告警(我在线上盯的就是这些)
关键指标(Prometheus/Metrics + Grafana):
- wsrep:wsrep_cluster_size、wsrep_ready、wsrep_flow_control_paused、wsrep_cert_deps_distance、wsrep_local_state_comment、wsrep_evs_repl_latency
- InnoDB:Buffer Pool Hit%、Redo Write/s、Checkpoints、Row Lock Time
- SST/IST:SST 事件计数、耗时
- 网络:LA↔HK RTT(smokeping)、丢包率
告警规则(示例):
- wsrep_flow_control_paused > 0.2 持续 5m
- wsrep_cluster_size < 3 持续 1m
- wsrep_evs_repl_latency_p99 > 300ms 持续 10m
十、零停机维护与升级流程(实操顺序)
- 逐台 SET GLOBAL wsrep_on=OFF;(或从负载摘除),清空连接。
- 节点停止后升级小版本(同大版本),启动观察无误再进下一台。
- 如需跨大版本,先排定只读窗口或拉一台新版本节点进行 SST/数据迁移,完成后逐台替换。
坑 5:混装 galera-3 与 galera-4
我曾在 g3 上装错了版,启动直接 wsrep provider not found。跨版本不兼容,保持 Galera provider 同版。
十一、真实坑位与当场修复
回源写放大:应用层误把缓存失效导致的读热,变相触发跨段一致性验证压力。
修复:支付和下单路径的读全部打 LA 段本地,HK 只读客服查询;缓存键 TTL 调整 + 本地化。
回调幂等缺失:第三方支付重放导致 payments 重复写。
修复:UNIQUE (provider, txn_id) + 应用重试只做 INSERT ... ON DUPLICATE KEY UPDATE。
SST 卡顿业务:donor 端 I/O 冲击明显。
修复:SST 仅在低谷时段执行;ionice -c2 -n7、nice -n 10;并增大 gcache、优先 IST。
HK 机房偶发丢包:evs 报 suspect。
修复:网络组排查后改了跨境专线 QoS;我这边把 evs.inactive_timeout 拉到 10s,减少误判。
十二、验证与验收:我们如何证明“实时”
| 指标 | 改造前(单主+半同步) | 改造后(Galera 多主, LA 写为主) |
|---|---|---|
| 高峰下单 TPS | ~2.8k | 3.5k |
| LA→HK 订单可见中位延迟 | 450–800ms | 180–300ms |
wsrep_flow_control_paused |
0.15–0.28 | 0.03–0.08 |
| 峰值 SST 次数/周 | 3 | 0–1(主要 IST) |
这个延迟不是“物理实时”,但对运营与客服已经达到“刷新即见”。CFO 早会的跨区报表也稳定了。
十三、运维口袋卡(复制就能用)
启动/停止
# 初始化集群
galera_new_cluster
# 常规节点
systemctl start|stop mariadb
状态检查
SHOW STATUS LIKE 'wsrep_cluster_size';
SHOW STATUS LIKE 'wsrep_cluster_status';
SHOW STATUS LIKE 'wsrep_local_state_comment';
SHOW STATUS LIKE 'wsrep_flow_control_paused';
临时只读(摘流)
SET GLOBAL wsrep_on=OFF;
FLUSH TABLES WITH READ LOCK; -- 如需快照/备份
安全重建节点(走 SST)
systemctl stop mariadb
rm -rf /var/lib/mysql/*
systemctl start mariadb
# donor 自动选择,注意业务低谷进行
十四、给后来者的建议(精华版)
- 写就近:把强一致写集中在延迟最低的段,跨洋节点尽量做只读或低频写。
- gcache 要大:能兜住你“最长一次链路抖动时段”的写入量。
- 幂等是王道:唯一键 + 应用层重试,化解认证冲突与重放。
- 监控先行:wsrep_flow_control_paused、evs_repl_latency 是两个最能救命的指标。
- SST 提前演练:不演不行,真遇到才不会慌。
- 分段(Segment)必配:跨海链路下显著降噪。
当 LA 的天边泛起一丝亮,wsrep_cluster_size=3、flow_control_paused=0.04,HK 的客服页刷新就是刚下的订单。CFO 8 点进群一个 “👍”。
我关掉了机房里嗡嗡作响的风扇声,掏出口袋里的速溶咖啡,笑了笑:这一次,我们把“跨境实时”落到了地上。