一、为什么需要热备方案

想象一下这样的场景:半夜三点,数据库突然宕机,报警电话把你从睡梦中惊醒。这时候如果有一套完善的热备方案,你只需要翻个身继续睡,系统会自动切换到备用节点,业务完全不受影响。这就是热备的魅力——让故障转移变得像呼吸一样自然。

PostgreSQL作为企业级开源数据库,提供了多种高可用方案。我们今天要聊的,就是如何用流复制(Streaming Replication)实现秒级故障转移。这就像给数据库上了个"复活甲",主库挂掉的瞬间,备库立即顶上。

二、流复制的工作原理

PostgreSQL的流复制核心是WAL(Write-Ahead Logging)机制。主库把数据变更记录到WAL日志,备库实时接收并重放这些日志。整个过程就像主库在直播自己的操作,备库则是个忠实观众,边看边模仿。

来看个配置示例(技术栈:PostgreSQL 12):

-- 在主库创建复制账号
CREATE ROLE replicator WITH LOGIN REPLICATION PASSWORD 'securepassword';

-- 修改pg_hba.conf添加访问规则
# TYPE  DATABASE    USER        ADDRESS     METHOD
host    replication replicator  192.168.1.0/24  md5

-- 修改postgresql.conf关键参数
wal_level = replica             # 启用WAL日志
max_wal_senders = 5            # 最大并发复制连接数
wal_keep_segments = 32         # 保留的WAL段数量

备库的恢复配置(recovery.conf):

standby_mode = 'on'
primary_conninfo = 'host=192.168.1.100 port=5432 user=replicator password=securepassword'
restore_command = 'cp /var/lib/pgsql/wal_archive/%f %p'  # WAL归档恢复命令
trigger_file = '/tmp/promote_to_primary'  # 触发提升为主动的文件

三、实战搭建热备集群

让我们从零开始搭建一套1主2备的集群。假设三台服务器IP分别为192.168.1.100(主)、192.168.1.101(备1)、192.168.1.102(备2)。

首先在主库执行初始化:

# 在主库生成基础备份
pg_basebackup -h 192.168.1.100 -U replicator -D /var/lib/pgsql/12/data -Fp -Xs -P -R

# 在备库启动服务前配置恢复参数
echo "primary_conninfo = 'host=192.168.1.100 port=5432 user=replicator password=securepassword'" >> /var/lib/pgsql/12/data/recovery.conf

关键监控SQL(三台服务器都适用):

-- 查看复制状态
SELECT client_addr, state, sync_priority, sync_state 
FROM pg_stat_replication;

-- 检查WAL延迟
SELECT pg_current_wal_lsn() - replay_lsn AS replication_lag
FROM pg_stat_replication;

四、自动故障转移方案

单纯的主备复制还不够,我们需要自动故障检测和切换。这里引入Patroni作为集群管理工具(技术栈:PostgreSQL + Patroni + etcd)。

Patroni配置文件示例(/etc/patroni.yml):

scope: pg_cluster
namespace: /service/
name: node1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.1.100:8008

etcd:
  hosts: 192.168.1.100:2379,192.168.1.101:2379,192.168.1.102:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        wal_keep_segments: 32
        max_wal_senders: 5

故障转移测试脚本:

#!/bin/bash
# 模拟主库宕机
ssh postgres@192.168.1.100 "sudo systemctl stop postgresql-12"

# 观察Patroni日志
tail -f /var/log/patroni.log

# 验证新主库
curl http://192.168.1.101:8008/patroni | jq .role

五、应用层无缝连接

数据库切换了,应用怎么自动跟上?这就要靠连接池的智能路由了。以PgBouncer为例:

配置示例(/etc/pgbouncer/pgbouncer.ini):

[databases]
mydb = host=192.168.1.100,192.168.1.101,192.168.1.102 port=5432 dbname=mydb

[pgbouncer]
pool_mode = transaction
server_check_query = SELECT 1
server_fast_close = 1
server_round_robin = 1
ignore_startup_parameters = extra_float_digits

应用连接字符串只需指向PgBouncer:

# Python连接示例
import psycopg2
conn = psycopg2.connect(
    host='pgbouncer-host',
    port=6432,
    dbname='mydb',
    user='appuser',
    password='apppassword'
)

六、性能优化与监控

热备集群建好了,但要让其高效运行还需要调优:

  1. WAL传输压缩(PostgreSQL 13+):
ALTER SYSTEM SET wal_compression = on;
  1. 并行恢复加速:
ALTER SYSTEM SET max_standby_streaming_delay = 30s;
ALTER SYSTEM SET max_logical_replication_workers = 4;
  1. 关键监控指标:
# 使用Prometheus收集指标
pg_stat_replication_lag{instance=~"$instance"} > 100000000  # 延迟超过100MB告警
pg_up{instance=~"$instance"} == 0  # 实例宕机检测

七、常见问题解决方案

在实际运维中,你可能会遇到这些问题:

问题1:备库长时间不同步

-- 检查复制槽状态
SELECT slot_name, active, restart_lsn FROM pg_replication_slots;

-- 重建复制槽
SELECT pg_create_physical_replication_slot('standby1_slot');

问题2:主备切换后原主库无法自动加入

# 使用pg_rewind同步数据
pg_rewind --target-pgdata=/var/lib/pgsql/12/data \
          --source-server="host=192.168.1.101 port=5432 user=postgres"

八、技术方案对比

让我们看看几种常见方案的优劣:

  1. 流复制+手动切换

    • 优点:配置简单,资源消耗低
    • 缺点:需要人工干预,恢复时间不可控
  2. Patroni+etcd自动切换

    • 优点:全自动故障转移,30秒内完成切换
    • 缺点:需要额外维护etcd集群
  3. 商业方案(如Repmgr)

    • 优点:提供图形化监控界面
    • 缺点:商业许可费用较高

九、最佳实践建议

根据多年运维经验,我总结出这些黄金法则:

  1. 永远监控复制延迟,设置合理的阈值告警
  2. 定期测试故障转移,至少每季度一次完整演练
  3. 备库硬件配置不应低于主库,特别是IO性能
  4. 跨机房部署时,考虑使用WAL压缩减少带宽占用
  5. 重要系统建议采用"主-备-备"的三节点架构

十、未来发展方向

PostgreSQL的高可用方案还在不断进化:

  1. 物理复制+逻辑复制混合模式:既保证数据一致性,又能做部分表同步
  2. 多主复制:像MongoDB那样实现真正的多写
  3. 云原生集成:与Kubernetes Operator深度整合