# 基于双活互备的 DR 解决方案

> 介绍如何规划、部署和运维 GreptimeDB 企业版双活互备 DR 方案。

# 基于双活互备的 DR 解决方案

GreptimeDB 企业版可以将两个 Standalone 节点部署为对等节点。两个节点都能接受读写、保存完整的数据副本，并将数据变更异步复制到对端，不存在长期固定的主节点。

该拓扑适用于边缘和中小型部署：无需运维 GreptimeDB 分布式集群，也能获得节点级或站点级容灾能力。负载均衡器、客户端驱动或服务发现系统负责将流量发送到可用节点。

<AnchorAlias id="架构与读写路径" />
<AnchorAlias id="写入路径" />
<AnchorAlias id="查询路径" />

## 架构

![两个 GreptimeDB 节点双向复制数据，并由外部故障切换机制管理流量](/img/active-active-forwarding-zh.svg)

两个节点相互独立地提供服务：

- 写入在接收请求的节点提交，随后复制到对端；客户端无需等待对端确认。
- 查询只在接收请求的节点上执行，GreptimeDB 不会合并两个节点的查询结果。
- 两个节点之间不要求网络持续连通。对端或网络不可用时，待同步的数据变更会保留在本地，并在连接恢复后继续发送；前提是源节点的存储仍然可用且容量充足。
- 从对端复制而来的数据变更不会再次发回源节点，从而避免循环复制。

由于复制是异步的，对端可能暂时读到旧数据。需要 read-after-write 一致性的应用，应将相关读取保持在接受写入的节点，或等待复制追平后再切换节点。

## 能力与边界

| 能力 | 行为 |
| --- | --- |
| 节点与站点冗余 | 每个节点保存完整的数据副本，并能对外提供服务。 |
| 写入复制 | 数据变更在两个节点之间双向异步复制。 |
| 查询执行 | 查询在接收请求的节点上本地执行。 |
| 网络中断 | 健康节点继续服务；对端恢复后继续同步待处理的数据变更。 |
| 流量切换 | 由外部负载均衡器、客户端驱动或服务发现系统切换端点。 |
| 一致性 | 两个节点的数据最终一致。 |
| 横向扩展 | 该拓扑提供双节点冗余，不能替代分布式集群。 |

规划 DR 时需要考虑以下限制：

- 客户端写入成功不代表对端已经收到该写入。
- 如果待同步的数据变更到达对端前，源节点及其本地存储永久丢失，这部分数据可能无法从对端恢复。
- 对端不可用期间执行的 Schema 变更，可能需要在恢复后核验或手动补偿。
- 该拓扑不会选举流量主节点，也不会自动切换客户端连接。

如果业务需要同步复制、单站点本地存储完全丢失后仍保持严格的零 RPO，或者需要横向扩展，请改用基于 Remote WAL 和跨区域对象存储的分布式 DR 架构。

## 规划部署

启用双活互备前：

1. 将两个节点部署到不同的故障域；对于站点级容灾，应使用不同的可用区或区域。
2. 为每个节点准备足以保存完整数据副本的存储，并预留能够覆盖计划中最长对端故障时间的空间。
3. 选择外部流量切换机制，并明确健康检查、重试策略和连接排空行为。
4. 为两个节点分配互不重叠的 table ID 范围，避免节点独立建表时发生 ID 冲突。
5. 明确应用如何处理结果不确定的写入和重试；应尽可能使用幂等写入。

## 配置

在每个节点上将另一个节点配置为同步目标。以下示例展示节点 A 上的相关配置：

```toml
[[sync_nodes]]
name = "node-b"
enabled = true

[sync_nodes.metasrv]
metasrv_addrs = ["10.0.2.10:3002"]

[sync_nodes.region_server]
id = 2
addr = "10.0.2.10:4001"

[table_id_range]
start = 1024
end = 1000000
```

在节点 B 上配置节点 A 的端点，并使用不同的 `table_id_range`。每个 `sync_nodes.name` 必须唯一，并且只能包含 ASCII 字母、数字、`_` 或 `-`。

实际端点和 table ID 范围取决于部署环境。生产配置应联系 Greptime 进行审核。

## 故障与恢复行为

| 事件 | 预期行为 | 运维操作 |
| --- | --- | --- |
| 对端或站点间网络不可用 | 健康节点继续服务，待处理的数据变更等待对端恢复。 | 保持流量在健康节点，并监控复制状态和本地存储容量。 |
| 一个节点不可用 | 外部切换机制将流量转走之前，发送到该节点的请求会失败。 | 确认存活节点健康，然后切换或排空流量。 |
| 不可用节点恢复 | 待处理的数据变更自动同步；追平前两个节点的数据可能不同。 | 保持流量稳定，核验数据和 Schema 后再恢复常规路由。 |
| 源节点本地存储空间耗尽或不可用 | 为保证数据可恢复性，新的写入可能被拒绝。 | 恢复本地存储容量，并根据应用语义重试失败的写入。 |
| 节点及其存储永久丢失 | 尚未复制的数据变更可能不在存活节点上。 | 如果存在其他数据副本，从中恢复，并评估实际 RPO。 |

### 切换流量

节点或站点故障时：

1. 确认备用节点可访问，并能执行关键查询。
2. 停止向故障端点发送新流量。
3. 通过选定的故障切换机制将流量发送到健康节点。
4. 核验应用错误率、写入成功率和关键查询结果。
5. 在故障节点恢复并追平数据之前，保持路由稳定。

两个节点无法通信时，应避免反复切换写流量。虽然两个节点都能接受写入，但断连期间在两端分别写入会增加需要收敛的状态，并提高恢复核验的复杂度。

### 恢复节点

故障节点或网络恢复后：

1. 继续将应用写入发送到健康节点。
2. 恢复故障节点及节点间的同步连接。
3. 等待复制状态恢复正常，并处理完积压的数据变更。
4. 对比两个节点上的关键表 Schema 和近期数据。
5. 对恢复节点执行应用级读写探测。
6. 逐步恢复流量，并持续监控两个节点。

应单独核验故障期间发生的 Schema 变更。关键表的 Schema 一致前，不要将生产流量恢复到该节点。

## RPO 与 RTO

### RPO

RPO 取决于故障类型和等待复制的数据量：

- 如果只有对端或网络故障，并且源节点存储仍然完整，待处理的数据变更可以在恢复后继续发送。
- 如果复制完成前源节点及其存储丢失，存活节点可能不包含待处理的数据变更。

该拓扑不提供同步复制模式。应在具有代表性的负载下测量复制状态，并将观察到的延迟纳入 DR 计划。

### RTO

RTO 包括故障检测、端点切换、连接重试和应用恢复时间。GreptimeDB 不在该双节点拓扑内执行流量切换，因此外部流量层及其配置决定了大部分 RTO。

应定期演练计划内和计划外的故障切换。演练需要覆盖存量连接、连接池、正在执行的写入，以及核验备用节点所需的时间。

<AnchorAlias id="故障切换实现方式" />

## 选择流量切换机制

- **负载均衡器。** 配置主动健康检查，并从转发列表中移除故障端点。托管负载均衡器或独立的 HAProxy 实例可以将切换策略与应用解耦。

  

- **客户端驱动。** 一些 MySQL 和 PostgreSQL 驱动支持配置多个主机，并在连接失败时尝试其他端点。例如参见 [MySQL Connector/J failover](https://dev.mysql.com/doc/connector-j/en/connector-j-config-failover.html) 和 [PostgreSQL JDBC connection failover](https://jdbc.postgresql.org/documentation/use/#connection-fail-over)。连接故障切换并不保证重试非幂等写入是安全的。

  

- **服务发现或端点更新。** 健康检查系统可以更新 DNS、服务注册表或应用的端点集合。估算 RTO 时，需要计入健康检查间隔、DNS TTL 和连接池刷新时间。

### HAProxy 示例

以下最小配置通过 `127.0.0.1:14000` 上的本地 TCP 端点代理 GreptimeDB HTTP API。节点 A 健康时，HAProxy 将流量发送到节点 A；连续三次健康检查失败后切换到节点 B。节点 A 连续两次健康检查成功后，会重新成为可选节点。

虽然两个 GreptimeDB 节点都能接受写入，但该示例使用主备流量路由，使应用在正常情况下只向一个节点写入。请根据实际环境替换主机名、端口、监听地址和超时时间。

```text title="haproxy.cfg"
global
    log /dev/log local0
    log /dev/log local1 notice
    daemon

defaults
    mode tcp
    log global
    option tcplog
    timeout connect 5s
    timeout client 30s
    timeout server 30s

frontend greptimedb_http
    bind 127.0.0.1:14000
    default_backend greptimedb_http_nodes

backend greptimedb_http_nodes
    # Preferred node
    server node-a node-a:4000 check inter 2s fall 3 rise 2

    # Failover node
    server node-b node-b:4000 check inter 2s fall 3 rise 2 backup
```

该配置只代理端口 `4000` 上的 HTTP API。如需代理 MySQL 或 PostgreSQL 协议，请分别创建独立的 frontend 和 backend，并将目标端口设置为 `4002` 或 `4003`。

启动 HAProxy 前先检查配置：

```shell
haproxy -c -f haproxy.cfg
haproxy -f haproxy.cfg
```

## 运维检查清单

应监控两个节点的可用性、复制错误、复制延迟和存储容量，并通过端到端探针向一个节点写入数据，再从对端核验结果。

投入生产前，确认：

- 两个节点都能正常读写；
- 任一方向的数据变更都能到达对端；
- 在任一节点建表都不会发生 table ID 冲突；
- 流量能在目标 RTO 内完成切换；
- 长时间网络中断后，积压的数据变更能够追平；
- 恢复节点接收流量前，关键 Schema 和近期数据已经一致；
- 应用能够安全处理连接中断和结果不确定的写入。

:::tip 注意

请参考[解决方案比较](/user-guide/deployments-administration/disaster-recovery/overview.md#解决方案比较)，比较不同 DR 方案的 RPO、RTO 和基础设施要求。

:::
