PostgreSQL服务器管理:高可用、负载均衡和复制

本文档为PostgreSQL 9.6.0文档,本转载已得到原译者彭煜玮授权。

数据库服务器可以一起工作,这样如果主要的服务器失效则允许一个第二服务器快速接手它的任务(高可用性),或者可以允许多个计算机提供相同的数据(负载均衡)。理想情况下,数据库服务器能够无缝地一起工作。提供静态网页服务的网页服务器可以非常容易地通过把网页请求均衡到多个机器来组合。事实上,只读的数据库服务器也可以相对容易地组合起来。不幸的是,大部分数据库服务器收到的请求是读/写混合的,并且读/写服务器更难于组合。这是因为尽管只读数据只需要在每台服务器上放置一次,但对于任意服务器的一次写动作却必须被传播给所有的服务器,这样才能保证未来对于那些服务器的读请求能返回一致的结果。

这种同步问题是服务器一起工作的最根本的困难。因为没有单一解决方案能够消除该同步问题对所有用例的影响。有多种解决方案,每一种方案都以一种不同的方式提出了这个问题,并且对于一种特定的负载最小化了该问题所产生的影响。

某些方案通过只允许一台服务器修改数据来处理同步。能修改数据的服务器被称为读/写、主控或主要服务器。跟踪主控机中改变的服务器被称为后备或从属服务器。如果一台后备服务器只有被提升为一台主控服务器后才能被连接,它被称为一台温后备服务器,而一台总是能够接受连接并且提供只读查询的后备服务器被称为一台热后备服务器。

某些方案是同步的,即一个数据修改事务只有到所有服务器都提交了该事务之后才被认为是提交成功。这保证了一次故障转移不会丢失任何数据并且所有负载均衡的服务器将返回一致的结果(不管哪台服务器被查询)。相反,异步的方案允许在一次提交和它被传播到其他服务器之间有一些延迟,这产生了切换到一个备份服务器时丢失某些事务的可能性,并且负载均衡的服务器可能会返回略微陈旧的结果。当同步通信可能很慢时,可以使用异步通信。

方案也可以按照它们的粒度进行分类。某些方案只能处理一整个数据库服务器,而其他的允许在每个表或每个数据库的级别上进行控制。

在任何选择中,都必须考虑性能。通常在功能和性能之间都存在着权衡。例如,在一个低速网络上的一种完全同步的方案可能使性能减少超过一半,而一种异步的方案产生的性能影响可能是最小的。

本节的剩余部分勾勒了多种故障转移、复制和负载均衡方案。其中也有一个术语表可用。

1. 不同方案的比较

共享磁盘故障转移

共享磁盘故障转移避免了只使用一份数据库拷贝带来的同步开销。它使用一个由多个服务器共享的单一磁盘阵列。如果主数据库服务器失效,后备服务器则可以挂载并启动数据库,就好像它从一次数据库崩溃中恢复过来了。这是一种快速的故障转移,并且不存在数据丢失。

共享硬件功能在网络存储设备中很常见。也可以使用一个网络文件系统,但是要注意的是该文件系统应具有完全的POSIX行为(见Section 18.2.2)。这种方法的一个重大限制是如果共享磁盘阵列失效或损坏,主要和后备服务器都会变得无法工作。另一个问题是在主要服务器运行时,后备服务器永远不能访问共享存储。

文件系统(块设备)复制

共享硬件功能的一种修改版本是文件系统复制,在其中对一个文件系统的所有改变会被镜像到位于另一台计算机上的一个文件系统。唯一的限制是该镜像过程必须能保证后备服务器有一份该文件系统的一致的拷贝 — 特别是对后备服务器的写入必须按照主控机上相同的顺序进行。DRBD是用于 Linux 的一种流行的文件系统复制方案。

事务日志传送

温备和热备服务器能够通过读取一个预写式日志(WAL)记录的流来保持为当前状态。如果主服务器失效,后备服务器拥有主服务器的几乎所有数据,并且能够快速地被变成新的主数据库服务器。这可以是同步的或异步的,并且只能用于整个数据库服务器。

可以使用基于文件的日志传送(Section 26.2)、流复制(见Section 26.2.5)或两者的组合来实现一个后备服务器。关于热备的信息可见Section 26.5。

基于触发器的主-备复制

一个主-备复制设置会把所有数据修改查询发送到主服务器。主服务器异步地将数据修改发送给后备服务器。当主服务器正在运行时,后备服务器可以回答只读查询。后备服务器对数据仓库查询是一种理想的选择。

Slony-I是这种复制类型的一个例子。它使用表粒度,并且支持多个后备服务器。因为它会异步更新后备服务器(批量),在故障转移时可能会有数据丢失。

基于语句的复制中间件

通过基于语句的复制中间件,一个程序拦截每一个 SQL 查询并把它发送给一个或所有服务器。每一个服务器独立地操作。读写查询必须被发送给所有服务器,这样每一个服务器都能接收到任何修改。但只读查询可以被只发送给一个服务器,这样允许读负载在服务器之间分布。

如果查询被简单地且未经修改地广播,random()、CURRENT_TIMESTAMP之类的函数以及序列在不同服务器上可能有不同的值。这是因为每一个服务器会独立地操作,并且 SQL 查询被广播(而不是真正被修改的行)。如果这不可接受,中间件或应用必须从一个单一服务器查询这样的值并且然后将那些值用在写查询中。另一个选项是将这个复制选项和一种传统主-备设置一起使用,即数据修改查询只被发送给主服务器并且通过主-备复制传播到后备服务器,而不是通过复制中间件。必须要注意的是,所有事务要么在所有服务器上都提交,要么在所有服务器上都中止,也许可以使用两阶段提交(PREPARE TRANSACTION和COMMIT PREPARED)。Pgpool-II和Continuent Tungsten是这种复制类型的例子。

异步多主控机复制

对于不会被定期连接的服务器(如笔记本或远程服务器),保持服务器间的数据一致是一个挑战。通过使用异步的多主控机复制,每一个服务器独立工作并且定期与其他服务器通信来确定冲突的事务。这些冲突可以由用户或冲突解决规则来解决。Bucardo 是这种复制类型的一个例子。

同步多主控机复制

在同步多主控机复制中,每一个服务器能够接受写请求,并且在每一个事务提交之前,被修改的数据会被从原始服务器传送给每一个其他服务器。繁重的写活动可能导致过多的锁定,进而导致很差的性能。事实上,写性能通常比一个单一服务器还要糟。读请求可以被发送给任意服务器。某些实现使用共享磁盘来减少通信负荷。同步多主控机复制主要对于读负载最好,尽管它的大优点是任意服务器都能接受写请求 — 没有必要在主服务器和后备服务器之间划分负载,并且因为数据修改被从一个服务器发送到另一个服务器,不会有非确定函数(如random())的问题。

PostgreSQL不提供这种复制类型,尽管在应用代码或中间件中可以使用PostgreSQL的两阶段提交(PREPARE TRANSACTION和COMMIT PREPARED)来实现这种复制。

商业方案

Because 因为PostgreSQL是开源的并且很容易被扩展,一些公司已经使用PostgreSQL并且创建了带有唯一故障转移、复制和负载均衡能力的商业性的闭源方案。

Table 26-1总结了上述多种方案的能力。

Table 26-1. 高可用、负载均衡和复制特性矩阵


PostgreSQL服务器管理:高可用、负载均衡和复制


有一些方案不适合上述的类别:

数据分区

数据分区将表分开成数据集。每个集合只能被一个服务器修改。例如,数据可以根据办公室划分,如伦敦和巴黎,每一个办公室有一个服务器。如果查询有必要组合伦敦和巴黎的数据,一个应用可以查询两个服务器,或者可以使用主/备复制来在每一台服务器上保持其他办公室数据的一个只读拷贝。

多服务器并行查询执行

上述的很多方案允许多个服务器来处理多个查询,但是没有一个允许一个单一查询使用多个服务器来更快完成。这种方案允许多个服务器在一个单一查询上并发工作。这通常通过把数据在服务器之间划分并且让每一个服务器执行该查询中属于它的部分,然后将结果返回给一个中心服务器,由它整合结果并发回给用户。Pgpool-II具有这种能力。同样,也可以使用PL/Proxy工具集来实现这种方案。

2. 日志传送后备服务器

连续归档可以被用来创建一个高可用性(HA)集群配置,其中有一个或多个后备服务器随时准备在主服务器失效时接管操作。这种能力被广泛地称为温备或日志传送。

主服务器和后备服务器一起工作来提供这种能力,但这些服务器只是松散地组织在一起。主服务器在连续归档模式下操作,而每一个后备服务器在连续恢复模式下操作并且持续从主服务器读取 WAL 文件。要启用这种能力不需要对数据库表做任何改动,因此它相对于其他复制方案降低了管理开销。这种配置对主服务器的性能影响也相对较低。

直接从一个数据库服务器移动 WAL 记录到另一台服务器通常被描述为日志传送。PostgreSQL通过一次一文件(WAL 段)的 WAL 记录传输实现了基于文件的日志传送。不管 WAL 文件(16 MB)要被送到一个临近的系统、同一站点的另一个系统或是在地球遥远的另一端的一个系统上,它都可以在任何距离上被简单和便宜地传送。这种技术所需的带宽取根据主服务器的事务率而变化。基于记录的日志传送具有更细的粒度并且能够在网络连接上以流的方式增量传递 WAL 的改变(见Section 26.2.5)。

需要注意的是日志传送是异步的,即 WAL 记录是在事务提交后才被传送。正因为如此,在一个窗口期内如果主服务器发生灾难性的失效则会导致数据丢失,还没有被传送的事务将会被丢失。基于文件的日志传送中这个数据丢失窗口的尺寸可以通过使用参数archive_timeout进行限制,它可以被设置成低至数秒。但是这样低的设置大体上会增加文件传送所需的带宽。流复制(见Section 26.2.5)允许更小的数据丢失窗口。

这种配置的恢复性能是足够好的,后备服务器在被激活后通常只有片刻就可以到达完全可用。因此,这被称为一种提供高可用性的温备配置。从一个已归档的基础备份恢复一个服务器并且前滚将需要较长时间,因此该技术只提供了灾难恢复的一种方案,而不适合于高可用性。一台后备服务器也可以被用于只读查询,在这种情况下它被称为一台热备服务器。

2.1. 规划

创建主服务器和后备服务器通常是明智的,因此它们可以尽可能相似,至少从数据库服务器的角度来看是这样。特别地,与表空间相关的路径名将被未经修改地传递,因此如果该特性被使用,主、备服务器必须对表空间具有完全相同的挂载路径。记住如果CREATE TABLESPACE在主服务器上被执行,在命令被执行前,它所需要的任何新挂载点必须在主服务器和所有后备服务器上先创建好。硬件不需要完全相同,但是经验显示,在应用和系统的生命期内维护两个相同的系统比维护两个不相似的系统更容易。在任何情况下硬件架构必须相同 — 从一个 32 位系统传送到一个 64 位系统将不会工作。

通常,不能在两个运行着不同主版本PostgreSQL的服务器之间传送日志。PostgreSQL 全球开发组的策略是不在次版本升级中改变磁盘格式,因此在主服务器和后备服务器上运行不同次版本将会成功地工作。不过,在这方面并没有提供正式的支持,因此我们建议让主备服务器上运行的版本尽可能相同。当升级到一个新的次版本时,最安全的策略是先升级后备服务器 — 一个新的次版本发行更可能兼容从前一个次版本读取 WAL 文件。

2.2. 后备服务器操作

在后备模式中,服务器持续地应用从主控服务器接收到的 WAL。后备服务器可以从一个 WAL 归档(restore_command)或者通过一个 TCP 连接直接从主控机(流复制)读取 WAL。后备服务器将也尝试恢复在后备集簇的pg_xlog目录中找到的 WAL。那通常在一次数据库重启后发生,那时后备机将在重启之前重播从主控机流过来的 WAL,但是你也可以在任何时候手动拷贝文件到pg_xlog让它们被重播。

在启动时,后备机通过恢复归档位置所有可用的 WAL 来开始,这称为restore_command。一旦它到达那里可用的 WAL 的末尾并且restore_command失败,它会尝试恢复pg_xlog目录中可用的任何 WAL。如果那也失败并且流复制已被配置,后备机会尝试连接到主服务器并且从在归档或pg_xlog中找到的最后一个可用记录开始流式传送 WAL。如果那失败并且没有配置流复制,或者该连接后来断开,后备机会返回到步骤 1 并且尝试再次从归档里的文件恢复。这种尝试归档、pg_xlog和流复制的循环会一直重复知道服务器停止或者一个触发器文件触发了故障转移。

当pg_ctl promote被运行或一个触发器文件被找到(trigger_file),后备模式会退出并且服务器会切换到普通操作。在故障转移之前,在归档或pg_xlog中立即可用的任何 WAL 将被恢复,但不会尝试连接到主控机。

2.3. 为后备服务器准备主控机

如Section 25.3中所述,在主服务器上设置连续归档到一个后备服务器可访问的归档目录。即使主服务器垮掉该归档位置也应当是后备服务器可访问的,即它应当位于后备服务器本身或者另一个可信赖的服务器,而不是位于主控服务器上。

如果你想要使用流复制,在主服务器上设置认证来允许来自后备服务器的复制连接。即创建一个角色并且在pg_hba.conf中提供一个或多个数据库域被设置为replication的项。还要保证在主服务器的配置文件中max_wal_senders被设置为足够大的值。如果要使用复制槽,请确保max_replication_slots也被设置得足够高。

2.4. 建立一个后备服务器

要建立后备服务器,恢复从主服务器取得的基础备份。在后备服务器的集簇数据目录中创建一个恢复命令文件recovery.conf,并且打开standby_mode。将restore_command设置为一个从 WAL 归档中复制文件的简单命令。如果你计划为了高可用性目的建立多个后备服务器,将recovery_target_timeline设置为latest来使得该后备服务器遵循发生在故障转移到另一个后备服务器之后发生的时间线改变。

Note: 不要把 pg_standby 或相似的工具和这里描述的内建后备模式一起使用。如果文件不存在,restore_command应该立即返回,如果必要该服务器将再次尝试该命令。

如果你想要使用流复制,在primary_conninfo中填入一个 libpq 连接字符串,其中包括主机名(或 IP 地址)和连接到主服务器所需的任何附加细节。如果主服务器需要一个口令用于认证,口令也应该被指定在primary_conninfo中。

如果你正在为高性能目的建立后备服务器,像主服务器一样建立 WAL 归档、连接和认证,因为在故障转移后该后备服务器将作为一个主服务器工作。

如果你正在使用一个 WAL 归档,可以使用archive_cleanup_command参数来移除后备服务器不再需要的文件,这样可以最小化 WAL 归档的尺寸。pg_archivecleanup工具被特别设计为在典型单一后备配置下与archive_cleanup_command共同使用,见pg_archivecleanup。不过要注意,如果你正在为备份目的使用归档,有一些文件即使后备服务器不再需要你也需要保留它们,它们被用来从至少最后一个基础备份恢复。

recovery.conf的一个简单例子是:


standby_mode = 'on'
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'

你可以有任意数量的后备服务器,但是如果你使用流复制,确保你在主服务器上将max_wal_senders设置得足够高,这样可以允许它们能同时连接。

2.5. 流复制

流复制允许一台后备服务器比使用基于文件的日志传送更能保持为最新的状态。后备服务器连接到主服务器,主服务器则在 WAL 记录产生时即将它们以流式传送给后备服务器而不必等到 WAL 文件被填充。

默认情况下流复制是异步的(见Section 26.2.8),在这种情况下主服务器上提交一个事务与该变化在后备服务器上变得可见之间存在短暂的延迟。不过这种延迟比基于文件的日志传送方式中要小得多,在后备服务器的能力足以跟得上负载的前提下延迟通常低于一秒。在流复制中,不需要archive_timeout来缩减数据丢失窗口。

如果你使用的流复制没有基于文件的连续归档,该服务器可能在后备机收到 WAL 段之 前回收这些旧的 WAL 段。如果发生这种情况,后备机将需要重新从一个新的基础备 份初始化。通过设置wal_keep_segments为一个足够高的值来确保旧 的 WAL 段不会被太早重用或者为后备机配置一个复制槽,可以避免发生这种情况。如 果设置了一个后备机可以访问的 WAL 归档,就不需要这些解决方案,因为该归档可以 为后备机保留足够的段,后备机总是可以使用该归档来追赶主控机。

如果你使用的流复制没有基于文件的连续归档,你必须在主服务器上设置wal_keep_segments为一个足够高的值来确保旧的 WAL 段不会被太早再利用,因为后备服务器可能还需要它们来追上主服务器。如果后备服务器落后太多,它需要从一个新的基础备份进行初始化。如果你设置了一个后备服务器可访问的 WAL 归档,wal_keep_segments就不是必要的,因为后备服务器总是可以使用该归档来追上主服务器。

要使用流复制,按Section 26.2所述建立一个基于文件的日志传送后备服务器。将一个基于文件日志传送后备服务器转变成流复制后备服务器的步骤是把recovery.conf文件中的primary_conninfo设置指向主服务器。设置主服务器上的listen_addresses和认证选项(见pg_hba.conf),这样后备服务器可以连接到主服务器上的伪数据库replication。

在支持 keepalive 套接字选项的系统上,设置tcp_keepalives_idle、tcp_keepalives_interval和tcp_keepalives_count有助于主服务器迅速地注意到一个断开的连接。

设置来自后备服务器的并发连接的最大数目。

当后备服务器被启动并且primary_conninfo被正确设置,后备服务器将在重放完归档中所有可用的 WAL 文件之后连接到主服务器。如果连接被成功建立,你将在后备服务器中看到一个 walreceiver 进程,并且在主服务器中有一个相应的 walsender 进程。

2.5.1. 认证

设置好用于复制的访问权限非常重要,这样只有受信的用户可以读取 WAL 流,因为很容易从 WAL 流中抽取出需要特权才能访问的信息。后备服务器必须作为一个超级用户或一个具有REPLICATION特权的账户向主服务器认证。我们推荐为复制创建一个专用的具有REPLICATION和LOGIN特权的用户账户。虽然REPLICATION特权给出了非常高的权限,但它不允许用户修改主系统上的任何数据,而SUPERUSER特权则可以。

复制的客户端认证由一个在database域中指定replication的pg_hba.conf记录控制。例如,如果后备服务器运行在主机 IP 192.168.1.100并且用于复制的账户名为foo,管理员可以在主服务器上向pg_hba.conf文件增加下列行:


# 允许来自 192.168.1.100 的用户 "foo" 在提供了正确的口令时作为一个
# 复制后备机连接到主控机。
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     foo             192.168.1.100/32        md5

主服务器的主机名和端口号、连接用户名和口令在recovery.conf文件中指定。在后备服务器上还可以在~/.pgpass文件中设置口令(在database域中指定replication)。例如,如果主服务器运行在主机 IP 192.168.1.50、端口5432上,并且口令为foopass,管理员可以在后备服务器的recovery.conf文件中增加下列行:


# 后备机要连接到的主控机运行在主机 192.168.1.50 上,
# 端口号是 5432,连接所用的用户名是 "foo",口令是 "foopass"。
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass

2.5.2. 监控

流复制的一个重要健康指标是在主服务器上产生但还没有在后备服务器上应用的 WAL 记录数。你可以通过比较主服务器上的当前 WAL 写位置和后备服务器接收到的最后一个 WAL 位置来计算这个滞后量。它们分别可以用主服务器上的pg_current_xlog_location和后备服务器上的pg_last_xlog_receive_location来检索。后备服务器的最后 WAL 接收位置也被显示在 WAL 接收者进程的进程状态中,即使用ps命令显示的状态。

你可以通过pg_stat_replication视图检索 WAL 发送者进程的列表。pg_current_xlog_location与sent_location域之间的巨大差异表示主服务器承受着巨大的负载,而sent_location和后备服务器上pg_last_xlog_receive_location之间的差异可能表示网络延迟或者后备服务器正承受着巨大的负载。

2.6. 复制槽

复制槽提供了一种自动化的方法来确保主控机在所有的后备机收到 WAL 段 之前不会移除它们,并且主控机也不会移除可能导致 恢复冲突的行,即使后备机断开也是如此。

作为复制槽的替代,也可以使用wal_keep_segments 阻止移除旧的 WAL 段,或者使用archive_command 把段保存在一个归档中。不过,这些方法常常会导致保留的 WAL 段比需要的 更多,而复制槽只保留已知所需要的段。这些方法的一个优点是它们为 pg_xlog的空间需求提供了界限,但目前使用复制槽无法做到。

类似地,hot_standby和 vacuum_defer_cleanup_age保护了相关行不被 vacuum 移除,但是前者在后备机断开期间无法提供保护,而后者则需要被设置为一个很高 的值已提供足够的保护。复制槽克服了这些缺点。

2.6.1. 查询和操纵复制槽

每个复制槽都有一个名字,名字可以包含小写字母、数字和下划线字符。

已有的复制槽和它们的状态可以在 pg_replication_slots 视图中看到。

槽可以通过流复制协议 或者 SQL 函数创建并且移除。

2.6.2. 配置实例

你可以这样创建一个复制槽:


postgres=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
  slot_name  | xlog_position
-------------+---------------
 node_a_slot |

postgres=# SELECT * FROM pg_replication_slots;
  slot_name  | slot_type | datoid | database | active | xmin | restart_lsn | confirmed_flush_lsn
-------------+-----------+--------+----------+--------+------+-------------+---------------------
 node_a_slot | physical  |        |          | f      |      |             |
(1 row)

要配置后备机使用这个槽,在后备机的recovery.conf中应该配置 primary_slot_name。这里是一个简单的例子:


standby_mode = 'on'
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
primary_slot_name = 'node_a_slot'

2.7. 级联复制

级联复制特性允许一台后备服务器接收复制连接并且把 WAL 记录流式传送给其他后备服务器,就像一个转发器一样。这可以被用来减小对于主控机的直接连接数并且使得站点间的带宽开销最小化。

一台同时扮演着接收者和发送者角色的后备服务器被称为一台级联后备服务器。“更直接”(通过更少的级联后备服务器)连接到主控机的后备服务器被称为上游服务器,而那些离得更远的后备服务器被称为下游服务器。级联复制并没有对下游服务器的数量或布置设定限制。

一台级联后备服务器不仅仅发送从主控机接收到的 WAL 记录,还要发送那些从归档中恢复的记录。因此即使某些上游连接中的复制连接被中断,只要还有新的 WAL 记录可用,下游的流复制都会继续下去。

级联复制目前是异步的。同步复制设置当前对级联复制无影响。

不管在什么样的级联布置中,热备反馈都会向上游传播。

如果一台上游后备服务器被提升为新的主控机,且下游服务器的recovery_target_timeline被设置成'latest',下游服务器将继续从新的主控机得到流。

要使用级联复制,要建立级联后备服务器让它能够接受复制连接(即设置max_wal_senders和hot_standby,并且配置基于主机的认证)。你还将需要设置下游后备服务器中的primary_conninfo指向级联后备服务器。

2.8. 同步复制

PostgreSQL流复制默认是异步的。如果主服务器崩溃,则某些已被提交的事务可能还没有被复制到后备服务器,这会导致数据丢失。数据的丢失量与故障转移时的复制延迟成比例。

同步复制能够保证一个事务的所有修改都能被传送到一台或者多台同步后备服务器。这扩大了由一次事务提交所提供的标准持久化级别。在计算机科学理论中这种保护级别被称为 2-safe 复制。而当synchronous_commit被设置为remote_write时,则是 group-1-safe (group-safe 和 1-safe)。

在请求同步复制时,一个写事务的每次提交将一直等待,直到收到一个确认表明该提交在主服务器和后备服务器上都已经被写入到磁盘上的事务日志中。数据会被丢失的唯一可能性是主服务器和后备服务器在同一时间都崩溃。这可以提供更高级别的持久性,尽管只有系统管理员要关系两台服务器的放置和管理。等待确认提高了用户对于修改不会丢失的信心,但是同时也不必要地增加了对请求事务的响应时间。最小等待时间是在主服务器和后备服务器之间的来回时间。

只读事务和事务回滚不需要等待后备服务器的回复。子事务提交也不需要等待后备服务器的响应,只有顶层提交才需要等待。长时间运行的动作(如数据载入或索引构建)不会等待最后的提交消息。所有两阶段提交动作要求提交等待,包括预备和提交。

2.8.1. 基本配置

一旦流复制已经被配置,配置同步复制就只需要一个额外的配置步骤:synchronous_standby_names必须被设置为一个非空值。synchronous_commit也必须被设置为on,但由于这是默认值,通常不需要改变。这样的配置将导致每一次提交都等待确认消息,以保证后备服务器已经将提交记录写入到持久化存储中。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 记录被传输到当前连接的后备服务器之前,服务器将不会完全关闭。

2.8.2. 多个同步后备

同步复制支持一个或者更多个同步后备服务器,事务将会等待,直到所有同步后备服务器都确认收到了它们的数据为止。事务必须等待其回复的同步后备的数量由synchronous_standby_names指定。这个参数也指定后备服务器的名称列表,它决定了每一个后备服务器被选中为同步后备的优先级。出现在列表中越靠前优先级越高。出现在列表中靠后位置的后备服务器表示潜在的同步后备。如果任何一个当前后备服务器由于任何原因断开连接,它将立刻被下一个具有最高优先级的后备服务器替代。

多个同步后备的synchronous_standby_names示例为:


synchronous_standby_names = '2 (s1, s2, s3)'

在这个例子中,如果有四个后备服务器s1、s2、s3和s4在运行,两个后备服务器s1和s2将被选中为同步后备,因为它们出现在后备服务器名称列表的前部。s3是一个潜在的同步后备,当s1或s2中的任何一个失效, 它就会取而代之。s4则是一个异步后备因为它的名字不在列表中。

2.8.3. 性能规划

同步复制通常要求仔细地规划和放置后备服务器来保证应用能令人满意地工作。等待并不利用系统资源,但是事务锁会持续保持直到传输被确认。其结果是,不小心使用同步复制将由于响应时间增加以及较高的争用率而降低数据库应用的性能。

PostgreSQL允许应用开发者通过复制来指定所要求的持久性级别。这可以为整个系统指定,不过它也能够为特定的用户或连接指定,甚至还可以为单个事务指定。

例如,一个应用的载荷的组成可能是这样:10% 的改变是重要的客户详情,而 90% 的改变是不太重要的数据,即使它们丢失业务也比较容易容忍(例如用户间的聊天消息)。

通过在应用级别(在主服务器上)指定的同步复制选项,我们可以为大部分重要的改变提供同步复制,并且不会拖慢整体的载荷。应用级别选项是使高性能应用享受同步复制的一种重要和实用的工具。

你应该认为网络带宽必须比 WAL 数据的产生率高。

2.8.4. 高可用性规划

当synchronous_commit被设置为on、remote_apply或者remote_write时, synchronous_standby_names指定产生的事务提交要等待其回应的同步后备的数量和名称。如果任一同步后备崩溃,这类事务提交可能无法完成。

高可用的最佳方案是确保有所要求数量的同步后备。这可以通过使用synchronous_standby_names指定多个潜在后备服务器来实现。出现在该列表前部的后备服务器将被用作同步后备。后面的后备服务器将在当前同步后备服务器失效时取而代之。

当一台后备服务器第一次附加到主服务器时,它将处于一种还没有正确同步的状态。这被描述为追赶模式。一旦后备服务器和主服务器之间的迟滞第一次变成零,我们就来到了实时的流式状态。在后备服务器被创建之后的很长一段时间内可能都是追赶模式。如果后备服务器被关闭,则追赶周期将被增加,增加量由后备服务器被关闭的时间长度决定。只有当后备服务器到达流式状态后,它才能成为一台同步后备。

如果在提交正在等待确认时主服务器重启,那些正在等待的事务将在主数据库恢复时被标记为完全提交。没有办法确认所有后备服务器已经收到了在主服务器崩溃时所有还未处理的 WAL 数据。某些事务可能不会在后备服务器上显示为已提交,即使它们在主服务器上显示为已提交。我们提供的保证是:在 WAL 数据已经被所有后备服务器安全地收到之前,应用将不会收到一个事务成功提交的显式确认。

如果实在无法保持所要求数量的同步后备,那么应该减少synchronous_standby_names中指定的事务提交应该等待其回应的同步后备的数量(或者禁用),并且在主服务器上重载配置文件。

如果主服务器与剩下的后备服务器是隔离的,你应当故障转移到那些其他剩余后备服务器中的最佳候选者上。

如果在事务等待时你需要重建一台后备服务器,确保命令 pg_start_backup() 和 pg_stop_backup() 被运行在一个synchronous_commit = off的会话中,否则那些请求将永远等待后备服务器出现。

2.9. 在后备机上连续归档

当在一个后备机上使用连续归档时,有两种不同的情景:WAL 归档在主服务器 和后备机之间共享,或者后备机有自己的 WAL 归档。当后备机拥有其自身的 WAL 归档时,将archive_mode设置为 always,后备机将在收到每个 WAL 段时调用归档命令, 不管它是从归档恢复还是使用流复制恢复。共享归档可以类似地处理,但是 archive_command必须测试要被归档的文件是否 已经存在,以及现有的文件是否有相同的内容。这要求 archive_command中有更多处理,因为它必须当心 不要覆盖具有不同内容的已有文件,但是如果完全相同的文件被归档两次时 应返回成功。并且如果两个服务器尝试同时归档同一个文件,所有这些都必须 在没有竞争情况的前提下完成。

如果archive_mode被设置为on,归档器 在恢复或者后备模式中无法启用。如果后备服务器被提升,它将在被提升后开始 归档,但是它将不会归档任何不是它自身产生的 WAL。要在归档中得到完整的 一系列 WAL 文件,你必须确保所有 WAL 在到达后备机之前都被归档。对于基于 文件的日志传输来说天然就是这样,因为后备机只能恢复在归档中找到的文件, 而启用了流复制时则不是这样。当一台服务器不在恢复模式中时,在 on和always模式之间没有差别。

3. 故障转移

如果主服务器失效,则后备服务器应该开始故障转移过程。

如果后备服务器失效,则不会有故障转移发生。如果后备服务器可以被重启(即使晚一点),由于可重启恢复的优势,那么恢复处理也能被立即重启。如果后备服务器不能被重启,则一个全新的后备服务器实例应该被创建。

如果主服务器失效并且后备服务器成为了新的主服务器,那么接下来旧的主服务器重启后,你必须有一种机制来通知旧的主服务器不再成为主服务器。有些时候这被称为STONITH(Shoot The Other Node In The Head,关闭其他节点),这对于避免出现两个系统都认为它们是主服务器的情况非常必要,那种情况将导致混乱并且最终导致数据丢失。

很多故障转移系统仅使用两个系统,主系统和后备系统,它们由某种心跳机制连接来持续验证两者之间的连接性和主系统的可用性。也可能会使用第三个系统(称为目击者服务器)来防止某些不当故障转移的情况,但是除非非常小心地建立它并且经过了严格地测试,额外的复杂度可能会使该工作得不偿失。

PostgreSQL并不提供在主服务器上标识失败并且通知后备数据库服务器所需的系统软件。现在已有很多这样的工具并且很好地与成功的故障转移所需的操作系统功能整合在一起,例如 IP 地址迁移。

一旦发生到后备服务器的故障转移,就只有单一的一台服务器在操作。这被称为一种退化状态。之前的后备服务器现在是主服务器,但之前的主服务器处于关闭并且可能一直保持关闭。要回到正常的操作,一个后备服务器必须被重建,要么在之前的主系统起来时使用它重建,要么使用第三台(可能是全新的)服务器来重建。在大型集簇上,pg_rewind功能可以被用来加速这个过程。一旦完成,主服务器和后备服务器可以被认为是互换了角色。某些人选择使用第三台服务器来为新的主服务器提供备份,直到新的后备服务器被重建,不过显然这会使得系统配置和操作处理更复杂。

因此,从主服务器切换到后备服务器可以很快,但是要求一些时间来重新准备故障转移集群。从主服务器到后备服务器的常规切换是有用的,因为它允许每个系统有常规的关闭时间来进行维护。这也可以作为一种对故障转移机制的测试,以保证在你需要它时它真地能够工作。我们推荐写一些管理过程来做这些事情。

要触发一台日志传送后备服务器的故障转移,运行pg_ctl promote或者创建一个触发器文件,其文件名和路径由recovery.conf中的trigger_file设置指定。如果你正在规划使用pg_ctl promote进行故障转移,trigger_file就不是必要的。如果你正在建立只用于从主服务器分流只读查询而不是高可用性目的的报告服务器,你不需要提升它。

4. 日志传送的替代方法

前一节描述的内建后备模式的一种替代方案是使用一个轮询归档位置的restore_command。这是版本 8.4 及以下版本中唯一可用的选项。在这种设置中,设置standby_mode为关闭,因为你要自行实现后备操作所需的轮询。关于这种实现的一个参考请见pg_standby模块。

注意在这种模式中,服务器将一次应用一整个文件的 WAL,因此如果你使用后备服务器来查询(见热备),那么主服务器上的一个动作和后备服务器上该动作变得可见之间会有一个延迟,该延迟对应着填满 WAL 文件的时间。archive_timeout可以被用来缩短该延迟。还要注意你不能把流复制和这种方法组合起来使用。

在主服务器和后备服务器上都会发生的操作是通常的连续归档和恢复任务。两个数据库服务器之间唯一的接触点是两者共享的 WAL 文件归档:主服务器写这个归档,后备服务器读取这个归档。必须要小心地保证来自独立主服务器的 WAL 归档不会混合在一起或者混淆。如果归档只被后备操作需要,它不必很大。

使得两台松耦合的服务器一起工作的诀窍是在后备服务器上使用的restore_command,当要求下一个 WAL 文件时,会等待它在主服务器上变得可用。restore_command在后备服务器的recovery.conf文件中指定。正常的恢复处理将从 WAL 归档请求一个文件,如果该文件不可用则会报告失败。对于后备处理来说下一个 WAL 文件不可用很正常,因此后备服务器必须等待它出现。对于以.backup或.history结尾的文件没有必要等待,并且必须返回一个非零的返回码。一个等待的restore_command可以用一种习惯的脚本编写,在其中轮询下一个 WAL 文件的存在之后进行循环。也必须有某种方式来触发故障转移,那将打断restore_command:打破循环并返回一个文件未找到错误给后备服务器。这会结束恢复并且后备服务器将接下来变成一个正常的服务器。

一个合适的restore_command的伪代码是:


triggered = false;
while (!NextWALFileReady() && !triggered)
{
    sleep(100000L);         /* wait for ~0.1 sec */
    if (CheckForExternalTrigger())
        triggered = true;
}
if (!triggered)
        CopyWALFileForRecovery();

在pg_standby模块中提供了一个等待的restore_command的工作例子。它也可被用作如何正确实现上述逻辑的参考。它也可以根据需要被扩展来支持指定的配置和环境。

触发故障转移的方法是规划和设计中的一个重要部分。一种潜在的选项是restore_command命令。它对每一个 WAL 文件被执行一次,但是运行restore_command的进程会为每一个文件创建和死亡,因此没有守护进程或服务器进程,并且也不能使用信号或信号句柄。因此,restore_command不适合于触发故障转移。可以使用一种简单的超时功能,特别是和主服务器上已知的archive_timeout设置一起。但是,由于一个网络问题或者繁忙的主服务器可能足以发起故障转移,这有点容易产生错误。如果可以安排,一种提醒机制(例如显式创建一个触发器文件)是最理想的。

4.1. 实现

使用这种替代方案配置一个后备服务器的简短过程如下所示。对于每一步的细节,可以参考之前的小节。

1.尽可能将主系统和后背系统设置成近乎一致,包括在同一发行级别上的两个相同的PostgreSQL拷贝。

2.在后备服务器上建立从主系统到一个 WAL 归档目录的连续归档。确保在主服务器上archive_mode、archive_command和archive_timeout被恰当地设置。

3.建立主服务器的一个基础备份,并且把该数据载入到后备服务器。

4.在后备服务器上开始从本地 WAL 归档的恢复,在recovery.conf中指定一个按之前所述进行等待的restore_command。

恢复将 WAL 归档当作只读的来处理,因此一旦一个 WAL 文件已经被复制到后备系统,在它正在被后备数据库服务器读取时可以被同时复制到磁带。因此,可以在为了长期灾难恢复目的存储文件的同时运行一个用于高可用性的后备服务器。

为了测试的目的,可以在一个相同的系统上运行主服务器和后备服务器。这对于服务器鲁棒性并不会提供任何有意义的改进,对 HA 也一样。

4.2. 基于记录的日志传送

也可以使用这种替代方法来实现基于记录的日志传送,不过这需要定制开发,并且只有在一整个 WAL 文件被传送之后改变才会对热后备查询可见。

一个外部程序可以调用pg_xlogfile_name_offset()函数来找出 WAL 的当前末端的文件名和其中准确的字节偏移。它接着可以直接访问 WAL 文件并且将从上一个已知的 WAL 末尾到当前末尾之间的数据拷贝到后备服务器。通过这种方法,数据丢失的窗口是复制程序的轮询周期时间,这可以为非常小,并且不会有强制部分使用的段文件被归档所浪费的带宽。注意后备服务器的restore_command脚本只能处理整个 WAL 文件,因此增量复制的数据通常不会对后备服务器可用。只有当主服务器死掉时它才有用 — 那时最后一个部分 WAL 文件会在允许它发生之前被喂给后备服务器。这个处理的正确实现要求restore_command脚本和数据复制程序的合作。

从PostgreSQL 版本 9.0 开始,你可以使用流复制来实现事半功倍的效果。

5. 热备

术语热备用来描述处于归档恢复或后备模式中的服务器连接到服务器并运行只读查询的能力。这有助于复制目的以及以高精度恢复一个备份到一个期望的状态。术语热备也指服务器从恢复转移到正常操作而用户能继续运行查询并且保持其连接打开的能力。

在热备模式中运行查询与正常查询操作相似,尽管如下所述存在一些用法和管理上的区别。

5.1. 用户概览

当hot_standby参数在一台后备服务器上被设置为真时,一旦恢复将系统带到一个一致的状态它将开始接受连接。所有这些连接都被限制为只读,甚至临时表都不能被写入。

后备服务器上的数据需要一些时间从主服务器到达后备服务器,因此在主服务器和后备服务器之间将有一段可以度量的延迟。近乎同时在主服务器和后备服务器上运行相同的查询可能因此而返回不同的结果。我们说后备服务器上的数据与主服务器是最终一致的。一旦一个事务的提交记录在后备服务器上被重播,那个事务所作的修改将对后备服务器上所有新取得的快照可见。快照可以在每个查询或每个事务的开始时取得,这取决于当前的事务隔离级别。

在热备期间开始的事务可能发出下列命令:

  • 查询访问 - SELECT、COPY TO
  • 游标命令 - DECLARE、FETCH、CLOSE
  • 参数 - SHOW、SET、RESET
  • 事务管理命令

    • BEGIN、END、ABORT、START TRANSACTION
    • SAVEPOINT、RELEASE、ROLLBACK TO SAVEPOINT
    • EXCEPTION块或其他内部子事务
  • LOCK TABLE,不过只在下列模式之一中明确发出: ACCESS SHARE、ROW SHARE 或 ROW EXCLUSIVE.
  • 计划和资源 - PREPARE、EXECUTE、 DEALLOCATE、DISCARD
  • 插件和扩展 - LOAD

在热备期间开始的事务将不会被分配一个事务 ID 并且不能被写入到系统的预写式日志。因此,下列动作将产生错误消息:

  • 数据操纵语言(DML) - INSERT、 UPDATE、DELETE、COPY FROM、 TRUNCATE。注意不允许在恢复期间导致一个触发器被执行的动作。这个限制甚至被应用到临时表,因为不分配事务 ID 表行就不能被读或写,而当前不可能在一个热备环境中分配事务 ID。
  • 数据定义语言(DDL) - CREATE、 DROP、ALTER、COMMENT。这个限制甚至被应用到临时表,因为执行这些操作会要求更新系统目录表。
  • SELECT ... FOR SHARE | UPDATE,因为不更新底层数据文件就无法取得行锁。
  • SELECT语句上的能产生 DML 命令的规则。
  • 显式请求一个高于ROW EXCLUSIVE MODE的模式的LOCK。
  • 默认短形式的LOCK,因为它请求ACCESS EXCLUSIVE MODE。
  • 显式设置非只读状态的事务管理命令:

    • BEGIN READ WRITE、 START TRANSACTION READ WRITE
    • SET TRANSACTION READ WRITE、 SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE
    • SET transaction_read_only = off
  • 两阶段提交命令 - PREPARE TRANSACTION、 COMMIT PREPARED、ROLLBACK PREPARED,因为即使只读事务也需要在准备阶段(两阶段提交中的第一个阶段)写 WAL。
  • 序列更新 - nextval()、setval()
  • LISTEN、UNLISTEN、NOTIFY

在正常操作中,"只读"事务被允许更新序列并且使用LISTEN、UNLISTEN和NOTIFY,因此热备会话在比普通只读会话更紧一点的限制下操作。这些限制中的某些可能会在一个未来的发行中被放松。

在热备期间,参数transaction_read_only总是为真并且不可以被改变。但是只要不尝试修改数据库,热备期间的连接工作起来更像其他数据库连接。如果发生故障转移或切换,该数据库将切换到正常处理模式。当服务器改变模式时会话将保持连接。一旦热备结束,它将可以发起读写事务(即使是一个在热备期间启动的会话)。

用户将可以通过发出SHOW transaction_read_only来了解他们的会话是不是只读的。此外,一组函数(Table 9-79)允许用户访问关于后备服务器的信息。这些允许你编写关心数据库当前状态的程序。这些可以被用来监控恢复的进度,或者允许你编写恢复数据库到特定状态的复杂程序。

5.2. 处理查询冲突

主服务器和后备服务器在多方面都松散地连接在一起。主服务器上的动作将在后备服务器上产生效果。结果是在它们之间有潜在的负作用或冲突。最容易理解的冲突是性能:如果在主服务器上发生一次大数据量的载入,那么着将在后备服务器上产生一个相似的 WAL 记录流,因而后备服务器查询可能要竞争系统资源(例如 I/O)。

随着热备发生的还可能有其他类型的冲突。对于可能需要被取消的查询和(某些情况中)解决它们的已断开会话来说,这些冲突是硬冲突。用户可以用几种方式来处理这种冲突。冲突情况包括:

  • 在主服务器上取得了访问排他锁(包括显式LOCK命令和多种DDL动作)与后备查询中的表访问冲突。
  • 在主服务器上删除一个表空间与使用该表空间存储临时工作文件的后备查询冲突。
  • 在主服务器上删除一个数据库与在后备服务器上连接到该数据库的会话冲突。
  • 从 WAL 清除记录的应用与快照仍能"看见"任意要被移除的行的后备事务冲突。
  • 从 WAL 清除记录的应用与在后备服务器*问该目标页的查询冲突,不管要被移除的数据是否为可见。

在主服务器上,这些情况仅仅会导致等待;并且用户可以选择取消这些冲突动作中间的一个。但是,在后备服务器上则没有选择:已被 WAL 记录的动作已经在主服务器上发生,那么后备服务器不能在应用它时失败。此外,允许 WAL 应用无限等待是非常不可取的,因为后备服务器的状态将变得逐渐远远落后于主服务器的状态。因此,提供了一种机制来强制性地取消与要被应用的 WAL 记录冲突的后备查询。

该问题情形的一个例子是主服务器上的一位管理员在一个表上运行DROP TABLE,而该表正在后备服务器上被查询。如果DROP TABLE被应用在后备服务器上,很明显该后备查询不能继续。如果这种情况在主服务器上发生,DROP TABLE将等待直到其他查询结束。但是当DROP TABLE被运行在主服务器上,主服务器没有关于运行在后备服务器上查询的信息,因此它将不会等待任何这样的后备查询。WAL 改变记录在后备查询还在运行时来到后备服务器上,导致一个冲突。后备服务器必须要么延迟 WAL 记录的应用(还有它们之后的任何事情),要么取消冲突查询这样DROP TABLE可以被应用。

当一个冲突查询很短时,我们通常期望能延迟 WAL 应用一小会儿让它完成;但是在 WAL 应用中的一段长的延迟通常是不受欢迎的。因此取消机制有参数,max_standby_archive_delay和max_standby_streaming_delay,它们定义了在 WAL 应用中的最大允许延迟。当应用任何新收到的 WAL 数据花费了超过相关延迟设置值时,冲突查询将被取消。设立两个参数是为了对从一个归档读取 WAL 数据(即来自一个基础备份的初始恢复或者"追赶"一个已经落后很远的后备服务器)和通过流复制读取 WAL数据的两种情况指定不同的延迟值。

在一台后备服务器上这主要是为了该可用性而存在,最好把延迟参数设置得比较短,这样服务器不会由于后备查询导致的延迟落后主服务器太远。但是,如果该后备服务器是位了执行长时间运行的查询,则一个较高甚至无限的延迟值更好。但是记住一个长时间运行的查询延迟了 WAL 记录的应用,它可能导致后备服务器上的其他会话无法看到主服务器上最近的改变。

一旦max_standby_archive_delay或max_standby_streaming_delay指定的延迟被超越,冲突查询将被取消。这通常仅导致一个取消错误,尽管在重放一个DROP DATABASE的情况下整个冲突会话都将被中断。另外,如果冲突发生在一个被空闲事务持有的锁上,该冲突会话会被中断(这种行为可能在未来被改变)。

被取消的查询可能会立即被重试(当然是在开始一个新的事务后)。因为查询取消依赖于 WAL 记录被重放的本质,如果一个被取消的查询被再次执行,它可能会很好地成功完成。

记住延迟参数是从 WAL 数据被后备服务器收到后流逝的时间。因此,留给后备服务器上任何一个查询的宽限期从不会超过延迟参数,并且如果后备服务器已经由于等待之前的查询完成而落后或者因为过重的更新负载而无法跟上主服务器,宽限期可能会更少。

在后备查询和 WAL 重播之间发生冲突的最常见原因是"过早清除"。正常地,PostgreSQL允许在没有事务需要看到旧行版本时对它们进行清除,这样可以保证根据 MVCC 规则的正确的数据可见性。不过,这个规则只能被应用于执行在主控机上的事务。因此有可能主控机上的清除会移除对一个后备服务器事务还可见的行版本。

有经验的用户应当注意行版本清除和行版本冻结都可能与后备查询冲突。即便在一个没有被更新或被删除行的表上运行一次手工VACUUM FREEZE也可能导致冲突。

用户应当清楚,主服务器上被正常和重度更新的表将快速地导致后备服务器上长时间运行的查询被取消。在这样的情况下,max_standby_archive_delay或max_standby_streaming_delay的有限制设置可以被视作statement_timeout设置。

如果发现后备查询取消的数量不可接受,还是有补救的可能。第一种选项是设置参数 hot_standby_feedback,它阻止VACUUM 移除最近死亡的元组并且因此清除冲突不会产生。如果你这样做,你应当 注意这将使主服务器上的死亡元组清除被延迟,这可能会导致不希望发生 的表膨胀。不过,清除的情况不会比在主服务器上直接运行后备查询时更糟, 并且你仍然能够享受将执行分流到后备服务器的好处。如果后备服务器频繁地连接和 断开,你可能想要做些调整来处理无法提供hot_standby_feedback 反馈的时期。例如,考虑增加max_standby_archive_delay,这样 在断开连接的期间查询就不会快速地被 WAL 归档文件中的冲突取消。你也应该考虑 增加max_standby_streaming_delay来避免重新连接后新到达的流 WAL 项导致的快速取消。

另一个选项是增加主服务器上的vacuum_defer_cleanup_age,这样死亡行不会像平常那么快地被清理。这将允许在后备服务器上的查询能在被取消前有更多时间执行,并且不需要设置一个很高的max_standby_streaming_delay。但是,这种方法很难保证任何指定的执行时间窗口,因为vacuum_defer_cleanup_age是用主服务器上被执行的事务数来衡量的。

查询取消的数量和原因可以使用后备服务器上的pg_stat_database_conflicts系统视图查看。pg_stat_database系统视图也包含汇总信息。

5.3. 管理员概览

如果hot_standby在postgresql.conf中被设置为on并且存在一个recovery.conf文件,服务器将运行在热备模式。但是,可能需要一些时间来允许热备连接,因为在服务器完成足够的恢复来为查询提供一个一致的状态之前,它将不会接受连接。在此期间,尝试连接的客户端将被一个错误消息拒绝。要确认服务器已经可连接,要么循环地从应用尝试连接,要么在服务器日志中查找这些消息:


LOG:  entering standby mode

... then some time later ...

LOG:  consistent recovery state reached
LOG:  database system is ready to accept read only connections

在主服务器上,一旦创建一个检查点,一致性信息就被记录下来。当读取在特定时段(当在主服务器上wal_level没有被设置为replica或者logical的期间)产生的 WAL 时无法启用热备。在同时存在这些条件时,到达一个一致状态也会被延迟:

  • 一个写事务有超过 64 个子事务
  • 生存时间非常长的写事务

如果你正在运行基于文件的日志传送(“温备”),你可能需要等到下一个 WAL 文件到达,这可能和主服务器上的archive_timeout设置一样长。

如果后备服务器上某些参数在主服务器上已经被改变,它们的设置将需要重新配置。对这些参数,在后备服务器上的值必须等于或者大于主服务器上的值。如果这些参数没有被设置得足够高,后备服务器将拒绝开始。较高的值被提供之后,服务器重新启动再次开始恢复。这些参数是:

  • max_connections
  • max_prepared_transactions
  • max_locks_per_transaction
  • max_worker_processes

管理员为max_standby_archive_delay和max_standby_streaming_delay选择适当的设置很重要。最好的选择取决于业务的优先级。例如如果服务器主要的任务是作为高可用服务器,那么你将想要低延迟设置,甚至是零(尽管它是一个非常激进的设置)。如果后备服务器的任务是作为一个用于决策支持查询的额外服务器,那么将其最大延迟值设置为很多小时甚至 -1 (表示无限等待)可能都是可以接受的。

在主服务器上写出的事务状态 "hint bits" 是不被 WAL 记录的,因此后备服务器上的数据将可能重新写出该提示。这样,即使所有用户都是只读的,后备服务器仍将执行磁盘写操作;但数据值本身并没有发生改变。用户将仍写出大的排序临时文件并且重新生成 relcache 信息文件,这样在热备模式中数据库没有哪个部分是真正只读的。还要注意使用dblink模块写到远程数据库以及其他使用 PL 函数位于数据库之外的操作仍将可用,即使该事务是本地只读的。

在恢复模式期间,下列类型的管理命令是不被接受的:

  • 数据定义语言(DDL) - 如CREATE INDEX
  • 特权和所有权 - GRANT、REVOKE、REASSIGN
  • 维护命令 - ANALYZE、VACUUM、CLUSTER、REINDEX

注意这些命令中的某些实际上在主服务器上的“只读”模式事务期间是被允许的。

结果是,你无法创建只存在于后备服务器上的额外索引以及统计信息。如果需要这些管理命令,它们应该在主服务器上被执行,并且最后那些改变将被传播到后备服务器。

pg_cancel_backend()和pg_terminate_backend()将在用户后端上工作,而不是执行恢复的 Startup 进程。pg_stat_activity不会为 Startup 进程显示一个项,也不会把恢复事务显示为活动。结果是在恢复期间pg_prepared_xacts总是为空。如果你希望解决不能确定的预备事务,查看主服务器上的pg_prepared_xacts并且发出命令来确定那里的事务。

和平常一样,pg_locks将显示被后端持有的锁。pg_locks也会显示一个由 Startup 进程管理的虚拟事务,它拥有被恢复重播的事务所持有的所有AccessExclusiveLocks。注意 Startup 进程不请求锁来做数据库更改,并且因此对于 Startup 进程除AccessExclusiveLocks之外的锁不显示在pg_locks中,它们仅被假定存在。

Nagios的插件check_pgsql将可以工作,因为它检查的简单信息是存在的。check_postgres监控脚本也将能工作,尽管某些被报告的值可能给出不同或者混乱的结果。例如,上一次清理时间将不会被维护,因为在后备服务器上不会发生清理。在主服务器上运行的清理仍会把它们的改变发送给后备服务器。

WAL 文件控制命令在恢复期间将不会工作,如pg_start_backup、pg_switch_xlog等。

可动态载入的模块可以工作,包括pg_stat_statements。

咨询锁在恢复期间工作正常,包括死锁检测。注意咨询锁从来都不会被 WAL 记录,因此在主服务器或后备服务器上一个咨询锁不可能会与 WAL 重播发生冲突。也不可能会在主服务器上获得一个咨询锁并且在后备服务器上开始一个相似的咨询锁。咨询锁只与它们被取得的那个服务器相关。

基于触发器的复制系统(如Slony、Londiste和Bucardo)将根本不会运行在后备服务器上,然而只要改变不被发送到要被应用的后备服务器,它们将在主服务器上运行得很好。WAL 重播不是基于触发器的,因此你不能用后备服务器接替任何需要额外数据库写操作或依赖触发器使用的系统。

新的 OID 不能被分配,然而某些UUID生成器仍然能工作,只要它们不依赖于向数据库写新的状态。

当前,在只读事务期间不允许创建临时表,因此在某些情况中现有的脚本将不会正确运行。这个限制可能会在稍后的发行中被放松。这既是一个 SQL 标准符合问题也是一个技术问题。

只有在表空间为空时DROP TABLESPACE才能成功。某些后备服务器用户可能正在通过他们的temp_tablespaces参数使用该表空间。如果在该表空间中有临时文件,所有活动查询将被取消来保证临时文件被移除,这样该表空间可以被移除并且 WAL 重播可以继续。

在主服务器上运行DROP DATABASE或ALTER DATABASE ... SET TABLESPACE将产生一个 WAL 项,它将导致所有连接到后备服务器上那个数据库的用户被强制地断开连接。这个动作会立即发生,不管max_standby_streaming_delay的设置是什么。注意ALTER DATABASE ... RENAME不会断开用户,这在大部分情况中不会有提示,然而如果它依赖某种基于数据库名的方法,在某些情况中会导致程序混乱。

在普通(非恢复)模式中,如果你为具有登录能力的角色发出DROP USER或DROP ROLE,而该用户仍然连接着,则对已连接用户不会发生任何事情 - 他们保持连接。但是用户不能重新连接。这种行为也适用于恢复,因此在主服务器上的一次DROP USER不会使后备服务器上的用户断开。

在恢复期间统计收集器是活动的。所有扫描、读、阻塞、索引使用等将在后备服务器上被正常的记录。被重播的动作将不会重复它们在主服务器上的效果,因此重播一个插入将不会导致pg_stat_user_tables的 Inserts 列上的递增。在恢复的开始 stats 文件会被删除,因此来自主服务器和后备服务器的 stats 将不同;这被认为是一种特性而不是缺陷。

在恢复期间自动清理不是活动的。它将在恢复末尾正常启动。

后台写入器在恢复期间是活动的并且将执行检查点(与主服务器上的检查点相似)以及正常的块清洁活动。这可以包括存储在后备服务器上的提示位信息的更新。在恢复期间,CHECKPOINT命令会被接受,然而它会执行一个重启点而不是一个新的检查点。

5.4. 热备参数参考

在主服务器上,可以使用参数wal_level和vacuum_defer_cleanup_age。在主服务器上设置max_standby_archive_delay和max_standby_streaming_delay不会产生效果。

在主服务器上,可以使用参数hot_standby、max_standby_archive_delay和max_standby_streaming_delay。只要服务器保持在后备模式vacuum_defer_cleanup_age就没有效果,然而当后备服务器变成主服务器时它将变得相关。

5.5. 警告

热备有一些限制。这些限制很可能在未来的发行中被修复:

  • 哈希索引上的操作目前是不被 WAL 记录的,因此重播将不会更新这些索引。
  • 在能够取得快照之前,需要正在运行的事务的完整知识。使用大量子事务(目前指超过 64 个)的事务将延迟只读连接的启动,直到最长的运行着的写事务完成。如果发生这种情况,说明消息将被发送到服务器日志。
  • 主服务器上的每一个检查点将产生用于后备查询的可用启动点。如果后备服务器在主控机处于关闭状态时被关闭,就没有办法在主服务器启动之前重新进入热后备,因此它在 WAL 日志中产生一个进一步启动点。这种情况在它可能发生的大部分常见情况中不是一个问题。通常,如果主服务器被关闭并且不再可用,这可能是由于某种严重错误要求后备服务器被转变成为一个新的主服务器来操作。并且在主服务器被故意关闭的情况下,协调保证后备服务器平滑地过渡为新的主服务器也是一种标准过程。
  • 在恢复尾声,由预备事务持有的AccessExclusiveLocks将要求两倍的正常锁表项。如果你计划运行大量并发的通常要求AccessExclusiveLocks的预备事务,或者你计划运行一个需要很多AccessExclusiveLocks的大型事务,我们建议你为max_locks_per_transaction选择一个更大的值,也许是主服务器上该参数值的两倍。如果你的max_prepared_transactions设置为 0,你根本不需要考虑这个问题。
  • 可序列化事务隔离级别目前在热备中不可用。尝试在热备模式中将一个事务设置为可序列化隔离级别将产生一个错误。
上一篇:蓝牙技术助力推动物联网应用发展


下一篇:【Kotlin 反应式编程】第1讲 你好,Reactive Programming