在PostgreSQL中配置同步复制以实现高可用性

学习使用同步流复制配置零数据丢失(RPO=0)的PostgreSQL高可用性。本分步教程涵盖了`wal_level`、复制槽、`pg_basebackup`等关键配置,以及在主服务器和备用服务器上正确设置`synchronous_commit`参数,以确保关键环境中事务的持久性。

在PostgreSQL中配置同步复制以实现高可用性

配置PostgreSQL以实现高可用性(HA)通常从一个棘手的问题开始:如果主服务器在提交后立即宕机,你能承受丢失多少数据?对于普通的异步流复制,答案是“可能一些”。主服务器可以在备用服务器接收或重放WAL记录之前,就告诉应用程序事务已提交。如果主服务器在那个小的时间窗口内发生故障,被提升的备用服务器可能不包含最后几个已提交的事务。

同步流复制改变了这种权衡。PostgreSQL在报告提交成功之前,会等待一个或多个命名的备用服务器。根据synchronous_commit的级别,备用服务器可能只需要将WAL写入操作系统,将其刷新到持久存储,或者重放它以便备用服务器上的查询可以看到它。这可以为已提交的事务提供零RPO,但也意味着写入路径现在依赖于网络和备用服务器的健康状况。

这种权衡很重要。同步复制适用于那些丢失一个已确认的事务都是不可接受的小数据集:支付、账户余额、库存预留、订单状态、审计跟踪。它通常不适用于高容量事件日志、点击流数据、指标,或者那些可用性和延迟比跨节点的完美持久性更重要的工作负载。在全局启用它之前,请确定你的工作负载中哪些部分真正需要它。

前提条件

开始之前,请确保你已设置好两台运行相同主要版本PostgreSQL的服务器(主服务器和备用服务器)。两台服务器必须具有网络连接。本指南假设:

  • 主服务器主机名/IPpg_primary
  • 备用服务器主机名/IPpg_standby
  • 复制用户repl_user
  • 数据库名称mydb

你还需要一个可用的备份和一个用于初始基础备份的维护窗口。这些示例假设使用PostgreSQL 12或更高版本,其中备用模式由standby.signal控制,连接设置通常由pg_basebackup -R写入。

第1步:配置主服务器

主服务器需要特定的设置来启用流复制并管理同步提交所需的预写日志(WAL)。

A. 调整主服务器上的postgresql.conf

编辑主服务器的postgresql.conf文件。以下参数对于流复制是必需的:

# --- 复制必需 ---
listen_addresses = '*'         # 允许来自备用服务器的连接
wal_level = replica            # 必须为'replica'或更高(例如'logical')
max_wal_senders = 10           # 来自备用服务器的最大并发连接数
max_replication_slots = 10     # 持久复制流所需的槽位

# --- 同步提交必需 ---
synchronous_standby_names = 'FIRST 1 (standby1)' # 通过application_name指定所需的备用服务器

# --- 可选但推荐 ---
wal_log_hints = on             # 推荐用于更安全的复制,但会增加WAL体积
shared_preload_libraries = 'pg_stat_statements' # 如果使用监控

关键参数说明:

  • wal_level = replica:这确保WAL中写入足够的信息,以便备用服务器能够重建数据库状态。对于同步提交,此级别是最低要求。
  • synchronous_standby_names:这是定义哪些备用服务器必须确认写入的核心设置。这里的名称是复制连接的application_name值,而不是复制槽名称。FIRST 1 (standby1)意味着PostgreSQL等待该列表中第一个可用的同步备用服务器。ANY 1 (standby1, standby2)意味着列表中的任何一个备用服务器都可以满足提交要求。

B. 配置基于主机的身份验证(pg_hba.conf

主服务器必须允许来自备用服务器的复制用户为复制目的进行连接。

在主服务器的pg_hba.conf中添加一个条目:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     repl_user       pg_standby/32           scram-sha-256

pg_standby/32替换为备用服务器的实际IP地址或子网。

C. 创建复制槽和用户

连接到主服务器上的PostgreSQL,创建必要的用户和复制槽。

1. 创建复制用户:

CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'a_strong_password';

2. 创建复制槽:

此槽确保WAL段被保留,直到备用服务器确认收到,防止备用服务器落后太多以至于需要新的基础备份。槽很有用,但如果备用服务器长时间宕机,它们也可能填满磁盘,因此请监控保留的WAL。

SELECT pg_create_physical_replication_slot('standby1_slot');

槽名称不必与synchronous_standby_names匹配。在此示例中,standby1是用于同步备用服务器选择的application_name,而standby1_slot是用于WAL保留的物理复制槽。

D. 重启主服务器

通过重启主服务器上的PostgreSQL服务来应用所有配置更改。

sudo systemctl restart postgresql

第2步:配置备用服务器

备用服务器配置为使用恢复配置从主服务器流式传输WAL记录。

A. 基础备份

在开始流式传输之前,备用服务器需要主服务器数据目录的完整副本。首先停止备用服务器上的PostgreSQL。

sudo systemctl stop postgresql

使用pg_basebackup进行基础备份。根据需要替换路径和连接详细信息:

# 使用pg_basebackup工具的示例
pg_basebackup -h pg_primary -D /var/lib/postgresql/15/main/ -U repl_user -P -Xs -R -W
  • -D:备用服务器上的目标数据目录。
  • -U:复制用户。
  • -P:显示进度。
  • -Xs:在基础备份期间包含必要的WAL文件。
  • -R:自动创建standby.signal文件并在postgresql.auto.conf(或恢复配置)中生成必要的连接设置。

B. 在备用服务器上配置postgresql.conf

在备用服务器上,确保PostgreSQL知道如何连接回主服务器。同步复制的关键细节是application_name;它必须与synchronous_standby_names中列出的名称匹配。

# --- 备用服务器必需 ---
primary_conninfo = 'host=pg_primary port=5432 user=repl_user password=a_strong_password application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on          # 允许在恢复/备用模式下进行读取查询

C. 启动备用服务器

启动备用服务器上的PostgreSQL服务。

sudo systemctl start postgresql

第3步:验证和测试同步提交

两台服务器都运行后,验证连接,然后测试同步行为。

A. 验证复制状态

连接到数据库并检查pg_stat_replication视图:

SELECT client_addr, application_name, state, sync_state FROM pg_stat_replication;

你应该会看到standby1的一个条目,其sync_statesync。如果显示potential,则备用服务器已连接,但当前不是满足同步提交的那个。如果显示async,则PostgreSQL未将其视为同步备用服务器;请检查application_namesynchronous_standby_names的拼写。

B. 测试同步提交

决定PostgreSQL等待程度的全局参数是synchronous_commit。对于RPO=0,你必须使用强制同步的值。

1. 设置全局行为

如果你按照第1步所示在主服务器上配置了synchronous_standby_names,则默认的synchronous_commit = on会等待同步备用服务器将WAL记录刷新到持久存储。remote_write会等待备用服务器将WAL记录写入操作系统,这通常更快,但如果备用服务器主机在刷新前崩溃,则强度不够。remote_apply会等待备用服务器重放事务,当你的应用程序在写入主服务器后立即从备用服务器读取时,这很有用。

对于大多数零数据丢失的HA设置,on是实际的起点。仅当备用服务器上的读写行为足够重要,值得增加额外延迟时,才使用remote_apply

# 在主服务器的postgresql.conf中
synchronous_commit = on

警告:与异步模式(offlocal)相比,同步提交会显著增加写入延迟。增加的延迟来自网络往返、备用服务器WAL写入速度,以及对于remote_apply,还有重放速度。

2. 在事务内测试

为了事务性地测试(无需全局配置更改),你可以按会话或事务进行设置:

-- 连接到主服务器

BEGIN;
SET LOCAL synchronous_commit = on;

INSERT INTO sales (item, amount) VALUES ('Widget A', 100);
-- 此INSERT准备WAL,必须由同步备用服务器确认。

COMMIT;
-- 仅当备用服务器确认WAL写入后,COMMIT才会成功。

如果在提交时没有配置的同步备用服务器可用,则提交会等待。这就是该功能的意义所在,但在中断期间可能会让团队感到惊讶:主服务器可能仍在运行,但写入看起来冻结了,因为PostgreSQL正在等待同步确认。当同步备用服务器消失时,没有通用的PostgreSQL设置会自动“回退”到异步提交。如果你想要这种行为,你的HA工具必须更改synchronous_standby_names,或者你必须有一个手册,在确定可用性比零数据丢失更重要后手动执行此操作。

更安全的双备用服务器模式

单个同步备用服务器在一切健康时提供了强大的持久性,但也为写入可用性创建了一个单点故障。如果该备用服务器宕机、缓慢或与主服务器隔离,提交将等待。在生产环境中,常见的模式是运行至少两个备用服务器,并要求一个同步确认:

synchronous_standby_names = 'ANY 1 (standby1, standby2)'

使用此设置,任何一个备用服务器都可以满足提交。如果standby1正在重启,standby2仍然可以确认写入。你仍然需要监控两个副本,因为一个备用服务器的长时间中断可能导致其复制槽保留大量WAL,但主服务器不太可能因为单个备用服务器故障而停滞。

要求两个确认是可能的:

synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'

这是一个更严格的持久性选择。它通常保留给具有非常低延迟链接的环境,并且有明确的理由要求在提交前有多个远程副本。对于许多应用程序数据库来说,“附近两个备用服务器中的任何一个”是更好的平衡。

启用后需要监控的内容

不要停留在“备用服务器已连接”。同步复制可能在技术上正常工作,而面向用户的延迟却变得更糟。部署后关注这些信号:

SELECT
    application_name,
    client_addr,
    state,
    sync_state,
    write_lag,
    flush_lag,
    replay_lag
FROM pg_stat_replication;

在主服务器上,sync_state = 'sync'告诉你哪个备用服务器当前是同步的。write_lagflush_lagreplay_lag有助于解释时间花在了哪里。如果write_lag很高,怀疑网络或备用服务器WAL写入压力。如果flush_lag很高,怀疑存储。如果只有replay_lag很高,备用服务器可能正在接收WAL,但由于I/O、CPU、锁或备用服务器上的长时间运行查询而应用缓慢。

还要监控槽保留:

SELECT
    slot_name,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;

复制槽保护备用服务器,但不能保护你的磁盘。如果一个槽处于非活动状态并且保留的WAL不断增长,要么快速修复备用服务器,要么在确认不再需要后删除该槽。

实用的部署计划

对于繁忙的生产系统,将同步复制视为一个分阶段更改,而不是一行配置调整。

首先,异步构建备用服务器并让其运行一段时间。确认它能在峰值写入期间跟上。如果它在异步时落后,那么当它变成同步时,将会损害提交延迟。

其次,设置application_name并验证主服务器在pg_stat_replication中看到的备用服务器完全符合你的预期。这里拼写错误很常见,因为synchronous_standby_names匹配的是运行时的application_name,而不是主机名,也不是槽。

第三,在低流量窗口期间启用同步复制,并从应用程序端观察提交延迟。PostgreSQL指标可能看起来正常,而应用程序连接池却因为事务现在持有连接时间稍长而积压。

最后,写下故障决策。如果同步备用服务器消失并且主服务器正在等待提交,谁被允许放宽synchronous_standby_names?在什么条件下?在重新加入节点之前,你将如何验证旧主服务器或旧备用服务器包含最新的数据?这些是操作决策,而不仅仅是数据库设置。

同步HA的最佳实践

  • 使用专用备用服务器:只将物理上靠近(低延迟)主服务器的备用服务器分配到你的同步复制列表中。高延迟将直接体现在提交时间上。
  • 监控复制延迟:即使在同步模式下,也要监控备用服务器延迟。一个仍然在技术上处于“同步”状态但处理WAL时间过长的慢速备用服务器仍然会影响用户体验。
  • 规划可用性权衡:提前决定操作员是否可以在事件期间临时从synchronous_standby_names中移除缺失的备用服务器。
  • 使用多个备用服务器:为了更好的写入可用性,配置synchronous_standby_names = 'ANY 1 (standby1, standby2)',这样任何一个备用服务器都可以确认提交。