技术方案 · 2026-09
面向中小项目的务实方案。任意一台机器宕机,业务不中断、数据不丢失,同时避开「数据库双写」带来的冲突处理成本。
两台服务器、一个域名。我们不追求「两台数据库同时可写」的真双活,因为那需要专门团队处理写冲突。取而代之的是一条更稳的路线:应用层两台同时工作,数据库层同一时刻只有一台可写,另一台实时复制并随时准备接管。
| 层 | 选用组件 | 作用 | 备注 |
|---|---|---|---|
| 入口 | Cloudflare Load Balancer | 健康检查两台机器,宕机的自动摘除 | 有免费额度。同机房也可用 Keepalived 漂移虚拟 IP |
| 应用 | 业务服务 × 2 | 两台同时接流量 | 前提是无状态化,见下文 |
| 会话 / 缓存 | Redis | Session、分布式锁 | 或改用 JWT,不存 Session |
| 文件 | 对象存储(R2 / S3 / MinIO) | 上传文件两台都能读到 | 不能存本地磁盘 |
| 数据库 | PostgreSQL 主从流式复制 | 从库实时同步,主挂了可接管 | 主 可写 从 只读 |
| 自动切换 | Patroni | 探测主挂 → 提升从库 → 旧主回来自动降为从 | 备选 pg_auto_failover |
| 连接路由 | HAProxy 或 PgBouncer | 应用只连本机 localhost:5000,不关心谁是主 | 跟随 Patroni 的健康接口 |
| 见证 | etcd 第三节点 | 提供第三票,防止脑裂 | 必须 两票无法多数决 |
| 定时任务 | Redis 锁 / PG advisory lock | 保证两台只有一台在跑 | 否则每个任务执行两次 |
两台机器之间网络一断,各自都会认为对方已死,各自都想当主。如果两台都接受写入,数据就分成两个版本,事后几乎无法合并。这是整个方案里最严重的故障模式,术语叫「脑裂」。
解决办法很便宜:租一台最低配 VPS 跑 etcd,或者用机房里的第三台旧机器。它只负责投票,不存任何业务数据。如果暂时没有第三票,就保留主从复制,但切换改为「告警 + 人工执行」。
财务、订单类数据建议用同步,并打开 Patroni 的 synchronous_mode: true。它的好处是从库挂了会自动退化为异步,不会把主库卡死。
这正是「不用处理数据冲突」的原因:任一时刻只有一个主库接受写,从库只读。代价是切换期间有一段写不可用的窗口,通常 10 到 30 秒。应用层要做到写失败自动重试,用户看到的只是一次略慢的请求。
Patroni、etcd、HAProxy 在 Windows 上都不顺手。建议两台机器装 Docker 或 WSL2 来跑这一套,或者把数据库层放到 Linux 小机上。
如果必须纯 Windows:保留 PostgreSQL 主从复制,用 pg_ctl promote 人工提升,配合监控告警,做到「几分钟内人工恢复」。对中小项目这通常已经够用。
这一步不做,后面全部白搭。「无状态」意思是任何一次请求落到 A 或 B 都得到相同结果,机器本地不保留用户相关的东西。
| 本地状态 | 问题 | 改法 |
|---|---|---|
| 内存 Session | 用户在 A 登录,下一次请求落到 B 就掉线 | Session 进 Redis,或改 JWT |
| 本地上传目录 | A 上传的文件 B 读不到 | 统一放对象存储 |
| 进程内定时任务 | 两台各跑一遍,重复推送、重复扣款 | 执行前先抢分布式锁 |
| 本地 SQLite | 根本无法两机共享 | 迁移到 PostgreSQL |
| 本地日志 | 排查问题要翻两台 | 各写各的即可,建议汇总到一处 |
按顺序做,每一步都能独立验证,任何一步停下来都比现状更好。
pg_ctl promote 手动切一次,记录耗时。双主要处理同一行被两边同时改写的冲突,方案(BDR、Galera 等)都有各自的限制和坑,需要有人长期盯。对中小项目,主备切换带来的十几秒写窗口远比冲突处理便宜。
可以,报表、列表这类只读查询可以走从库,减轻主库压力。但要接受几毫秒到几百毫秒的复制延迟,刚写完马上读的场景仍然走主库。
要。切换后任意一台都可能成为主库,配置低的那台会成为瓶颈。
Redis 本身也是单点。用户量不大时接受「Redis 挂了所有人重新登录」;需要更稳则用 Redis Sentinel 三节点,见证机可以顺便跑一个 Sentinel。