连续归档可用于创建一种高可用性(HA)集簇配置,其中有一个或多个备库随时准备在主库失效时接管操作。这种能力通常称为温备或日志传送。
主库和备库协同工作以提供这种能力,不过两者之间只是松耦合。主库运行在持续归档模式下,而每台备库都运行在持续恢复模式下,从主库读取 WAL 文件。启用这种能力不需要修改数据库表,因此与其他一些复制方案相比,它的管理开销较低。这种配置对主库的性能影响也相对较小。
将 WAL 记录直接从一台数据库服务器移动到另一台数据库服务器,通常称为日志传送。PostgreSQL 通过一次传送一个文件(WAL 段)中的 WAL 记录来实现基于文件的日志传送。WAL 文件(16MB)可以轻松且低成本地传送到任意距离,无论是邻近系统、同一站点的另一台系统,还是地球另一端的系统。该技术所需的带宽取决于主库的事务速率。基于记录的日志传送粒度更细,会通过网络连接增量地流式传输 WAL 变更(见 Section 26.2.5)。
需要注意的是,日志传送是异步的,也就是说 WAL 记录是在事务提交之后才被传送的。因此,如果主库发生灾难性故障,就会存在一个数据丢失窗口;尚未传送的事务将会丢失。对于基于文件的日志传送,可以通过使用 archive_timeout 参数来限制这个数据丢失窗口,它可以设置为低至数秒。不过,如此低的设置会显著增加文件传送所需的带宽。流复制(见 Section 26.2.5)允许把数据丢失窗口缩得更小。
恢复性能已经足够好,因此一旦备库被激活,通常只需片刻就能达到完全可用状态。因此,这种配置被称为温备配置,它提供了高可用性。从归档的基础备份中恢复服务器并向前重放则需要更长时间,因此这种技术只能用于灾难恢复,而不是高可用性。备库还可以用于只读查询,这种情况下它被称为热备服务器。更多信息见 Section 26.5。
通常,最好让主库和备库尽可能相似,至少从数据库服务器的角度看应如此。尤其是,与表空间相关的路径名会原样传递,因此如果使用该特性,主库和备库必须为表空间配置完全相同的挂载路径。请记住,如果在主库上执行 CREATE TABLESPACE,则它所需的任何新挂载点都必须在执行该命令之前先在主库和所有备库上创建好。硬件不必完全相同,但经验表明,在应用和系统的整个生命周期内,维护两个相同的系统要比维护两个不同的系统更容易。无论如何,硬件架构必须相同 — 例如,从 32 位系统向 64 位系统传送日志是不可行的。
一般来说,不能在运行不同主版本 PostgreSQL 的服务器之间传送日志。PostgreSQL 全球开发组的策略是在次版本升级期间不改变磁盘格式,因此主库和备库运行不同次版本通常也能正常工作。不过,这方面并没有正式支持,因此仍建议主库和备库尽量保持在相同的发行级别。当升级到新的次版本时,最安全的策略是先升级备库 — 新的次版本更有可能兼容读取前一个次版本生成的 WAL 文件,反过来则未必。
如果服务器启动时其数据目录中存在 standby.signal 文件,服务器就会进入备库模式。
在备库模式中,服务器会持续应用从主库接收到的 WAL。备库可以从 WAL 归档中读取 WAL(见 restore_command),也可以通过 TCP 连接直接从主库读取 WAL(流复制)。备库还会尝试恢复位于备库集簇 pg_wal 目录中的任何 WAL。这种情况通常发生在服务器重启之后,此时备库会再次重放重启前从主库流入的 WAL;不过你也可以在任何时候手动把文件复制到 pg_wal 中,以便让它们被重放。
启动时,备库首先通过调用 restore_command 恢复归档位置中所有可用的 WAL。一旦到达其中可用 WAL 的末尾,并且 restore_command 失败,它就会尝试恢复 pg_wal 目录中可用的任何 WAL。如果这也失败,并且已经配置了流复制,备库就会尝试连接到主库,并从归档或 pg_wal 中找到的最后一条有效记录开始流式接收 WAL。如果这仍然失败,或者没有配置流复制,或者连接后来断开,备库就会返回第 1 步,再次尝试从归档恢复文件。这样在归档、pg_wal 以及流复制之间循环重试,直到服务器停止或被提升。
运行 pg_ctl promote 或发现触发文件(promote_trigger_file)时,服务器会退出备库模式,切换到正常运行。在故障切换前,会恢复归档或 pg_wal 中能立即获取的所有 WAL,但不会尝试连接主库。
如 Section 25.3 所述,在主库上设置持续归档,将日志归档到备库可访问的归档目录。即使主库宕机,该归档位置也应该对备库可访问;也就是说,它应位于备库本身或另一台可信服务器上,而不是位于主库上。
如果想使用流复制,就需要在主库上配置认证,以允许来自备库的复制连接;也就是说,创建一个角色,并在 pg_hba.conf 中添加一个或多个把数据库字段设置为 replication 的合适项。还要确保主库配置文件中的 max_wal_senders 被设置为足够大的值。如果要使用复制槽,还应确保 max_replication_slots 也设置得足够高。
如 Section 25.3.2 所述,获取一个基础备份来引导备库。
要设置备库,先恢复从主库获取的基础备份(见 Section 25.3.4)。然后在备库的集簇数据目录中创建一个 standby.signal 文件。把 restore_command 设置为一个从 WAL 归档复制文件的简单命令。如果你打算出于高可用目的设置多个备库,请确认 recovery_target_timeline 被设置为 latest(默认值),这样该备库就会跟随故障切换到另一台备库后产生的时间线变化。
不要将 pg_standby 或类似工具与这里介绍的内置备库模式一起使用。如果文件不存在,restore_command 应立即返回;服务器会在必要时重试该命令。有关 pg_standby 等工具的使用,参见 Section 26.4。
如果想使用流复制,就在 primary_conninfo 中填入一个 libpq 连接字符串,其中包含主机名(或 IP 地址)以及连接主库所需的其他细节。如果主库认证需要密码,也应在 primary_conninfo 中指定该密码。
如果你是为了高可用目的设置备库,那么也应像主库一样设置 WAL 归档、连接和认证,因为故障切换后该备库将作为主库工作。
如果使用 WAL 归档,可以借助archive_cleanup_command 参数删除备库不再需要的文件,以尽量缩小归档大小。pg_archivecleanup 工具专门设计用于在典型的单备库配置中与 archive_cleanup_command 配合使用,见 pg_archivecleanup。不过请注意,如果你还把归档用于备份目的,即使这些文件对备库已经不再需要,也必须至少保留从最新基础备份恢复所需的那些文件。
一个简单的配置示例如下:
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000''' restore_command = 'cp /path/to/archive/%f %p' archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'
备库的数量可以任意多,但如果使用流复制,请确保主库上的 max_wal_senders 设置得足够高,以允许它们同时连接。
与基于文件的日志传送相比,流复制可以让备库保持得更接近最新状态。备库连接到主库,主库在生成 WAL 记录时就会把它们流式发送给备库,而不必等待 WAL 文件被写满。
默认情况下流复制是异步的(见 Section 26.2.8),在这种情况下主库上提交一个事务与该变化在备库上变得可见之间存在短暂的延迟。不过这种延迟比基于文件的日志传送方式中要小得多,在备库的能力足以跟得上负载的前提下,延迟通常低于一秒。在流复制中,不需要 archive_timeout 来缩减数据丢失窗口。
如果使用流复制却未使用基于文件的连续归档,服务器可能在备库收到旧 WAL 段之前就将其回收。如果发生这种情况,备库需要从新的基础备份重新初始化。可以将 wal_keep_segments 设得足够大,确保 WAL 段不会过早回收,或者为备库配置复制槽,以避免这种情况。如果配置了备库可以访问的 WAL 归档,则不需要这些办法,因为只要归档保留了足够的段,备库就始终可以借助归档追赶主库。
要使用流复制,请先按 Section 26.2 所述设置一个基于文件的日志传送备库。把它转变为流复制备库的步骤,就是把 primary_conninfo 设置为指向主库。还要在主库上设置 listen_addresses 和认证选项(见 pg_hba.conf),以便备库能够连接到主库上的 replication 伪数据库(见 Section 26.2.5.1)。
在支持 keepalive 套接字选项的系统上,设置 tcp_keepalives_idle、tcp_keepalives_interval 和 tcp_keepalives_count 有助于主库迅速注意到断开的连接。
设置来自备库的最大并发连接数(详见 max_wal_senders)。
备库启动后,如果正确设置了 primary_conninfo,备库会在重放归档中所有可用的 WAL 文件后连接主库。如果连接成功,就会在备库上看到 WAL 接收进程,在主库上看到对应的 WAL 发送进程。
务必正确设置复制访问权限,使只有受信任的用户才能读取 WAL 流,因为很容易从中提取特权信息。备库必须以超级用户或具有 REPLICATION 权限的账户向主库认证。建议为复制创建专用用户账户,并授予 REPLICATION 和 LOGIN 权限。虽然 REPLICATION 权限赋予的权限很高,但它不允许用户修改主库系统上的任何数据,而 SUPERUSER 权限允许这样做。
复制的客户端认证由 pg_hba.conf 中的记录控制。记录要将 replication 指定为 database 字段的值。例如,如果备库所在主机的 IP 地址为 192.168.1.100,用于复制的账户名为 foo,管理员可以将以下行添加到 pg_hba.conf 文件(位于主库)中:
# Allow the user "foo" from host 192.168.1.100 to connect to the primary # as a replication standby if the user's password is correctly supplied. # # TYPE DATABASE USER ADDRESS METHOD host replication foo 192.168.1.100/32 md5
主库的主机名、端口号、连接用户名和密码在 primary_conninfo 中指定。也可以将密码设置在备库的 ~/.pgpass 文件中(将 replication 指定为 database 字段的值)。例如,如果主库所在主机的 IP 地址为 192.168.1.50,端口为 5432,用于复制的账户名为 foo,密码为 foopass,管理员可以将以下行添加到 postgresql.conf 文件(位于备库)中:
# The standby connects to the primary that is running on host 192.168.1.50 # and port 5432 as the user "foo" whose password is "foopass". primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
流复制的一个重要健康指标,是主库上已经生成但尚未在备库上应用的 WAL 记录量。你可以通过比较主库上的当前 WAL 写入位置和备库收到的最后一个 WAL 位置来计算这种滞后。这些位置分别可以用主库上的 pg_current_wal_lsn 和备库上的 pg_last_wal_receive_lsn 取得(详见 Table 9.84 和 Table 9.85)。备库上的最后一个 WAL 接收位置也会显示在 WAL 接收进程的进程状态中,即通过 ps 命令显示的状态(详见 Section 27.1)。
可以通过 pg_stat_replication 视图获取 WAL 发送进程列表。pg_current_wal_lsn 与该视图的 sent_lsn 字段相差较大,可能表示主库负载很高;而 sent_lsn 与备库上 pg_last_wal_receive_lsn 的差值较大,则可能表示网络延迟或备库负载很高。
在热备上,可以通过 pg_stat_wal_receiver 视图获取 WAL 接收进程的状态。如果 pg_last_wal_replay_lsn 与该视图的 received_lsn 相差较大,说明接收 WAL 的速度超过了重放速度。
复制槽提供了一种自动化方法,以确保主库在所有备库都收到 WAL 段之前不会删除它们,并且即使备库处于断开状态,主库也不会删除可能导致恢复冲突的行。
除了使用复制槽,也可以通过 wal_keep_segments 防止旧 WAL 段被删除,或者使用 archive_command 将这些段保存在归档中。不过,这些方法通常会保留多于实际所需的 WAL 段,而复制槽只保留已知必需的段数。这些方法的优点是能限制 pg_wal 的空间需求;目前使用复制槽还无法做到这一点。
类似地,hot_standby_feedback 和 vacuum_defer_cleanup_age 可以保护相关行,避免它们被清理删除,但前者在备库未连接期间无法提供保护,后者则通常需要设为较大的值才能提供充分保护。复制槽克服了这些缺点。
每个复制槽都有一个名称,该名称可以包含小写字母、数字和下划线字符。
现有复制槽及其状态可以在 pg_replication_slots 视图中查看。
复制槽可以通过流复制协议(见Section 52.4)或者 SQL 函数(见Section 9.26.6)创建和删除。
可以这样创建复制槽:
postgres=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
slot_name | lsn
-------------+-----
node_a_slot |
postgres=# SELECT slot_name, slot_type, active FROM pg_replication_slots;
slot_name | slot_type | active
-------------+-----------+--------
node_a_slot | physical | f
(1 row)
要配置备库使用此复制槽,需要将 primary_slot_name 配置在备库上。下面是一个简单的示例:
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass' primary_slot_name = 'node_a_slot'
级联复制特性允许一台备库接受复制连接,并像中继器一样把 WAL 记录流式发送给其他备库。这可以用来减少直接连接到主库的连接数,并使站点间的带宽开销最小化。
同时充当接收方和发送方的备库称为级联备库。与主库连接更直接的备库称为上游服务器,距离更远的备库则称为下游服务器。级联复制不限制下游服务器的数量或组织方式,不过每个备库只连接一个上游服务器,并最终连接到同一个主库。
级联备库不仅发送从主库接收到的 WAL 记录,也会发送那些从归档中恢复的记录。因此,即使某条上游复制连接被中断,只要仍有新的 WAL 记录可用,下游的流复制就会继续。
级联复制目前是异步的。同步复制(见Section 26.2.8)设置当前对级联复制无影响。
热备反馈会向上传播,无论级联拓扑如何。
如果某个上游备库被提升为新主库,只要 recovery_target_timeline 设为 'latest'(默认值),下游服务器就会继续从新主库接收流。
要使用级联复制,需要把级联备库设置为能够接受复制连接(也就是设置 max_wal_senders 和 hot_standby,并配置 基于主机的认证)。你还需要把下游备库中的 primary_conninfo 设置为指向级联备库。
PostgreSQL 的流复制默认是异步的。如果主库崩溃,则某些已提交的事务可能尚未复制到备库,从而导致数据丢失。数据丢失量与故障切换时的复制延迟成正比。
同步复制能够确认一个事务所做的全部修改已经被传送到一台或多台同步备库。这扩展了事务提交所提供的标准持久性级别。在计算机科学理论中,这种保护级别被称为 2-safe 复制;而当synchronous_commit被设置为remote_write时,则称为 group-1-safe(group-safe 和 1-safe)。
请求同步复制时,每个写事务的提交都会等待,直到收到确认,表明该提交已被写入主库和备库磁盘上的预写式日志。数据唯一可能丢失的情况,是主库和备库同时崩溃。这可以提供更高的持久性级别,不过前提是系统管理员必须谨慎地部署和管理这两台服务器。等待确认会增强用户对服务器崩溃时更改不会丢失的信心,但也必然会增加请求事务的响应时间。最短等待时间是主库与备库之间的往返时间。
只读事务和事务回滚无需等待备库回复。子事务提交也不等待备库响应,只有顶层提交才会等待。数据加载或索引构建等长时间运行的操作,直到最后的提交消息才会等待。所有两阶段提交操作都需要等待提交,包括准备和提交两个阶段。
同步备库可以是物理复制备库,也可以是逻辑复制订阅者。它还可以是任何其他物理或逻辑 WAL 复制流的消费者,只要它知道如何发送适当的反馈消息。除了内置的物理和逻辑复制系统之外,还包括pg_receivewal和pg_recvlogical之类的专用程序,以及一些第三方复制系统和定制程序。关于同步复制支持的细节,请查看相应文档。
一旦流复制已经配置好,配置同步复制只需要额外一步:必须把synchronous_standby_names设置为非空值。synchronous_commit也必须设置为on,但由于这是默认值,通常无需更改(见Section 19.5.1和Section 19.6.2)。这样的配置会导致每次提交都等待确认,以保证备库已经把提交记录写入持久存储。synchronous_commit可以由单个用户设置,因此既可以在配置文件中配置,也可以针对特定用户或数据库配置,或者由应用动态配置,从而在每事务级别控制持久性保证。
当提交记录已经在主库上写入磁盘之后,WAL 记录就会被发送到备库。每当新的一批 WAL 数据被写入磁盘时,备库就会发送回复消息,除非备库上的wal_receiver_status_interval被设置为零。如果synchronous_commit被设置为remote_apply,那么备库会在提交记录被重放时发送回复消息,从而使该事务变得可见。如果根据主库上的synchronous_standby_names设置,该备库被选为同步备库,那么它发出的回复会与其他同步备库的回复一起,用于决定何时释放那些正在等待确认提交记录已被收到的事务。这些参数允许管理员指定哪些备库应作为同步备库。注意,同步复制的配置主要在主库上进行。被命名的备库必须直接连接到主库;主库并不知道使用级联复制的下游备库。
把synchronous_commit设置为remote_write,会使每次提交都等待,直到备库确认已经收到提交记录并把它写入自己的操作系统,但不会等待数据被刷到备库磁盘上。与on相比,这种设置提供较弱一些的持久性保证:在操作系统崩溃时,备库可能丢失数据,尽管在PostgreSQL崩溃时不会。不过在实践中,这是一种有用的设置,因为它可以降低事务响应时间。只有当主库和备库都崩溃,并且主库数据库同时发生损坏时,才可能发生数据丢失。
把synchronous_commit设置为remote_apply,会使每次提交都等待,直到当前同步备库报告它们已经重放了该事务,从而使它对用户查询可见。在简单场景下,这可以支持具备因果一致性的负载均衡。
如果请求快速关闭,用户将停止等待。不过,与使用异步复制时一样,在所有尚未解决的 WAL 记录传输到当前已连接的备库之前,服务器不会完全关闭。
同步复制支持一台或多台同步备库;事务将一直等待,直到所有被视为同步的备库确认已收到其数据。事务需要等待多少台同步备库的回复,由synchronous_standby_names指定。该参数还指定了一个备库名称列表,以及从中选择同步备库的方法(FIRST和ANY)。
FIRST方法指定基于优先级的同步复制,使事务提交等待,直到它们的 WAL 记录被复制到按优先级选出的、所要求数量的同步备库上。列表中出现得越早的备库优先级越高,并会被视为同步备库。该列表中更靠后的备库则是潜在的同步备库。如果当前某个同步备库因任何原因断开连接,它将立刻由下一个优先级最高的备库替代。
基于优先级的多同步备库的synchronous_standby_names示例如下:
synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'
在这个例子中,如果四台备库s1、s2、s3和s4都在运行,则s1和s2会被选为同步备库,因为它们的名字在备库名称列表中出现得更早。s3是潜在的同步备库,当s1或s2中的任意一台失效时,它就会接替其角色。由于s4的名称不在列表中,因此它是异步备库。
ANY方法指定基于法定人数的同步复制,使事务提交等待,直到它们的 WAL 记录被复制到列表中所要求数量的同步备库中的至少这么多台。
基于法定人数的多同步备库的synchronous_standby_names示例如下:
synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
在这个例子中,如果四台备库s1、s2、s3和s4都在运行,事务提交将等待来自s1、s2和s3中的任意两台备库的回复。由于s4的名字不在列表中,因此它是异步备库。
备库的同步状态可以通过pg_stat_replication视图查看。
同步复制通常要求对备库进行仔细规划和部署,才能保证应用具有可接受的性能。等待本身不会占用系统资源,但事务锁会一直保持到传输得到确认。因此,若不谨慎使用同步复制,数据库应用的性能会因为响应时间增加和争用加剧而下降。
PostgreSQL允许应用开发者通过复制来指定所需的持久性级别。这可以在整个系统级别指定,也可以针对特定用户、特定连接,甚至单个事务指定。
例如,一个应用负载可能由如下部分组成:10% 的变更是重要的客户资料,而 90% 的变更是不太重要、即使丢失业务也较容易承受的数据,例如用户之间的聊天消息。
通过在应用级别(在主库上)指定同步复制选项,我们可以只对最重要的变更提供同步复制,而不会拖慢整体工作负载。应用级别选项是让高性能应用获得同步复制收益的一种重要而实用的工具。
你应当确保网络带宽高于 WAL 数据的生成速率。
synchronous_standby_names指定了当synchronous_commit设置为on、remote_apply或remote_write时,事务提交需要等待其响应的同步备库的数量和名称。如果任意一台同步备库崩溃,此类事务提交就可能永远无法完成。
对于高可用而言,最好的办法是确保你始终保有所要求数量的同步备库。这可以通过在synchronous_standby_names中命名多个潜在同步备库来实现。
在基于优先级的同步复制中,列表中出现较早的备库将被用作同步备库。列在它们之后的备库,会在当前同步备库失效时接替其角色。
在基于法定人数的同步复制中,列表中的所有备库都会被用作同步备库候选。即使其中一台失效,其他备库仍会继续承担同步备库候选的角色。
当一台备库第一次连接到主库时,它还没有正确同步。这种状态称为catchup模式。一旦备库与主库之间的滞后第一次变为零,它就会进入实时的streaming状态。备库刚创建之后,追赶阶段可能会持续较长时间。如果备库被关闭,则追赶阶段会随着它停机时间的延长而变长。只有在到达streaming状态后,备库才能成为同步备库。这种状态可以通过pg_stat_replication视图查看。
如果主库在提交正等待确认时重启,这些等待中的事务会在主库恢复后被标记为已完全提交。无法确定在主库崩溃时,所有备库是否已经收到全部尚未处理的 WAL 数据。因此,某些事务可能不会在备库上显示为已提交,即使它们在主库上显示为已提交。我们所提供的保证是:只有在确认 WAL 数据已经被所有同步备库安全接收之后,应用才会收到事务成功提交的显式确认。
如果你确实无法维持所要求数量的同步备库,那么就应当在synchronous_standby_names中减少事务提交需要等待其响应的同步备库数量(或者禁用它),然后在主库上重新加载配置文件。
如果主库与剩余的备库隔离开了,你应当故障切换到那些剩余备库中最佳的候选者。
如果需要在事务等待期间重新创建备库,请确保在 synchronous_commit = off 的会话中运行 pg_start_backup() 和 pg_stop_backup() 命令,否则这些请求会一直等待备库出现。
当在备库中使用持续 WAL 归档时,有两种不同的场景:WAL 归档可以由主库和备库共享,或者备库可以拥有自己的 WAL 归档。当备库拥有自己的 WAL 归档时,应把archive_mode设置为always,这样备库就会为它接收到的每个 WAL 段调用归档命令,无论该 WAL 段是通过从归档恢复得到的,还是通过流复制得到的。共享归档也可以类似处理,但archive_command必须检查正在归档的文件是否已经存在,以及已有文件的内容是否完全相同。这就要求在archive_command中更加小心:既不能用不同内容覆盖现有文件,又要在同一个文件被归档两次且内容完全相同时返回成功。如果两台服务器同时尝试归档同一个文件,还必须确保整个过程不存在竞争条件。
如果archive_mode被设置为on,那么归档器在恢复期间或备库模式下不会启用。如果备库被提升,它会在提升后开始归档,但不会归档任何不是由它自己生成的 WAL 或时间线历史文件。要在归档中获得完整的一系列 WAL 文件,就必须确保所有 WAL 在到达备库之前已经被归档。对于基于文件的日志传送,这天然成立,因为备库只能恢复归档中找到的文件;但在启用流复制时则不是这样。当服务器不处于恢复模式时,on和always模式之间没有区别。