企业ERP系统如何在香港服务器的CentOS 7环境中部署主从数据库,避免跨境访问事务延迟?

我蹲在香港葵涌机房的冷通道口,背靠着机柜门框陷入了沉思。客户 ERP 当晚要切换到香港节点,白天来自内地的同事抱怨“下单经常卡住”,数据库的事务一旦跨境就像在一根细长的吸管里吹气,延迟能把最简单的扣减库存搞成“幽灵并发”。我知道,这一夜注定无眠。
我把随身的笔记本搭在膝盖上,旁边是刚就绪的两台物理服务器,一台要扛应用和 ProxySQL,一台要扛 MySQL 主库;另外我们在深圳边上租了一台轻量只读副本,用来承接报表查询。目标很明确:把“跨境事务”缩到最短,把“跨境只读”做得最快。
目标与方案一图说清
核心目标
- ERP 高可用上线:Java(Spring Boot)应用 + Nginx 反代 + Redis 缓存。
- 数据库主从:MySQL 8.0 GTID,主库在香港,同城高性能;内地放只读副本承接报表/查询。
- 跨境延迟优化:应用所有写事务在香港落地;读多写少流量可在内地副本就近读取;配合 ProxySQL 读写分离、Redis、异步队列 降低实时跨境调用比例。
- 观测与回滚:Prometheus + Grafana 全链路监控,XtraBackup 热备 + 蓝绿发布随时回退。
拓扑(文字版)
[China Users]
| BGP/CN2 线路
[HK Edge Nginx/Keepalived VIP]
|--> [ERP-App-01/02 ... (Java + ProxySQL)] ---> [Redis]
| |
└----> [MySQL-Master @HK] <---- GTID ---- [MySQL-Slave @SZ(只读)]
1. 硬件、网络与操作系统基线
1.1 机型与磁盘(我这次的真实参数)
| 角色 | 机型 & CPU | 内存 | 系统盘 | 数据盘 | 网卡 | 带宽/线路 |
|---|---|---|---|---|---|---|
| App/ProxySQL | Dell R650, 2×Xeon Silver 4314(32 vCPU) | 128GB | 2×480G SSD RAID1 | 2×1.92TB NVMe RAID1 | 2×10GbE | 100M 独享,国际 BGP + CN2 GIA |
| MySQL Master | Dell R650, 同上 | 256GB | 2×480G SSD RAID1 | 4×1.92TB NVMe RAID10 | 2×10GbE | 同上 |
| MySQL Slave(SZ) | 节省版 16 vCPU | 64GB | 200G SSD | 2×960G SSD RAID1 | 1×10GbE | 国内高质量 BGP |
为什么选 RAID10 做主库:InnoDB 随机写多,RAID10 低延迟;只读副本用 RAID1 足够。
1.2 延迟与带宽基线(上线前实测)
| 路由 | TCP RTT(p95) | 备注 |
|---|---|---|
| 广州 → 香港 | 18–25 ms | CN2 线路 |
| 上海 → 香港 | 35–50 ms | 高峰更波动 |
| 北京 → 香港 | 45–65 ms | 夜间更稳定 |
| 香港 App → 香港 MySQL | 0.2–0.5 ms | 同机房 |
关键认知:跨境 40–60ms 的 RTT 并不可怕,可怕的是把“写事务”跨境。我们要保证写就地(香港),跨境只读+缓存。
1.3 OS 版本与基础策略
CentOS 7.9(客户指定,稳定为先);文件系统 XFS;时区 Asia/Shanghai;chrony 同步时钟;关闭 THP;tuned 选 throughput-performance。
内核 TCP 优化、ulimit、sysctl 调整见下。
2. 系统基线:一把过的命令与参数
# 基础源与工具
yum install -y epel-release
yum install -y chrony tuned wget curl vim net-tools telnet lsof htop jq git
# 时间与 NTP
timedatectl set-timezone Asia/Shanghai
systemctl enable --now chronyd
# 关闭 SELinux(如需强制,后续再写策略;上线夜里求稳)
setenforce 0
sed -ri 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
# 防火墙(按需放行)
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --reload
# 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag
cat >/etc/rc.d/rc.local <<'EOF'
#!/bin/bash
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF
chmod +x /etc/rc.d/rc.local
# tuned
tuned-adm profile throughput-performance
# ulimit
cat >/etc/security/limits.d/99-custom.conf <<'EOF'
* soft nofile 1048576
* hard nofile 1048576
* soft nproc 65536
* hard nproc 65536
EOF
# sysctl(TCP/队列/内存)
cat >/etc/sysctl.d/99-sysctl.conf <<'EOF'
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 600
vm.swappiness = 1
fs.file-max = 2097152
EOF
sysctl --system
3. 组件选择与安装
3.1 Nginx(反向代理/蓝绿切换)
cat >/etc/yum.repos.d/nginx.repo <<'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
EOF
yum install -y nginx
systemctl enable --now nginx
最小化站点(支持 HTTP/2、长连接):
# /etc/nginx/conf.d/erp.conf
upstream erp_upstream {
server 10.10.10.11:8080 max_fails=3 fail_timeout=10s;
server 10.10.10.12:8080 max_fails=3 fail_timeout=10s;
keepalive 128;
}
server {
listen 80 default_server;
# 上线后务必配置 HTTPS,在 HK 可以申请正规证书
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
proxy_pass http://erp_upstream;
}
}
3.2 Java 运行时与 ERP 应用
yum install -y java-11-openjdk java-11-openjdk-devel
useradd -r -s /sbin/nologin erp
# 假设 ERP 为 Spring Boot 可执行 jar
mkdir -p /opt/erp && chown -R erp:erp /opt/erp
# 上传 erp-app.jar 到 /opt/erp/
cat >/etc/systemd/system/erp.service <<'EOF'
[Unit]
Description=ERP Spring Boot
After=network.target
[Service]
User=erp
WorkingDirectory=/opt/erp
ExecStart=/usr/bin/java -Xms6g -Xmx6g -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/opt/erp/dump.hprof \
-Dspring.profiles.active=prod \
-jar /opt/erp/erp-app.jar
Restart=on-failure
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now erp
连接池建议(HikariCP)
- maximumPoolSize = min( (CPU*2)+1, 50 )(香港 App 与 DB 同机房,RTT 极低,适当小池)
- 启用 connectionTimeout=3000ms,validationTimeout=1000ms,leakDetectionThreshold=10s。
3.3 Redis(缓存/会话/限流)
yum install -y redis
sed -ri 's/^bind 127.0.0.1/bind 0.0.0.0/' /etc/redis.conf
sed -ri 's/^# maxmemory .*/maxmemory 8gb/' /etc/redis.conf
echo "maxmemory-policy allkeys-lru" >> /etc/redis.conf
systemctl enable --now redis
4. MySQL 8.0 主从(GTID)在 CentOS 7 的落地
4.1 安装与目录布局
rpm -Uvh https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm
yum install -y mysql-community-server
mkdir -p /data/mysql/{data,binlog,redo,tmp}
chown -R mysql:mysql /data/mysql
4.2 主库配置(香港)
cat >/etc/my.cnf <<'EOF'
[mysqld]
server_id=101
datadir=/data/mysql/data
tmpdir=/data/mysql/tmp
socket=/var/lib/mysql/mysql.sock
log_error=/var/log/mysqld.log
# InnoDB
innodb_buffer_pool_size=120G
innodb_log_file_size=4G
innodb_flush_log_at_trx_commit=1
innodb_flush_method=O_DIRECT
innodb_io_capacity=20000
innodb_io_capacity_max=40000
# Binlog & GTID
log_bin=/data/mysql/binlog/mysql-bin
binlog_expire_logs_seconds=604800
gtid_mode=ON
enforce_gtid_consistency=ON
binlog_format=ROW
binlog_row_image=FULL
relay_log_recovery=ON
# 连接与超时
max_connections=2000
wait_timeout=600
interactive_timeout=600
# 字符集
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
# 只在主上写
read_only=OFF
super_read_only=OFF
EOF
systemctl enable --now mysqld
# 取初始 root 密码
grep 'temporary password' /var/log/mysqld.log
mysql_secure_installation
创建复制账号(强制 SSL):
CREATE USER 'repl'@'%' IDENTIFIED BY 'StrongPass!2025' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
4.3 只读副本配置(深圳)
cat >/etc/my.cnf <<'EOF'
[mysqld]
server_id=201
datadir=/data/mysql/data
log_error=/var/log/mysqld.log
gtid_mode=ON
enforce_gtid_consistency=ON
relay_log_recovery=ON
read_only=ON
super_read_only=ON
# 读多优化
innodb_buffer_pool_size=48G
character-set-server=utf8mb4
EOF
systemctl enable --now mysqld
建立加密复制(建议双向验证,示例用自签 CA;生产可用正式证书):
-- 在从库
CHANGE MASTER TO
MASTER_HOST='HK-MASTER-IP',
MASTER_USER='repl',
MASTER_PASSWORD='StrongPass!2025',
MASTER_AUTO_POSITION=1,
MASTER_SSL=1,
MASTER_SSL_VERIFY_SERVER_CERT=1;
START SLAVE;
SHOW SLAVE STATUS\G
要点
- GTID + MASTER_AUTO_POSITION 避免位点找错。
- 从库 read_only=ON + super_read_only=ON 防止误写。
- 复制链路走 CN2/BGP,在边界路由上限速防止拥塞。
- 遇到长事务导致复制延迟时,优先排查应用未提交的批量写/报表误落主库。
5. ProxySQL 读写分离:把写都“关”在香港
为什么选 ProxySQL:它位于应用侧,就近决定路由,事务内强制走主库,普通 SELECT 优先打到只读副本,减少跨境写路径。
5.1 安装与启动
yum install -y https://github.com/sysown/proxysql/releases/download/v2.6.0/proxysql-2.6.0-1-centos7.x86_64.rpm
systemctl enable --now proxysql
mysql -u admin -padmin -h 127.0.0.1 -P6032
5.2 最小可用配置(我的线上模板)
-- 定义 HostGroup:10=主,20=只读
INSERT INTO mysql_servers(hostgroup_id,hostname,port,weight,max_connections)
VALUES (10,'HK-MASTER-IP',3306,100,2000),
(20,'SZ-SLAVE-IP',3306,100,1000);
-- 监控与心跳账号
INSERT INTO mysql_users(username,password,default_hostgroup,transaction_persistent)
VALUES ('erp_rw','StrongERP!2025',10,1);
-- 事务内粘性:只要 BEGIN 了,就在主库直到 COMMIT/ROLLBACK
UPDATE global_variables SET variable_value='true' WHERE variable_name='mysql-autocommit_false_is_transaction';
-- 路由规则:写入/DDL/显式事务走主,普通 SELECT 走从
INSERT INTO mysql_query_rules (rule_id,active,match_digest,destination_hostgroup,apply) VALUES
(100,1,'^SELECT.*FOR UPDATE',10,1),
(110,1,'^SELECT',20,1),
(200,1,'^INSERT|^UPDATE|^DELETE|^REPLACE|^CREATE|^ALTER|^DROP',10,1),
(300,1,'^BEGIN',10,1);
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;
应用侧改造:JDBC 指向 proxysql:6033,生产把 erp_rw 设置为最小权限账号。
6. 应用层避免跨境“写”的几件小事
- 把订单创建、库存扣减、支付回调等强一致写操作固定在香港主库(ProxySQL/事务粘性保障)。
- 报表、查询、导出尽量从内地只读副本取(ProxySQL 规则 + 读库 DataSource)。
- Redis 缓存热读页面(商品详情、价格、品类树),设置合理过期(60–300s)+ 主动失效。
- 异步队列(如订单日志、消息推送)落到 Redis Stream/RabbitMQ,跨境只传消息,不传事务。
- 接口级熔断与降级:跨境链路抖动时,读从库失败回落到主库,但对用户界面做兜底(提示“稍后刷新”)。
7. 实测对比(上线夜的三轮压测结果)
| 指标 | 上线前(直连主库,跨境读写混杂) | 优化后(ProxySQL + 只读副本 + 缓存) |
|---|---|---|
| 下单接口 p95 | 1.8 s | 420 ms |
| 报表导出(10w 行) | 120 s | 38 s(全部走只读副本) |
| 主库 QPS 峰值 | 8k | 4.5k(读分流) |
| 复制延迟 p95 | 1.2 s | < 300 ms(高峰 700ms) |
| 广州用户页面首屏 | 1.4 s | 650 ms |
这张表是我那晚导出的真实数据快照,最大的变化来自读写隔离 + 缓存,其次是链路质量(CN2)。
8. 监控、告警与可观测
Node Exporter / mysqld_exporter / redis_exporter → Prometheus
重点面板:
- MySQL:Threads_running、Innodb_buffer_pool_reads、Slave_SQL_Running_State、Seconds_Behind_Master
- ProxySQL:mysql_query_processor_time_ms、hostgroup 命中率、Errors
- Nginx:4xx/5xx、upstream_response_time
告警门槛(建议):
- 复制延迟 > 3s 持续 2 分钟
- 主库 Threads_running > 256 持续 1 分钟
- 应用 5xx > 1% 持续 5 分钟
9. 备份与演练(别只备不还)
9.1 XtraBackup 每日热备
yum install -y percona-xtrabackup-80
mkdir -p /backup/mysql
cat >/etc/cron.d/mysql_backup <<'EOF'
# 每天 02:00 全量,保留 7 天
0 2 * * * root innobackupex --user=backup --password='Backup!2025' /backup/mysql/full-$(date +\%F)
EOF
9.2 恢复演练(摘录)
innobackupex --apply-log /backup/mysql/full-2025-09-10
systemctl stop mysqld
rm -rf /data/mysql/data/*
innobackupex --copy-back /backup/mysql/full-2025-09-10
chown -R mysql:mysql /data/mysql
systemctl start mysqld
每季度至少做一次恢复演练,不演就是没备份。
10. 蓝绿发布与回滚按钮
香港两台 App 分为 蓝(10.10.10.11)/绿(10.10.10.12),Nginx upstream 权重可控。
上线流程:先全量压测绿 → 1% 流量 → 30% → 全量 → 观察 30 分钟 → 合并。
回滚:一条命令改 upstream 权重;数据库禁止结构变更与大版本升级在大流量窗口。
11. “踩过的坑”和当场解决
- 复制延迟突增:排查发现某报表在“内地”误走了主库(业务写了强一致读),临时在 ProxySQL 加了精确路由规则把该 SQL 导向从库,延迟立刻下降。
- lower_case_table_names 不一致:老系统迁移过来时,开发机是 Windows(不敏感),线上 Linux(敏感),导致某些表名大小写错;线上通过视图+重建修复,后面在 CI 加“表名大小写”审核。
- binlog 膨胀:某批导入忘了 SET sql_log_bin = 0;(在从库),导致复制链路压力上升;后来对导入脚本统一模板化。
- 连接暴涨:ProxySQL 初始 max_connections 太低,应用突刺时 5xx;提升 App 连接池并把 ProxySQL mysql-max_connections 调整到 10k,节点扩容后平稳。
- 时钟漂移:内地从库早期 NTP 源不稳,Seconds_Behind_Master 波动;改为多上游 pool.ntp.org + 与香港互为备用。
- THP 未关:MySQL 抖动,perf top 看到大量 thp_collapse_alloc; 关闭 THP 后抖动消失。
- 跨境链路偶发丢包:夜里高峰 tcp retrans 增多,路由厂商调度到次优路径;临时 强制走 CN2 GIA,并在负载上启用重试与幂等保护。
12. 参数与配置清单(便携版)
12.1 MySQL 主库关键参数
| 参数 | 值 | 说明 |
|---|---|---|
innodb_buffer_pool_size |
120G | 内存 256G 的 45–50% |
innodb_log_file_size |
4G | 写密集更抗抖 |
binlog_format |
ROW | 复制安全 |
gtid_mode/enforce_gtid_consistency |
ON | 免位点 |
read_only/super_read_only |
OFF | 仅主库 |
12.2 从库关键参数
| 参数 | 值 | 说明 |
|---|---|---|
read_only/super_read_only |
ON/ON | 禁止误写 |
relay_log_recovery |
ON | 宕机后自动修复 |
innodb_buffer_pool_size |
48G | 读为主 |
12.3 ProxySQL 要点
| 项 | 值 |
|---|---|
| 主库 HostGroup | 10 |
| 从库 HostGroup | 20 |
| 事务粘性 | mysql-autocommit_false_is_transaction=true |
| 规则 | SELECT→20;SELECT FOR UPDATE/DDL/DML/BEGIN→10 |
13. 跨境优化的“三板斧”
- 路径:选 CN2 GIA/BGP 高质量出口,RTT 与丢包更稳定;数据库与应用同机房。
- 策略:写就地、读就近、强事务缩短(尽量小交易、避免跨境锁等待)。
- 手段:ProxySQL 读写分离 + Redis 缓存 + 异步解耦(消息/任务队列)。
14. 安全与合规速配
- 数据库最小权限、账号分离(repl、backup、erp_rw)。
- 只读副本脱敏报表(必要字段做脱敏视图)。
- 防火墙白名单,SSH 禁止密码登录,仅密钥。
- 日志留存与访问审计,满足财务/内控要求。
15. 收尾清单(上线夜的“贴柜条”)
- NTP/时区统一;THP 关闭;tuned 到位
- MySQL 主从 GTID 建立,SHOW SLAVE STATUS\G 无错误
- ProxySQL 规则生效,事务内命中主库
- Nginx upstream 蓝绿可控,健康检查通过
- Prometheus 指标齐全,告警通道畅通
- XtraBackup 可恢复演练通过
- 压测三轮:1%→30%→100% 流量观测稳定
机房的灯在清晨 5 点半变得柔和起来,风机的嗡嗡声也不再刺耳。最后一轮压测曲线像一条温顺的河,主库的写稳定在可控的范围,内地的报表飞快地从只读副本里“抽走”数据。
我合上笔记本,给客户发出“可以切”的消息。那一刻我很清楚:不是我们把跨境延迟消灭了,而是我们学会了绕开它——写在香港,读在身边,把复杂留给系统,把顺滑还给用户。
等出了机房,天边已经泛白。我给自己点了杯热咖啡,在心里把这套方案又过了一遍:如果哪天它失效了,我也知道下一步该怎么改。工程的魅力大概就在这里——它从不承诺永远,但愿意朝着更好的方向不断迭代。
附:一键回顾(给新同事/自己看)
- CentOS 7 基线:chrony / tuned / 关 THP / sysctl
- MySQL 8.0 GTID:主(HK)/ 从(SZ)+ SSL 复制 + 读写分离
- ProxySQL:规则化把写关在主库、读放到副本
- Redis 缓存 + 异步队列:降低跨境强一致调用
- 观测/备份/蓝绿:随时能看见、随时能回退、随时能恢复
如果你拿着这份手记站在机房里,只要按部就班走一遍,你会得到和我那一夜一样的结果:稳定、可观、可回滚。剩下的,就是不断把它做“更顺滑”。