数据库层跨区复制:MySQL、PostgreSQL 与 MongoDB 的异地容灾实践

数据库层的跨区复制,是整个异地容灾体系里最核心、也最棘手的一环。说白了,你的应用可以无状态地随便漂,但数据不行。数据必须老老实实地待在某个地方,还得在另一个地方有一模一样的副本。

我这些年经手过的容灾项目,数据库层出问题的概率最高。不是方案选型不对,就是细节没抠到位。今天咱们就把 MySQL、PostgreSQL 和 MongoDB 这三兄弟的跨区复制方案掰开揉碎讲清楚。

MySQL 主从复制跨区域部署

MySQL 的主从复制,大家都不陌生。但跨区域部署时,很多坑就冒出来了。

核心原理

主库把变更写入 binlog,从库的 I/O 线程拉过来写到 relay log,然后 SQL 线程回放。就这么简单。但跨区域时,网络延迟会让这个流程变得很脆弱。

关键参数调整

  • slave_net_timeout:默认 60 秒,跨区域建议调到 10-15 秒,快速感知网络中断
  • sync_binlog=1:每次事务提交都刷盘,保证主库崩溃不丢数据
  • innodb_flush_log_at_trx_commit=1:配合上面的参数,做到真正的 ACID

我曾经遇到过一个案例,主库在北京,从库在深圳。网络偶尔抖动一下,从库就落后了十几分钟。查了半天,发现是 slave_net_timeout 设成了默认的 60 秒,网络闪断后要等一分钟才能重连。改成 10 秒后,问题就解决了。

半同步复制

异步复制有个致命问题:主库挂了,最后几个事务可能没传到从库。半同步复制就是来解决这个的。

-- 主库安装插件
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 5000;  -- 5秒超时

-- 从库安装插件
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;

注意那个超时参数。跨区域时,网络延迟可能超过 100ms,如果超时设得太短,半同步会退化成异步。我建议设成 5-10 秒,既保证数据安全,又不至于因为网络波动频繁降级。

避坑指南

我曾经在跨区域部署时,把半同步超时设成了 1 秒。结果网络稍微一抖,主库就频繁退化成异步模式,等网络恢复后又切回半同步。这种来回切换本身就有风险,而且监控告警响个不停。后来改成 5 秒,世界清净了。

MySQL Group Replication 跨区域部署

Group Replication 是 MySQL 官方的高可用方案,支持多主写入。但跨区域部署时,它的网络要求比主从复制苛刻得多。

组通信的痛点

Group Replication 底层用了 Paxos 协议的变种,需要大多数节点确认才能提交事务。跨区域时,延迟会直接影响写入性能。

节点分布 写入延迟 容灾能力 推荐场景
同城三机房 < 5ms 单机房故障 同城容灾
两地三中心 20-50ms 单区域故障 异地容灾
三地五中心 50-100ms 多区域故障 金融级容灾

我个人的习惯是,跨区域部署 Group Replication 时,尽量把多数节点放在同一个区域。比如三个节点,两个放在主区域,一个放在备区域。这样大多数事务在主区域内部就能达成共识,跨区域流量只发生在少数场景下。

配置要点

-- 所有节点都要设置
SET GLOBAL group_replication_group_name = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
SET GLOBAL group_replication_local_address = '公网IP:33061';
SET GLOBAL group_replication_group_seeds = '节点1IP:33061,节点2IP:33061,节点3IP:33061';

-- 跨区域必须调整
SET GLOBAL group_replication_communication_max_message_size = 10485760;  -- 10MB
SET GLOBAL group_replication_poll_spin_loops = 10000;  -- 减少CPU空转

经验之谈

跨区域部署 Group Replication,一定要把 group_replication_communication_max_message_size 调大。默认是 1MB,跨区域传输大事务时很容易超时。我一般设成 10MB,够用又不浪费内存。

PostgreSQL 流复制与逻辑复制

PostgreSQL 的复制机制比 MySQL 更灵活。流复制适合同城,逻辑复制适合跨区域异构环境。

流复制跨区域部署

流复制是物理级别的,传输的是 WAL 日志。跨区域时,带宽和延迟是主要瓶颈。

-- 主库配置 postgresql.conf
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1024  -- 保留 1GB 的 WAL

-- 从库配置 recovery.conf
primary_conninfo = 'host=主库IP port=5432 user=repl password=xxx'
recovery_target_timeline = 'latest'

跨区域时,我建议开启 synchronous_commit 的 remote_write 模式。它不要求从库刷盘,只要求数据到达操作系统缓存。这样既保证了数据不丢,又不会因为从库刷盘慢而拖慢主库。

流复制 vs 逻辑复制

特性 流复制 逻辑复制
复制粒度 整个实例 表级别
版本要求 主从版本一致 可跨大版本
DDL 支持 自动同步 需手动处理
跨区域延迟 低 中等

逻辑复制实战

逻辑复制最大的好处是灵活。你可以只复制某几张表,甚至可以在复制过程中做数据转换。

-- 发布端
CREATE PUBLICATION my_pub FOR TABLE users, orders;

-- 订阅端
CREATE SUBSCRIPTION my_sub 
CONNECTION 'host=主库IP port=5432 dbname=mydb user=repl password=xxx'
PUBLICATION my_pub;

我记得有个项目,需要把生产库的部分表实时同步到分析库。生产库是 PostgreSQL 13,分析库是 PostgreSQL 16。用流复制不行,版本不一致。逻辑复制完美解决了这个问题。

注意

逻辑复制不会自动同步 DDL。你在主库加了新列,从库不会自动加。我建议用事件触发器来捕获 DDL,然后通过外部工具同步到从库。或者干脆约定好,DDL 变更走工单流程,两边手动执行。

MongoDB 副本集跨区域配置

MongoDB 的副本集天生支持跨区域部署。它的选举机制和读写偏好设置,让跨区域容灾变得相对简单。

节点分布策略

MongoDB 副本集最多支持 7 个投票节点。跨区域时,我建议这样分布:

  • 主区域:2 个数据节点 + 1 个仲裁节点
  • 备区域:2 个数据节点
  • 第三区域:1 个数据节点(只用于读)

这样配置的好处是,主区域挂了,备区域还有 2 个节点,加上第三区域的 1 个节点,可以组成新的多数派。第三区域的节点设置 priority: 0,永远不会成为主节点,只用于读。

配置示例

// 副本集配置
{
  _id: "myReplSet",
  members: [
    { _id: 0, host: "主区域节点1:27017", priority: 2 },
    { _id: 1, host: "主区域节点2:27017", priority: 2 },
    { _id: 2, host: "主区域仲裁:27017", arbiterOnly: true },
    { _id: 3, host: "备区域节点1:27017", priority: 1 },
    { _id: 4, host: "备区域节点2:27017", priority: 1 },
    { _id: 5, host: "第三区域节点:27017", priority: 0, hidden: true }
  ],
  settings: {
    electionTimeoutMillis: 10000,  // 跨区域适当调大
    heartbeatIntervalMillis: 2000
  }
}

你想想看,跨区域网络延迟高,如果选举超时设得太短,网络抖动就会引发不必要的选举。我一般设成 10 秒,比默认的 5 秒长一倍,稳定性好很多。

读写偏好设置

跨区域部署时,读写偏好直接影响用户体验和一致性。

读偏好 延迟 一致性 适用场景
primary 高(跨区域) 强一致 写后立即读
primaryPreferred 中等 最终一致 大多数场景
nearest 低 最终一致 非关键查询

我个人习惯是,写操作走 primary,读操作走 nearest。这样写入保证强一致,读取用最近的节点,延迟最低。如果业务要求写后立即读到,那就用 writeConcern: majority 配合 readPreference: primary。

小技巧

MongoDB 4.4 之后支持了 readPreferenceTags,可以按区域标签路由。比如给主区域的节点打上 region: beijing,备区域打上 region: shanghai。应用层可以根据用户所在地,选择最近的节点读取数据。

跨区域复制架构总览

下面这张图展示了三种数据库跨区域复制的核心架构。你可以看到,虽然实现方式不同,但核心思路是一致的:数据在多个区域之间保持同步,任何一个区域故障都不会导致数据丢失。

数据库层跨区域复制架构总览 区域 A(主) MySQL 主库 PG 主库 MongoDB Primary MongoDB Secondary MongoDB Arbiter 区域 B(备) MySQL 从库 PG 从库 MongoDB Secondary MongoDB Secondary 区域 C(第三) MySQL 从库(只读) PG 从库(只读) MongoDB Hidden binlog WAL oplog 核心原则:主区域写入,备区域同步,第三区域只读。任何区域故障,数据不丢。

嗯,到这里,数据库层的跨区复制方案就讲完了。MySQL 的主从复制和 Group Replication、PostgreSQL 的流复制和逻辑复制、MongoDB 的副本集,各有各的适用场景。选型的时候,要考虑业务对一致性、延迟、可用性的要求,没有银弹。