暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Postgresql Standby数据库专题

1.Standby数据库原理

Standby数据库可类比于其他数据库产品的从库。

数据库内部记录的是数据, 当多台数据库同时提供服务时, 不会像Web服务器那么简单, 因为Web服务器
是无状态的, 而数据库是有状态的, 主备数据库之间存在着数据同步, 通常是一台主数据库提供读写, 然后把数据同步到另一台备数据库, 这台备数据库不断应用(apply) 从主数据库发来的变化数据。
这台备数据库服务器不能提供写服务, 通常最多提供只读服务。

在9.0版本之前,备份数据库在接收主数据库同步数据和应用同步数据时不能提供只读服务, 该备份数据库称为Warm Standby Server,在主库宕机后可以提升为新主进而提供服务。
在此后的版本推出了Hot Standby功能,允许Standby数据库可读,进而允许提供负载均衡服务。

1.1 PITR原理

PITR即“Point-in-Time Recovery”,基于时间点的恢复。
当有WAL日志之后, 备份数据库不再需要一个完美的一致性备份, 备份中的任何非一致性数据都会被重放最后一次Checkpoint点之后的WAL日志文件纠正, 所以我们可以通过类似拷贝文件的操作,来实现数据库的在线备份,之后不停地重放WAL日志就可以把数据库推到备份结束后的任意一个时间点。

使用简单的cp命令或其他命令把数据库在线复制出来的备份, 称为基础备份, 从基础备份操作开始之后产生的WAL日志和此基础备份构成了一个完整的备份。

把基础备份恢复到另一台机器, 然后不停地从原始数据库机器上接收WAL日志, 在新机器上持续重放WAL日志, 只要应用WAL日志足够快, 该备数据库就会追上主数据库的变化, 拥有当前主数据库的最新数据状态。
这个新机器上的数据库被称为Standby数据库。
当主数据库出现问题无法正常提供服务时, 可以把Standby数据库打开提供服务, 从而实现高可用。

把WAL日志传送到另一台机器上的方法有两种, 一种是通过WAL归档日志方法; 另一种是PostgreSQL 9.X版本开始提供的被称为流复制的方法。

1.2 WAL日志归档

WAL日志归档, 本质就是把在线的已写完的WAL日志复制到其他目录中去。
在PostgreSQL中配置归档的方法是在配置文件“postgresql.conf”中配置参数“archive_mode”和“archive_command”, archive_command的配置值是一个UNIX命令, 此命令把WAL日志文档复制到其他地方, 示例如下:

archive_mode = on
archive_command = 'cp %p /backup/pgarch/%f'

其中“archive_mode=on”表示打开归档备份;
参数“archive_command”的配置值是一UNIX的cp命令,
命令中的“%p”表示在线WAL日志文件的全路径名,
“%f”表示不包括路径的WAL日志文件名。
在实际执行备份时, PostgreSQL会把“%p”替换成实际的在线WAL日志文件的全路径名, 并把“%f”替换成不包括路径的WAL日志名。

也可以使用操作系统命令scp把WAL日志复制到其他机器上, 从而实现跨机器的归档日志备份, 如:

archive_mode = on
archive_command = 'scp %p postgres@192.168.1.100:/backup/pgarch/%f'

使用上面复制WAL文件的方式来同步主、 备数据库之间的数据, 会导致备库落后主库一个WAL日志文件, 具体落后多长时间取决于主库上生成一个完整的WAL文件所需要的时间。

1.3 流复制

流复制是PostgreSQL从9.0版本开始提供的一种新的传递WAL日志的方法。
使用流复制时, Primary数据库的WAL日志一产生, 就会马上传递到Standby数据库。

流复制传递日志的方式有两种, 一种是异步方式; 另一种是同步方式。

  • 使用同步方式, 则在Primary数据库提交事务时, 一定会等到WAL日志传递到Standby数据库后才会返回, 这样可以做到Standby数据库接收到的WAL日志完全与Primary数据库同步, 没有一点落后, 当主备库切
    换时使用同步方式可以做到零数据丢失。
  • 异步方式, 则是事务提交后不必等日志传递到Standby数据库就即可返回, 所以Standby数据库通常比Primary数据库落后一定的时间, 落后时间的多少取决于网络延迟和备库的I/O能力。

1.4 Standby数据库的运行原理

当PostgreSQL数据库异常中止后, 数据库刚重启时, 会重放停机前最后一个Checkpoint点之后的WAL日志, 把数据库恢复到停机时的状态, 恢复完成后自动进入正常的状态, 可以接收其他用户的查询和修改。
想象另一个场景: 如果A机器上的数据库停止后, 把A机器上的数据库整个复制到另一台机器B上, 在机器B上启动这个数据库时, 机器B上的数据库也将做与A机器上数据库重启时相同的事, 即重放停止之前最后一个Checkpoint点之后的WAL日志, 把数据库推到停机时的状态。
正常的数据库完成恢复后会自动进入正常状态, 如果有办法让该数据库不自动进入正常状态, 而是一直等待新的WAL日志, 如果有新的WAL日志来则自动进行重放, 直到主库失败后, 再让B机器上的数据库进入正常
状态, 这样B机器上的数据库就成了一个Standby数据库, 实现了当A机器上的数据库失败后, B机器上的数据库能立即接管的功能。

在PostgreSQL9.0之前的版本中没有流复制的功能, 基本上只能一个个地传送WAL日志文件(除非使用第三方的软件) , 所以备库最少比主库落后一个WAL日志文件, 在出现故障后, 使用Standby数据库接管数据库服务, 丢失的数据会比较多。
PostgreSQL 9.0版本后提供了流复制功能, 当主库产生一点日志后就会马上传送到备库, 从而一般只丢失最
多几秒的数据。
PostgreSQL 9.1中, 流复制的功能得到了进一步的提升, 提供了同步复制的功能, 这样主备切换后, 就不存在数据丢失的问题。

有人就会问, 如果同步复制, 当备库出现问题后, 会不会导致主库也会被hang住?
通常会导致这个问题, 但PostgreSQL提供了多个Standby数据库的功能, 如配置两个Standby数据库, 当一个Standby数据库损坏时,主数据库不会被hang住, 两个备数据库都出现问题时才会导致主数据库不能写。

PostgreSQL 9.2版本开始, 增加级连复制的功能, 也就是一个Standby数据库后面可以再级连另一个Standby数据库, 也就是说, 其他Standby数据库不必都从主数据库上拉取WAL日志, 可以从其他Standby数据库拉取WAL日志。

流复制协议不仅能传递WAL日志, 也能传递数据文件, 后面介绍的pg_basebackup工具就是通过流复制协议把远程主库的所有数据文件传输到本地的。

PostgreSQL数据库是通过在数据目录下建一个特殊的文件来指示数据库启动在主库模式还是在备库模式, 在PostgreSQL 12版本之前是通过文件“recovery.conf”来指示数据库启动在备库模式的(当然需要在recovery.conf中配置一些合适的内容才可以) , 从PostgreSQL 12版本开始把recovery.conf中的配置项全部移到postgresql.conf配置文件中, 不再使用recovery.conf文件。
当然为了指示该数据库是备库, 还需要在数据目录下建一个名为“standby.signal”的空文件。

如果我们在postgresql.conf中配置了“hot_standby”为“on”, 说明备库是“Hot Standby”,即可以只读的;如果配置“hot_standby”为“off”, 说明备库是“Warm Standby”, psql是无法连接这个备库的, 连接时会报如下错误:

psql: FATAL: the database system is starting up

1.5 建Standby数据库的步骤

对于PostgreSQL 12版本的数据库, 只需要在数据库的数据目录下建standby.signal文件, 然后重新启动数据库, 数据库就会进入Standby模式下。
当然由于PostgreSQL 12版本中postgresql.conf的参数“hot_standby”是打开的, 该数据库是只读的。

对于PostgreSQL 12版本之前的数据库, 如PostgreSQL 11版本, 需要创建一个recovery.conf文件, 并在文件中设置如下内容:

standby_mode = 'on'

※ 当我们把文件standby.signal(如果是PostgreSQL 12之前的版本数据库是recovery.conf) 删除, 再重启数据库, 数据库就变回主库了。

当然上面的步骤只是把主库转换成了备库, 变成了只读库, 并没有新建一个备库, 通常我们需要新建一个只读备库, 并从主库进行WAL日志的同步, 最简单的方法是把主数据库停下来, 把主数据库的数据目录原封不动地复制到备机, 在备机数据库的数据目录下建一个指示这个库是备库的文件(如果是PostgreSQL 12及以上版本是standby.signal文件,如果是PostgreSQL 12之前的版本是recovery.conf文件) , 然后在指定的配置文件(如果是在PostgreSQL 12及以上版本是postgresql.conf文件, 如果是PostgreSQL 12之前的版本是recovery.conf文件) 中配置如何连接主库的流复制, 然后启动备库就完成了Standby备库的搭建。
上面这种通过冷备库的方式搭建备库的方式需要停止主库, 如果数据库比较大, 会有比较长的停库时间, 这时会不方便, 所以PostgreSQL也提供了热备份的方式搭建Standby备库, 即在主库不停机, 也不终止正常读写的情况下, 就可以在线搭建Standby备库。

热备份的方式建Standby备库的过程可分为以下两个大步骤:

  • 第一步: 通过在线热备份的方式生成一个基础备份, 并把生成的基础备份传到备机上;
  • 第二步: 在备库上配置相关配置文件后, 把备库启动在Standby模式下, 这样就完成了Standby库的搭建。
    该步骤与冷备份搭建Standby备库的过程基本相同。

通过热备份的方式生成基础备份的方法有以下两种:

  • 第一种是通过底层API的方式一步一步地完成。
  • 第二种是通过pg_basebackup工具一键完成。

底层API的方式可以让我们更深入地了解热备份的原理, 同时复制数据文件时可以使用更灵活的方式, 如并发运行几个scp同时复制不同的数据文件, 这样对于比较大的数据库可以更快地完成备库的搭建; 使用pg_basebackup工具可以做到一键完成备库的搭建,这样会更方便。
pg_basebackup实际上是底层API的包装, 帮助我们更方便地搭建Standby备库, 此后将仔细讲解pg_basebackup工具的使用方法。

我们先介绍用底层API的方式搭建备库的过程和步骤:

  • 以数据库超级用户身份连接到数据库, 发出命令“SELECT pg_start_backup(‘label’)”。
  • 执行备份: 使用任何方便的文件系统工具, 比如tar或cp直接把数据目录复制下来。操作过程中既不需要关闭数据库, 也不需要停止对数据库的任何写操作。
  • 再次以数据库超级用户身份连接数据库, 然后发出命令“SELECT pg_stop_backup()”。 这将中止备份模式并自动切换到下一个WAL段。 设置自动切换是为了在备份间隔中写入的最后一个WAL段文件可以立即为下次备份做好准备。
  • 把备份过程中产生的WAL日志文件也复制到备机上。

在上面的步骤中, 有人可能会问, 为什么热备份数据库前需要执行pg_start_backup()?
实际上, pg_start_backup()主要做了以下两项工作:

  • 置写日志标志位: XLogCtl->Insert.forcePageWrites=true, 也就是把这个标志设置为“t
    rue”后, 数据库会把变化的整个数据块都记录到数据库中, 而不仅仅是块中记录的变化。
  • 强制发生一次Checkpoint。

为什么要强制WAL日志把整个块都写入WAL中呢?
想象一下: 如果用cp命令复制文件时, 数据库可能同时写这个文件, 那么可能会出现一个数据块, 数据库正在写, cp命令正在读, 这样有可能复制的数据块的前半部分是旧数据, 后半部分是新数据, 也就是单个
数据块的数据不一致, 这时, 如果后面使用WAL日志把数据推到一个一致点时, WAL日志中只记录块中行的变化, 那么这种不一致的数据块就无法恢复, 但如果WAL日志中记录的是整个新数据块的内容, 那么重演WAL日志时, 用整个新块的内容覆盖数据块后,就不会存在不一致的数据块了。
强制发生一次Checkpoint, 也是为了把前面的脏数据都刷到磁盘中, 这样之后产生的日志都记录了整个数据块, 这可以保证恢复的正确性。

Standby数据库一直运行在恢复状态, 如何让数据库运行在恢复状态呢?
在PostgreSQL中是通过配置recovery.conf文件来实现的。
在数据库启动过程中, 如果发现数据目录($PGDATA环境变量指向的目录) 下存在recovery.conf, 就会按recovery.conf文件中指示的情况把数据库启动到恢复状态。
后面会详细介绍recovery.conf的配置方法。

2.pg_basebackup命令行工具

2.1 pg_basebackup概述

pg_basebackup是从9.1版本开始提供的一个方便基础备份的工具。
pg_basebackup工具把整个数据库实例的数据都物理地复制出来, 而不是也不能只把数据库实例中的部分内容如某些表单独备份出来。

该工具使用流复制的协议连接到主数据库上, 所以主数据库中的pg_hba.conf必须允许replication连接, 也就是在pg_hba.conf中必须有如下形式的内容:

local replication osdba trust
local replication osdba ident
host replication osdba 0.0.0.0/0 md5

上例中第二列的数据库名填写的是“replication”, 这并不是表示连接到名为“replication”的数据库上, 而是表示允许这些客户端机器发起流复制连接。

理论上, 一个数据库可以被几个pg_basebackup同时连接, 但为了不影响主库的性能, 建议最好还是一个数据库上同时只有一个pg_basebackup在它上面做备份。
PostgreSQL9.2之后支持级连复制, 所以在9.2及以上的版本中pg_basebackup也可以从另一个Standby库上做基础备份, 但从Standby备份时需要注意以下事项:

  • 从Stamdby备份时不会创建备份历史文件(backup history file, 即类似0000000100001234000055CD.007C9330.backup的文件) 。
  • 不确保所有需要的WAL文件都备份了, 如果想确保, 需要加命令行参数“-X stream”。
  • 在备份过程中, 如果Standby被提升为主库, 则备份会失败。
  • 要求主库中打开full_page_writes参数, WAL文件不能被类似pg_compresslog的工具去掉full-page writes信息。

2.2 pg_basebackup的命令行参数

pg_basebackup命令的使用方法如下:

pg_basebackup [option...]

此命令后可以跟多个选项, 各选项的具体说明如下:

  • -D directory或–pgdata=directory: 指定备份的目标目录, 即备份到哪儿。
    如果这个目录或目录路径中的各级父目录不存在, pg_basebackup就会自动创建该目录。
    如果目录存在, 但不为空, 则会导致pg_basebackup执行失败。
    如果备份的输出是tar结果(指定-F tar, 后面会介绍此选项) , 而-D参数后的目录名写成“-”(中划线) , 则备份会输出到标准输出, 此项功能是为了方便通过管道与其他工具配合使用。
  • -F format或–format=format: 指定输出的格式。
    目前支持两种格式, 第一种格式是原样输出, 即把主数据库中的各个数据文件、 配置文件、 目录结构都完全一样地写到备份目录中, 这种情况下“format”指定为“p”或“plain”; 第二种格式是tar格式, 相当于把输出的备份文件打包到一个tar文件中, 这种情况下“format”应为“t”或“tar”。
  • -r, --max-rate=RATE: 限速参数, 热备份会在主库产生较多的I/O和网络开销, 可以用该参数限制速率。 速率的默认单位是“kB/s”, 当然也可以指定单位“k”或“M”。
  • -R, --write-recovery-conf: 是否生成recovery.conf文件。
  • -x或–xlog: 备份时会把备份中主库产生的WAL文件也自动备份出来, 这样在恢复数据库时, 做出的备份才能应用这些WAL文件把数据库推到一个一致点, 然后才能打开备份的数据库。
    该选项与下面的选项“-X fetch”是完全一样的。
    使用该选项需要设置wal_keep_segments参数, 以保证在备份过程中需要的WAL日志文件不会被覆盖。
    注意, 该参数在PostgreSQL 10版本之后废弃, 请用“-X fetch”替代。
  • -X method或–xlog-method=method: method可以取的值为“f”“fetch”“s”“stream”, “f”与“fetch”相同, 其含义与“-x”参数是一样的。
    “s”与“stream”表示的含义相同, 均表示备份开始后, 启动另一个流复制连接从主库接收WAL日志。
    这种方式避免了使用“-X f”时, 主库上的WAL日志有可能被覆盖而导致失败的问题。
    但这种方式需要与主库建两个连接,因此使用这种方式时, 主库的max_wal_senders参数要设置为大于或等于2的值。
  • -z或–gzip: 仅能与tar输出模式配合使用, 表明输出的tar备份包是经过gzip压缩的,相当于生成了一个*.tar.gz的备份包。
  • -Z level或–compress=level: 指定gzip的压缩级别, 可以选1~9的数字, 与gzip命令中的压缩级别的含义是一样的, 9表示最高压缩率, 但也最耗CPU。
  • -c fast|spread或–checkpoint=fast|spread: 设置Checkpoint的模式是fast还是spread。
  • -l label或–label=label: 指定备份的一个标识, 备份的标识是一个任意字符串, 便于今后维护人员识别该备份, 该标识就是手动做基础备份时运行“select pg_start_backup(‘lable’)”传递给pg_start_backup函数的参数。
    在备份集中有一个文件叫“backup_label”, 这里面除了记录开始备份时起始的WAL日志的开始位置、 Checkpoint的WAL日志位置、 备份的开始时间, 也记录了该标识串的信息。
  • -P或–progress: 允许在备份过程中实时地打印备份的进度。
    当然, 所打印的进度不是百分之百精确的, 因为在备份过程中, 数据库的数据还会发生变化, 还会不断产生一些WAL日志。
  • -v或–verbose: 详细模式, 如当使用了-P参数时, 还会打印出正在备份哪个具体文件的信息。
  • -V或–version: 打印pg_basebackup的版本后退出。
  • -?或–help: 显示帮助信息后退出。

下面是控制连接数据库的参数的说明:

  • -h host或–host=host: 指定连接的数据库的主机名或IP地址。
  • -p port或–port=port: 指定连接的端口。
  • -s interval或–status-interval=interval: 指定向服务器端周期反馈状态的秒数, 如果服务器上配置了流复制的超时, 当使用–xlog=stream选项时需要设置该参数, 默认值为10秒。 如果设置为“0”, 表示不向服务器反馈状态。
  • -U username或–username=username: 指定连接的用户名。
  • -w或–no-password: 指定从来不提示输入密码。
  • -W或–password: 强制让pg_basebackup出现输入密码的提示。

2.3 pg_basebackup使用示例

示例一: 在数据库机器上执行如下命令

pg_basebackup -D backup -Ft -z -P

上面命令的执行过程如下:

[postgres@pg01 ~]$ pg_basebackup -D backup -Ft -z -P
25318/25318 kB (100%), 1/1 tablespace

上面的pg_basebackup没有指定任何连接参数, 所以它就如psql命令中没有指定连接参数的方式一样连接到本地的数据库上。
因为用“-Ft -z”指定了tar和压缩模式, 所以在backup目录下生成了如下文件:

[postgres@pg01 ~]$ ls -l backup
total 2996
-rw------- 1 postgres postgres 3046647 Feb 9 16:39 base.tar.gz
-rw------- 1 postgres postgres 17073 Feb 9 16:39 pg_wal.tar.gz

上例中, 如果把base.tar.gz压缩文件解压, 其中的backup_label文件的内容如下:

START WAL LOCATION: 0/7000028 (file 000000010000000000000007)
CHECKPOINT LOCATION: 0/7000060
BACKUP METHOD: streamed
BACKUP FROM: master
START TIME: 2020-02-09 16:44:51 CST
LABEL: pg_basebackup base backup
START TIMELINE: 1

从上面的内容可以看出, 如果不指定备份label, pg_basebackup工具生成的label为“pg_basebackup base backup”。

示例二: 自另一台机器上执行如下命令

pg_basebackup -h 10.0.3.101 -U postgres -F p -P -X stream -R -D $PGDATA -l osdbabackup201912151010

这是一个跨机器备份示例, 上面的命令是把10.0.3.101上的数据库备份到本地, 使用的连接用户名为“postgres”, 输出格式为普通原样输出“-F p”, “-P”参数表示在执行过程中输出备份的进度, “-X stream”参数表示把在备份过程中产生的xlog文件也备份出来, “-R”参数表示在备份中会生standby.signal文件, 并把连接主库的信息放到postgresql.auto.conf中, 如果是PostgreSQL 12版本之前会生成配置文件“recovery.conf”, 当用此备份启动备库时, 只需要简单修改recovery.conf就可以把数据库启动到备库模式。
“-D”参数指定了备份文件都生成到环境变量“$PGDATA”指向的目录下, “-l”参数指定了备份的标识串为“osdbabackup201912151010”。

3.异步流复制Hot Standby的示例

3.1 配置环境

环境见表:

主机名 IP地址 角色 数据目录
pg01 10.0.3.101 主库 /home/postgres/pg12data
pg02 10.0.3.102 Standby /home/postgres/pg12data

数据库是在操作用户“postgres”下, .bash_profile中配置的环境变量如下:

export PGDATA=~/pg12data
export PATH=/usr/pgsql-12/bin:$PATH
export LD_LIBRARY_PATH=/usr/pgsql-12/lib:$LD_LIBRARY_PATH
export PGHOST=/tmp
export LANG=en_US.UTF-8

当前数据库的版本是PostgreSQL 12。

3.2 主数据库的配置

要使用流复制, 需要允许主库接受流复制的连接, 这就需要在/home/postgres/pg12data/pg_hba.conf中做如下配置:

host replication all 0/0 md5

上面这条SQL语句的含义是允许任意用户从任何网络(0/0) 网络上发起到本数据库的流复制连接, 使用MD5的密码认证。 用户“postgres”是该演示环境上的超级用户, 当然, 换成一个有流复制权限的用户也可以。
要想搭建流复制, 需要在主库“pg01”的/home/postgres/pg12data/postgresql.conf中设置以下几个参数:

listen_addresses = '*'
max_wal_senders = 10
wal_level = replica

注意:一定要把max_wal_senders参数设置成一个大于零的值, 在这里设置为“10”,同时需要把wal_level参数设置为“replica”或“logical”。
对上面两个参数的修改都是需要重启数据库的, 所以在实际生产中第一次建生产库时, 最好先把这两个参数设置成上面的值。
另外, min_wal_size参数的默认值为“80MB”, 该值通常太小, 很容易导致备库失效,也需要设置得大一些:

min_wal_size = 800MB

3.3 在Standby上生成基础备份

做完以上准备工作后, 就可以使用pg_basebackup命令行工具在pg02机器上生成基础备份了, 命令如下:

pg_basebackup -h 10.0.3.101 -U postgres -F p -P -X stream -R -D $PGDATA -l osdbabackup201912151110

执行情况如下:

[postgres@pg02 ~]$ pg_basebackup -h 10.0.3.101 -U postgres -F p -P -X stream -R -D $PGDATA -l osdbabackup
201912151110
Password:
25318/25318 kB (100%), 1/1 tablespace

执行完上面的命令后, 就可在pg02机器上的/home/postgres/pg12data目录下看到复制过来的各种数据文件及配置文件, 命令如下:

[postgres@pg02 ~]$ ls -l $PGDATA
total 60
-rw------- 1 postgres postgres 222 Feb 9 16:31 backup_label
drwx------ 5 postgres postgres 41 Feb 9 16:31 base
-rw------- 1 postgres postgres 30 Feb 9 16:31 current_logfiles
drwx------ 2 postgres postgres 4096 Feb 9 16:31 global
drwx------ 2 postgres postgres 32 Feb 9 16:31 log
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_commit_ts
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_dynshmem
-rw------- 1 postgres postgres 4724 Feb 9 16:31 pg_hba.conf
-rw------- 1 postgres postgres 1636 Feb 9 16:31 pg_ident.conf
drwx------ 4 postgres postgres 68 Feb 9 16:31 pg_logical
drwx------ 4 postgres postgres 36 Feb 9 16:31 pg_multixact
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_notify
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_replslot
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_serial
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_snapshots
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_stat
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_stat_tmp
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_subtrans
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_tblspc
drwx------ 2 postgres postgres 6 Feb 9 16:31 pg_twophase
-rw------- 1 postgres postgres 3 Feb 9 16:31 PG_VERSION
drwx------ 3 postgres postgres 60 Feb 9 16:31 pg_wal
drwx------ 2 postgres postgres 18 Feb 9 16:31 pg_xact
-rw------- 1 postgres postgres 262 Feb 9 16:31 postgresql.auto.conf
-rw------- 1 postgres postgres 26780 Feb 9 16:31 postgresql.conf
-rw------- 1 postgres postgres 0 Feb 9 16:31 standby.signal

因为使用pg_basebackup命令时使用了“-R”参数, 所以也会生成standby.signal文件, 同时在postgresql.auto.conf中生成如下内容:

primary_conninfo = 'user=postgres password=postgres host=10.0.3.101 port=5432 sslmode=prefer sslcompression=0 gssencmode=prefer krbsrvname=postgres target_session_attrs=any'

如果没有加“-R”参数, 我们也可以手动添加上面的内容。

如果是PostgreSQL 12版本之前的数据库, 使用了“-R”参数会生成recovery.conf文件:

standby_mode = 'on'
primary_conninfo = 'user=postgres password=XXXXXX host=10.0.3.101 port=5432 sslmode=disable sslcompression=1'

3.4 启动Standby数据库

在pg02机器上启动Standby数据库之前, 检查/home/postgres/pg12data/postgresql.conf中的参数“hot_standby”是否为“on”, 设置该参数是为了让备库是Hot Standby, 即可以对外提供只读服务。
当然该参数在较新版本的PostgreSQL中默认已经被设置成“on”。
然后启动Standby数据库, 就可自动进入Hot Standby状态, 这时可以连接到Hot Standby上。
命令如下:

[postgres@pg02 ~]$ pg_ctl start
waiting for server to start....
done
server started

如果备库连接到主库上, 在主库“pg01”的pg_stat_replication视图中, 就可以看到从备库过来的流复制连接:

postgres=# select client_addr,state,sync_state from pg_stat_replication;
client_addr | state | sync_state
-------------+-----------+------------
10.0.3.102 | streaming | async
(1 row)

如果看不到备库过来的连接, 说明备库没有连过来, 需要检查备库的日志文件查看原因。
如果看到的流复制状态“state”的值不是“streaming”, 也说明备库的流复制有问题。

如果备库的日志中出现如下错误信息:

< 2019-12-10 15:35:42.987 CST > FATAL: could not connect to the primary server: FATAL: password authent
ication failed for user "osdba"

这有可能是配置参数“primary_conninfo”中连接主数据库的用户名或密码不正确, 可以修改此配置参数后, 重启数据库重试。

如果日志文件中的错误信息如下:

< 2019-12-10 15:47:36.879 CST > FATAL: could not connect to the primary server: FATAL: no pg_hba.conf e
ntry for replication connection from host "10.0.3.102", user "postgres", SSL off

这通常是主库上的pg_hba.conf文件中缺失了允许流复制连接的配置, 示例如下:

host replication all 0/0 md5

上面这个配置项没有正确地配置到主库的pg_hba.conf文件中, 或者配置错误。
修改主库上的pg_hba.conf, 然后在主库上“pg_ctl reload”重新装载新的配置, 问题就会解决。

主备库之间的同步正常后, 我们测试一下数据同步, 在主库上建一个测试表, 然后插入几条数据,在备库上查看, 可以发现数据马上就同步过来了。
因为Hot Standby是只读的, 所以如果在Standby上做修改, 会操作失败:

ERROR: cannot execute DELETE in a read-only transaction

3.5 交换主备库的角色

对于Oracle数据库的DBA来说, 切换Oracle数据库的主备库之间的角色的过程叫“switchover”, Oracle提供了相应的“switchover”的一些较复杂的命令和过程。
对于PostgreSQL数据库来说, 切换操作的步骤比较简单:

  • 先停主库, 再停备库。
  • 在原主库的数据目录中建文件“standby.signal”(如果是PostgreSQL 12之前的版本是“recovery.conf”) , 并配置连接新主库的流复制参数。
  • 把原备库数据目录下的文件“standby.signal”(如果是PostgreSQL 12之前的版本是“recovery.conf”) 重命名或直接删除。
  • 启动原备库, 这时该备库变成了主库。
  • 启动原主库, 这时该主库变成了备库。

我们现在演示这个切换过程。
在主库(10.0.3.101) 上执行如下命令:

[postgres@pg01 ~]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

在备库(10.0.3.102) 上执行如下命令:

[postgres@pg02 ~]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

在主库(10.0.3.101) 上建文件“standby.signal”:

touch $PGDATA/standby.signal

并在postgresql.conf中添加如下内容:

primary_conninfo = 'user=postgres password=XXXXXX host=10.0.3.101 port=5432 sslmode=prefer sslcompression
=0

如果是PostgreSQL 12之前的版本, 需要创建recovery.conf文件, 内容如下:

standby_mode = 'on'
primary_conninfo = 'user=osdba password=XXXXXX host=10.0.3.102 port=5432 sslmode=disable sslcompression=1

把备库(10.0.3.102) 上的standby.signal删除:

[postgres@pg02 ~]$ rm $PGDATA/standby.signal

如果是PostgreSQL 12之前的版本, 需要重命名recovery.conf文件:

[postgres@pg02 ~]$ cd $PGDATA
[postgres@pg02 ~]$ mv recovery.conf recovery.done

在10.0.3.102上启动数据库, 该数据库就从备库变成了主库:

[postgres@pg02 ~]$ pg_ctl start

在psql下查看该数据库是否是主库, 我们发现它已经是主库了:

postgres=# select pg_is_in_recovery();
pg_is_in_recovery
-------------------
f
(1 row)

在原主库(10.0.3.101) 上启动数据库, 该数据库就从主库变成了备库。
这时我们在新主库上查询视图“pg_stat_replication”, 可以看到新备库过来的连接:

postgres=# select client_addr,state,sync_state from pg_stat_replication;
 client_addr |   state   | sync_state
-------------+-----------+------------
  10.0.3.101 | streaming | async
(1 row)

当然我们还可以重复上面的步骤再次交换主备库的角色。

3.6 故障切换

通常故障切换称为“Failover”。
异步复制时, 如果主库出现了问题, 可以激活备库作为主库提供服务。
在PostgreSQL9.1版本之前是在recovery.conf中配置一个trigger文件, 当备库检测到该文件时, 就自动把自己激活成主库, PostgreSQL9.1版本之后提供了命令“pg_ctl promote”来激活备库, 所以现在很少有人再以配置trigger文件的方式激活备库了。

我们演示一下trigger文件激活的方式, 先在postgresql.conf中进行如下配置:

promote_trigger_file = '/tmp/pg_trigger'

如果是PostgreSQL 12之前的版本, 应该在recovery.conf中配置trigger文件, 命令如下:

standby_mode = 'on'
trigger_file = '/tmp/pg_trigger'
primary_conninfo = 'user=osdba password=XXXXXX host=10.0.3.102 port=5432 sslmode=disable sslcompression=1'

在另一个窗口中监控此备库的日志:

tail -f postgresql-Fri.log

然后执行下面的命令:

touch /tmp/pg_trigger

然后就可以在前面监控日志的窗口中看到此备库被激活的日志:

< 2019-12-07 17:33:15.768 CST > LOG: trigger file found: /tmp/pg_trigger
2019-12-09 17:38:00.822 CST [4475] LOG: promote trigger file found: /tmp/pg_trigger
2019-12-09 17:38:00.822 CST [4479] FATAL: terminating walreceiver process due to administrator command
2019-12-09 17:38:00.831 CST [4475] LOG: redo done at 0/E0000A0
2019-12-09 17:38:00.832 CST [4475] LOG: selected new timeline ID: 2
2019-12-09 17:38:00.890 CST [4475] LOG: archive recovery complete
2019-12-09 17:38:00.902 CST [4473] LOG: database system is ready to accept connections

我们用psql连接到数据库中, 可以看到数据库已经变成了主库状态:

postgres=# select pg_is_in_recovery();
pg_is_in_recovery
-------------------
f
(1 row)

原主库出现问题后, 通常这些故障并没有导致数据丢失, 如宕机、 机器重启的故障。

当故障解决之后, 通常我们会把原主库转换成新主库的Standby备库, 该转换一般来说需要重新搭建备库。
这是因为原主库的一些数据没有同步过去就把备库激活了, 备库相当于丢失了一些数据。
而重新搭建备库的话, 如果数据库很大, 基础备份会执行很长时间, 为了解决这个问题, 从PostgreSQL 9.5版本开始提供pg_rewind命令, 不需要复制太多的数据就可以把原主库转换成新主库的备库。
该命令相当于把原主库的数据“回滚”到新主库激活时的状态, 当然这里所说的“回滚”不是真的“回滚”, 只是为了让我们更好地理解pg_rewind的作用。

使用pg_rewind命令要求主库必须把参数“wal_log_hints”设置成“on”或主库在建数据库实例时打开了checksum, 这样配置的主库在出现故障时才能使用pg_rewind命令。
当然这样做之后, 数据库会产生更多的WAL日志, 所以数据库默认是没有打开checksum参数的。
数据库实例打开checksum参数的方法是, 在用initdb命令初始化数据库实例时使用“-k”或“–data-checksums”参数。

如果我们没有把参数“wal_log_hints”或“checksum”打开, 运行pg_rewind时会报错:

[postgres@pg02 ~]$ pg_rewind -D $PGDATA --source-server='host=10.0.3.102 user=postgres password=XXXXX'
target server needs to use either data checksums or "wal_log_hints = on"
Failure, exiting

接下来演示pg_rewind的使用方法。
主库在10.0.3.101上, 备库在10.0.3.102上。
这时主库出问题了, 如机器宕机了(我们用强制关机来模拟) , 我们现在激活备库,即在10.0.3.102上执行如下命令:

[postgres@pg02 ~]$ pg_ctl promote
server promoting

现在10.0.3.102库变成了主库。
然后把原主库开机, 在10.0.3.101上执行pg_rewind命令:

[postgres@pg01 ~]$ pg_rewind -D $PGDATA --source-server='host=10.0.3.102 user=postgres password=postgres'
pg_rewind: fatal: target server must be shut down cleanly

我们发现pg_rewind报错, 这时就需要把这个库启动一下, 然后再正常关闭。

[postgres@pg01 ~]$ pg_ctl start
pg_ctl: another server might be running; trying to start server anyway
waiting for server to start....
done
server started
[postgres@pg01 ~]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

然后再执行pg_rewind:

[postgres@pg01 ~]$ pg_rewind -D $PGDATA --source-server='host=10.0.3.102 user=postgres password=postgres'
pg_rewind: servers diverged at WAL location 0/18DCD08 on timeline 1
pg_rewind: rewinding from last common checkpoint at 0/18C1DE0 on timeline 1
pg_rewind: Done!

注意, 上面的“-D”参数指向本地的目录。

pg_rewind执行完之后, 需要手动建文件“standby.signal”:

touch $PGDATA/standby.signal

并在postgresql.conf中添加如下内容:

primary_conninfo = 'user=postgres password=XXXXXX host=10.0.3.102 port=5432 sslmode=prefer sslcompression=0'

这样原主库才能变成新主库的备库。
如果是PostgreSQL 12版本之前的数据库, 需要手动创建recovery.conf文件, 内容如下:

standby_mode = 'on'
recovery_target_timeline = 'latest'
primary_conninfo = 'user=osdba password=XXXXXX host=10.0.3.102 port=5432 sslmode=disable sslcompression=1'

注意, 上面的命令中比之前多了一行“recovery_target_timeline=‘latest’”, 这时因为新主库的时间线与原主库的不一样了, 加上这一行命令才能让原主库切换到新主库的时间线上。

这时再在10.0.3.101上启动数据库, 原主库就变成了新主库的备库:

[postgres@pg01 ~]$ pg_ctl start
waiting for server to start....
done
server started

注意上面的操作, 一定要先建好standby.signal(或是recovery.conf) , 再启动数据库, 否则启动了数据库就会进入主库模式。
如果这样做了, 需要把数据库停下来, 重新运行pg_rewind命令。

4.同步流复制的Standby数据库

4.1 同步流复制的架构

PostgreSQL异步流复制的缺点是当主库损坏的时候, 激活备库后会丢失一些数据, 这对于一些不允许丢失数据的应用来说是不可接受的, 所以PostgreSQL从9.1版本开始提供同步流复制的功能, 解决了主备库切换时丢失数据的问题。
同步复制要求WAL日志写入Standby数据库后commit才能返回, 所以Standby库出现问题时, 会导致主库被hang住。
解决这个问题的方法是启动两个Standby数据库, 这两个Standby数据库只要有一个是正常运行的就不会让主库hang住。
所以在实际应用中, 同步流复制, 总是有一个主库和两个以上的Standby备库。

即使是同步复制, 如果因主库发生临时故障激活了其中一个备库, 要想把原主库转换成新主库的备库, 仍然需要用pg_rewind处理一下才行, 这是因为虽然是同步复制, 但并不是把主库的WAL日志完全同步地传输到备库, 同步只是到事务提交时才保证其已经传输到了备库, 一些未提交事务的WAL日志可能还没有传输到备库, 因此激活备库时, 还是会丢失一些WAL日志。
当然对于用户来说, 未提交事务的WAL日志丢失, 并不会导致用户数据的丢失。

4.2 同步复制的配置

同步复制的配置主要是在主库上配置参数“synchronous_standby_names”, 该参数指定多个Standby的名称, 各个名称用逗号分隔, 而Standby名称是在Standby连接到主库时由连接参数“application_name”指定的。
要使用同步复制, 在Standby数据库中primary_conninfo参数一定要指定连接参数“application_name”。 primary_conninfo参数的配置示例如下:

primary_conninfo = 'application_name=standby01 user=postgres password=XXXXXX host=10.0.3.101 port=5432 sslmode=disable sslcompression=1'

注意, 在PostgreSQL 12之前的版本中, primary_conninfo配置参数是在recovery.conf文件中的, 而PostgreSQL 12及以上版本中转移到了postgresql.conf配置文件中。

在PostgreSQL 9.6版本之前, 只允许有一个同步的Standby备库, 即“synchronous_standby_names”参数的配置值只有一种格式:

standby_name [, ...]

例如, 我们配置了“synchronous_standby_names=‘s1,s2,s3’”, 虽然配置了多个备库s1、 s2、 s3, 但只有第一个备库s1是同步的, 其他均是潜在的同步备库, 即只要WAL日志传递到第一个备库s1, 事务commit就可以返回了, 当第一个备库s1出现问题时, 第二个备库s2才会提升为同步备库。

从PostgreSQL 9.6版本开始, 可以设置多个同步的备库, 配置格式如下:

num_sync ( standby_name [, ...] )

其中“num_sync”是一个数字, 如“synchronous_standby_names='2 (s1,s2,s3)”表示, WAL日志必须传到前两个备库“s1”和“s2”, 事务commit才可以返回。
所以之前版本中的配置“s1,s2,s3”相当于“1(s1,s2,s3)”备。

从PostgreSQL 10版本开始, 可以设置基于quorum的方式设置备库, 新增的格式如下:

ANY num_sync ( standby_name [, ...] )

例如, 我们配置“synchronous_standby_names=‘ANY 2 (s1,s2,s3)’”时, 只要WAL日志传到了任意两个备库, 事务commit就可以返回了。

影响同步复制的还有一个参数“synchronous_commit”, 该参数可以取的值有以下几个:

  • remote_apply: WAL日志被传到备库并被apply, 事务commit才返回。
  • on: WAL日志被传到备库并被持久化( 不必等其被apply) , 事务commit才返回。
  • remote_write: WAL日志被传到备库的内存中( 不必等其被持久化) , 事务commit才返回。
  • local: WAL日志被本地持久化后( 不用管远程) 事务commit就可以返回。
  • off: 不必等WAL日志被本地持久化, 也不管是否传到远程, 事务commit都可以立即返回。

由上面说明即可联想到同步复制, synchronous_commit的可选值为“on”“remote_apply”“remote_write”。

4.3 同步复制配置示例

同步流复制的Hot Standby的示例环境见表:

主机名 IP地址 角色 数据目录
pg01 10.0.3.101 主库 /home/postgres/pg12data
pg02 10.0.3.102 Standby /home/postgres/pg12data
pg03 10.0.3.103 Standby /home/postgres/pg12data
pg04 10.0.3.104 Standby /home/postgres/pg12data

同步流复制的Hot Standby的示例环境配置步骤如下。
第一步: 主库“pg01”上的配置。
与异步的流复制一样, 在主库的/home/osdba/pgdata/pg_hba.conf中做如下配置:

host replication osdba 10.0.3.0/24 md5

在主库“db01”的/home/osdba/pgdata/postgresql.conf中设置如下两个参数:

max_wal_senders = 10
wal_level = hot_standby

在主数据库上指定同步复制的Standby备库的名称, 在pg01的/home/osdba/pgdata/postgresql.conf中增加如下命令行:

synchronous_standby_names = 'standby102,standby103,standby104'

上例中“standby102,standby103”就是在Standby数据库中配置连接参数“application_name”时指定的名称。

第二步: 备库“pg02”上的配置。
在pg02上的配置项“primary_conninfo”中增加连接参数“application_name”, 命令如下:

primary_conninfo = 'application_name=standby102 user=postgres password=XXXXXX host=10.0.3.101 port=5432 sslmode=disable sslcompression=1'

配置完成后, 启动数据库, 命令如下:

[postgres@pg02 pg12data]$ pg_ctl start
waiting for server to start....
done
server started

第三步: 备库“pg03”上的配置。
同样, 在pg03上的配置项“primary_conninfo”中增加连接参数“application_name”, 命令如下:

standby_mode = 'on'
primary_conninfo = 'application_name=standby103 user=postgres password=XXXXXX host=10.0.3.101 port=5432 s
slmode=disable sslcompression=1'

配置完成后, 启动数据库, 命令如下:

[postgres@pg03 pg12data]$ pg_ctl start
waiting for server to start....
done
server started

第四步: 备库“pg04”上的配置。
同样, 在pg04上的配置项“primary_conninfo”中增加连接参数“application_name”, 命令如下:

standby_mode = 'on'
primary_conninfo = 'application_name=standby104 user=postgres password=XXXXXX host=10.0.3.101 port=5432 s
slmode=disable sslcompression=1'

配置完成后, 启动数据库, 命令如下:

[postgres@pg04 pg12data]$ pg_ctl start
waiting for server to start....
done
server started

第五步: 在主库上启动同步复制。
在主库上修改参数“synchronous_standby_names”并不需要重启主库, 只需要重新装载配置即可。
运行如下命令重新装载新的配置:

pg_ctl reload -D /home/osdba/pgdata

然后查看同步的状态:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby102 | 10.0.3.102 | streaming | 1 | sync
standby103 | 10.0.3.103 | streaming | 2 | potential
standby104 | 10.0.3.104 | streaming | 3 | potential
(3 rows)

可以看到备库“standby102”的同步状态是“sync”, 其他备库的状态都是“potential”, 表示是潜在的同步库。

测试同步复制(配置了一个同步节点的情况)
先关掉一台Standby库(pg02) , 看主库是否能正常工作, 命令如下:

[postgres@pg02 log]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

到主库“pg01”上查看同步状态:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby103 | 10.0.3.103 | streaming | 2 | sync
standby104 | 10.0.3.104 | streaming | 3 | potential
(2 rows)

可以看到10.0.3.103变成了同步库, 10.0.3.104还处于“potential”状态。

然后在主库上进行如下操作:

postgres=# insert into test01 values(1,'1111');
INSERT 0 1

从上面的结果中可以看到, 当一台Standby备库损坏时, 主库是不受影响的。
再关掉一台Standby库(pg03) , 看主库是否能正常工作, 命令如下:

osdba@db03:~$ pgstop
waiting for server to shut down.... done
server stopped

在主库中插入一条记录:

postgres=# insert into test01 values(2,'2222');
INSERT 0 1

我们看到此时主库中还可以插入数据。

我们把最后一台Standby库(pg04) 停掉:

[postgres@pg04 ~]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

在主库中插入一条记录:

postgres=# insert into test01 values(3,'3333');

我们会发现主库被hang住了, 然后我们另外开一个窗口, 对主库做只读查询:

postgres=# select * from test01;
id | t
----+------
1 | 1111
2 | 2222
(2 rows)

我们发现对主库的非更新查询都是可以正常执行的, 这时再启动一台Standby库(pg04) , 命令如下:

[postgres@pg04 ~]$ pg_ctl start
waiting for server to start....
done
server started

我们会发现主库之前hang住的插入操作可以继续执行了, 命令如下:

postgres=# insert into test01 values(3,'3333');
INSERT 0 1

查看主库的同步状态, 命令如下:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby104 | 10.0.3.104 | streaming | 3 | sync
(1 row)

我们把备库“10.0.3.103”打开, 命令如下:

[postgres@pg03 ~]$ pg_ctl start
waiting for server to start....
done
server started

查看主库的同步状态, 命令如下:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby104 | 10.0.3.104 | streaming | 3 | potential
standby103 | 10.0.3.103 | streaming | 2 | sync
(2 rows)

这时我们发现10.0.3.104的同步状态从原先的“sync”变成了“potential”, 而新启动的备库“10.0.3.103”的同步状态变成了“sync”。

最后我们把备库“10.0.3.102”打开, 命令如下:

[postgres@pg02 ~]$ pg_ctl start
waiting for server to start....
done
server started

查看主库的同步状态, 命令如下:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby104 | 10.0.3.104 | streaming | 3 | potential
standby103 | 10.0.3.103 | streaming | 2 | potential
standby102 | 10.0.3.102 | streaming | 1 | sync
(3 rows)

此时我们发现10.0.3.103的同步状态从原先的“sync”变成了“potential”, 而新启动的备库“10.0.3.102”的同步状态变成了“sync”。
从上面的示例中可以看出, 同步的优先级是以各个备库在synchronous_standby_names中的配置顺序决定的。

配置两个同步节点的情况
我们将主库的synchronous_standby_names进行如下配置:

synchronous_standby_names = '2(standby102,standby103,standby104)'

然后reload让其生效, 命令如下:

[postgres@pg01 pg12data]$ pg_ctl reload
server signaled

查看同步状态, 命令如下:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby104 | 10.0.3.104 | streaming | 3 | potential
standby103 | 10.0.3.103 | streaming | 2 | sync
standby102 | 10.0.3.102 | streaming | 1 | sync
(3 rows)

这时可以看出10.0.3.102和10.0.3.103的同步状态都是“sync”了。
这时把10.0.3.102上的备库关掉, 命令如下:

[postgres@pg02 ~]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

在主库上插入一条记录, 我们发现此时还是可以插入的:

postgres=# insert into test01 values(4,'4444');
INSERT 0 1

这时我们再停掉一个备库(10.0.3.103) 。
在主库上插入一条记录, 此时主库就hang了:

postgres=# insert into test01 values(5,'5555');

这时我们再把10.0.3.103上的备库打开, 命令如下:

[postgres@pg03 ~]$ pg_ctl start
waiting for server to start....
done
server started

发现原先在主库hang的SQL命令可以正常执行了。
这就说明目前这个配置必须有两个正常的同步备库, 主库才可以做更新操作。
我们把最后一个备库也启动, 命令如下:

[postgres@pg02 ~]$ pg_ctl start
waiting for server to start....
done
server started

配置quorum模式的两个同步节点的情况
我们将主库的synchronous_standby_names进行如下配置:

synchronous_standby_names = 'ANY 2(standby102,standby103,standby104)'

然后reload让其生效, 命令如下:

[postgres@pg01 pg12data]$ pg_ctl reload
server signaled

查看同步状态, 命令如下:

postgres=# select application_name,client_addr,state, sync_priority, sync_state from pg_stat_replication;
application_name | client_addr | state | sync_priority | sync_state
------------------+-------------+-----------+---------------+------------
standby104 | 10.0.3.104 | streaming | 1 | quorum
standby103 | 10.0.3.103 | streaming | 1 | quorum
standby102 | 10.0.3.102 | streaming | 1 | quorum
(3 rows)

此时我们发现现在各个节点的优先级都是一样的, 同步状态全部变成了“quorum”。
如果我们停掉其中任意一个备库, 还有两个正常工作的备库时, 主库还是可以做数据更新的, 但如果再停掉一个备库, 主库的更新操作就会hang住, 这里不再赘述。

5.检查备库及流复制情况

5.1 检查异步流复制的情况

查看流复制的信息可以使用主库上的视图“pg_stat_replication”, 如果流复制是异步的, 查询视图“pg_stat_replication”看到的信息如下:

postgres=# select pid,state,client_addr, sync_priority,sync_state from pg_stat_replication;
pid | state | client_addr | sync_priority | sync_state
-----+-----------+-------------+---------------+------------
650 | streaming | 10.0.3.102 | 0 | async
614 | streaming | 10.0.3.103 | 0 | async
(2 rows)

从上面的运行结果中可以看到sync_state字段显示的信息为“async”。

另外, pg_stat_replication视图中的以下几个字段记录了一些WAL日志的位置。

  • sent_lsn: 发送WAL的位置。
  • write_lsn: 可以认为是备库已经接收到了这部分日志, 但还没有刷到磁盘中。
  • flush_lsn: 备库已经把WAL日志刷到磁盘中的位置。
  • replay_lsn: 备库应用日志的位置。

查看备库落后主库多少字节的WAL日志, 可以使用如下SQL命令:

postgres=# select pg_wal_lsn_diff(pg_current_wal_lsn (),replay_lsn) from pg_stat_replication;
pg_wal_lsn_diff
----------------
11815016
(1 row)

上面的SQL语句中, 使用pg_current_wal_lsn ()获得当前主库的WAL日志的位置, replay_location为当前备库应用WAL日志的位置, 再使用函数“pg_wal_lsn_diff”就可以算出差异的字节数, 注意, 上面示例中算出的结果的单位是字节。

注意, 在PostgreSQL10及以上版本中与WAL日志有关的函数的名称有所改变, 名称中的“xlog”改成了“wal”, “location”改成了“lsn”, pg_stat_replication中列名也有类似的变化:

  • “pg_xlog_location_diff”改成了“pg_wal_lsn_diff”。
  • “pg_current_xlog_location”改成了“pg_current_wal_lsn”。
  • “replay_location”改成了“replay_lsn”。

所以如果是PostgreSQL 10之前的版本, 我们应该用如下SQL语句查询:

select pg_xlog_location_diff(pg_current_xlog_location(),replay_location) from pg_stat_replication;

PostgreSQL 10及以上版本在pg_stat_replication中还提供了以下3个落后时间的字段。

  • write_lag: 备库已接收到的日志目前落后主库的时间间隔。
  • flush_lag: 备库持久化的日志目前落后主库的时间间隔。
  • replay_lag: 备库已经应用过的日志目前落后主库的时间间隔。
    这几个参数都是“时间间隔(interval) ”的类型。

5.2 检查同步流复制的情况

同步流复制的环境中, 在主库查询pg_stat_replication可以看到如下信息:

postgres=# select pid,state,client_addr, sync_priority,sync_state from pg_stat_replication;
pid | state | client_addr | sync_priority | sync_state
-----+-----------+-------------+---------------+------------
599 | streaming | 10.0.3.102 | 1 | sync
614 | streaming | 10.0.3.103 | 2 | potential
(2 rows)

可以看到pg02的优先级是“1”, pg03的优先级是“2”, 这个优先级是由synchronous_standby_names参数配置中的顺序决定的。
目前主数据库与pg02处于同步“sync”, 而pg03的状态为“potential”, 表示它是一个潜在的同步Standby备库, 当pg02损坏时, pg03会切换到同步状态, 这时关掉pg02, 可看到如下内容:

postgres=# select pid,state,client_addr, sync_priority,sync_state from pg_stat_replication;
pid | state | client_addr | sync_priority | sync_state
-----+-----------+-------------+---------------+------------
614 | streaming | 10.0.3.103 | 2 | sync
(1 row)

再次启动pg02, 此时查看同步情况如下:

postgres=# select pid,state,client_addr, sync_priority,sync_state from pg_stat_replication;
pid | state | client_addr | sync_priority | sync_state
-----+-----------+-------------+---------------+------------
650 | streaming | 10.0.3.102 | 1 | sync
614 | streaming | 10.0.3.103 | 2 | potential
(2 rows)

从中可以发现pg03又从“sync”状态变成了“potential”状态, pg02重新变成了“同步状态”。

5.3 pg_stat_replication视图详解

列名称 类型 说明
pid integer 数据库上 WAL Sender 进程的进程 ID
usesysid oid 登录主库的流复制用户的 OID
usename name 登录主库的流复制用户的名称
application_name text 流复制连接中连接参数 “application_name” 中指定的字符串
client_addr inet Standby 的 IP 地址
client_hostname text Standby 的主机名。注意,只有在配置文件中打开了 log_hostnam配置项和同时使用了 IP 连接时,这列才会显示主机名,否则显示为空
client_port integer 流复制连接中 Standby 端的 socket 端口
backend_start timestamp with time zone WAL Sender 进程启动的时间。实际也是 Standby 连接过来的时间,因为只有 Standby 连接过来时才会启动一个 WAL Sender 进程,连接中断后 WAL Sender 进程也会停止
state text WAL Sender 进程的状态
sent_location 或 sent_lsn text 或 pg_lsn 流复制连接上发送 WAL 时的发送位置,注意 PostgreSQL 10 及以上版本中改成了 “sent_lsn”,类型改成了 “pg_lsn”,下面的 3 个字段也做了类似调整
write_location/write_lsn text/pg_lsn Standby 端写 WAL 日志的位置
flush_location/write_lsn text/pg_lsn Standby 端写 WAL 日志刷新到磁盘的位置
replay_location/replay_lsn text/pg_lsn Standby 端重放 WAL 日志的位置
write_lag interval PostgreSQL 10 版本之后才有的字段,表示写的延迟间隔
flush_lag interval PostgreSQL 10 版本之后才有的字段,表示刷新到磁盘的延迟间隔
replay_lag interval PostgreSQL 10 版本之后才有的字段,表示应用的延迟间隔
sync_priority integer 同步复制时不同 Standby 的优先级,对于异步复制,此字段的值总是 “0”
sync_state text 同步的状态,可以为 “sync”“potential”“async”

可以在主库上把WAL位置转换成WAL文件名和偏移量, 命令如下:

postgres=# SELECT * FROM pg_walfile_name_offset('0/5F8862F0');
file_name | file_offset
--------------------------+-------------
00000001000000000000005F | 8938224
(1 row)

从上面的示例中我们注意到PostgreSQL10版本之前函数“pg_walfile_name_offset”的名称是“pg_xlogfile_name_offset”。

5.4 查看备库的状态

前面讲解了在主库上通过查看pg_stat_replication视图获得备库流复制状态的方法, 在备库上也可以通过查看视图“pg_stat_wal_receiver”来查看流复制的状态:

postgres=# \x
Expanded display is on.
postgres=# select * from pg_stat_wal_receiver;
-[ RECORD 1 ]---------+---------------------------------------------------------
pid | 2484
status | streaming
receive_start_lsn | 0/1000000
receive_start_tli | 3
received_lsn | 0/157EA78
received_tli | 3
last_msg_send_time | 2019-02-08 01:12:15.275259+08
last_msg_receipt_time | 2019-02-08 01:12:15.27815+08
latest_end_lsn | 0/157EA78
latest_end_time | 2019-02-08 01:10:45.172582+08
slot_name |
conninfo | user=postgres password=******** dbname=replication host=
10.0.3.102 port=5432 fallback_application_name=walreceiver sslmode=disable sslcompression=1 krbsrvname=po
stgres

从上面的示例中可以看出, 这个视图实际上是显示备库上WAL接收进程的状态, 其
中的主要字段的说明如下:

  • pid: WAL接收进程的PID。
  • status: 状态, 只有“streaming”是正常状态。
  • receive_start_lsn: WAL接收进程启动时使用的第一个WAL日志的位置。
  • receive_start_tli: WAL接收进程启动时使用的第一个时间线编号。
  • received_lsn: 已经接收到并且已经被写入磁盘的最后一个WAL日志的位置。
  • received_tli: 已经接收到并且已经被写入磁盘的最后一个WAL日志的时间线编号。
  • last_msg_send_time: 接收到最后一条WAL日志消息后, 向主库发回确认消息的发送
    时间。
  • last_msg_receipt_time: 备库接收到最后一条WAL日志消息的接收时间。
  • latest_end_lsn: 报告给主库最后一个WAL日志的位置。
  • latest_end_time: 报告给主库最后一个WAL日志的时间。
  • slot_name: 使用的复制槽的名称。
  • conninfo: 连接主库的连接串, 密码等安全相关的信息会被隐去。

如何判断数据库处于备库的状态?
如果数据库处于Hot Standby状态, 可以连接到数据库中执行pg_is_in_recovery()函数, 如果是在主库上, 此函数返回的值是“False”, 如果是在备库上, 返回的值是“True”。

在主库上执行pg_is_in_recovery()函数的示例如下:

postgres=# select pg_is_in_recovery();
pg_is_in_recovery
-------------------
f
(1 row)

在备库上执行pg_is_in_recovery()函数的示例如下:

postgres=# select pg_is_in_recovery();
pg_is_in_recovery
-------------------
t
(1 row)

如果备库不是Hot Standby状态, 不能直接连接上去, 这时可以使用命令行工具“pg_controldata”来进行判断, 在主库上看到“Database cluster state”为“in production”, 命令如下:

postgres@pg01:~$ pg_controldata
pg_control version number: 937
Catalog version number: 201306121
Database system identifier: 5980482081191294407
Database cluster state: in production
pg_control last modified: Sat 08 Mar 2014 07:54:23 PM CST
....
....

在备库上看到“Database cluster state”为“in archive recovery”, 命令如下:

postgres@pg02:~$ pg_controldata
pg_control version number: 937
Catalog version number: 201306121
Database system identifier: 5980482081191294407
Database cluster state: in archive recovery
pg_control last modified: Sat 08 Mar 2014 08:00:05 PM CST
....
....

在Hot Standby备库上, 还可以执行如下函数查看备库接收的WAL日志和应用WAL日
志的状态:

  • pg_last_wal_receive_lsn (), PostgreSQL 10之前为pg_last_xlog_receive_location()。
  • pg_last_wal_replay_lsn (), PostgreSQL10之前为pg_last_xlog_replay_location()。
  • pg_last_xact_replay_timestamp()。

应用示例如下:

postgres=# set timezone = 8;
SET
postgres=# osdba=# select pg_last_wal_receive_lsn(),pg_last_wal_replay_lsn(),pg_last_xact_replay_timestam
p();
pg_last_wal_receive_lsn | pg_last_wal_replay_lsn | pg_last_xact_replay_timestamp
-------------------------+------------------------+-------------------------------
0/17000000 | 0/16FFFD80 | 2019-12-07 22:51:08.434698+08
(1 row)

6.Hot Standby的限制

6.1 Hot Standby的查询限制

DML语句(如INSERT、 UPDATE、 DELETE、 COPY FROM、 TRUNCATE等) 和DDL(如CREATE、 DROP、 ALTER、 COMMENT等) 都不能在Hot Standby备库上执行, 这很好理解。
另外, “SELECT…FOR SHARE|UPDATE”语句在Hot Standby备库中也不能执行, 因为在PostgreSQL中, 行锁是要更新数据行的。
如果在Hot Standby备库执行上述SQL语句, 会报如下错误:

postgres=# select * from test01 for update;
ERROR: cannot execute SELECT FOR UPDATE in a read-only transaction

虽然在Hot Standby备库中行锁不能使用, 但部分类型的表锁是可以使用的, 但要注意, 这部分表锁需要在BEGIN启动的事务块中使用, 直接使用会报错, 示例如下:

postgres=# LOCK TABLE test01 in ACCESS SHARE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in ROW SHARE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in ROW EXCLUSIVE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in SHARE UPDATE EXCLUSIVE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in SHARE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in SHARE ROW EXCLUSIVE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in EXCLUSIVE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# LOCK TABLE test01 in ACCESS EXCLUSIVE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=#

但在使用BEGIN命令启动的事务块中使用, 则不会报错, 示例如下:

postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in ACCESS SHARE MODE;
LOCK TABLE
postgres=# END;
COMMIT
postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in ROW SHARE MODE;
LOCK TABLE
postgres=# END;
COMMIT
postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in ROW EXCLUSIVE MODE;
LOCK TABLE
postgres=# END;
COMMIT
postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in SHARE UPDATE EXCLUSIVE MODE;
ERROR: cannot execute LOCK TABLE during recovery
postgres=# END;
ROLLBACK
postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in SHARE MODE;
ERROR: cannot execute LOCK TABLE during recoverypostgres=# END;
ROLLBACK
postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in SHARE ROW EXCLUSIVE MODE;
ERROR: cannot execute LOCK TABLE during recovery
postgres=# END;
ROLLBACK
postgres=# BEGIN;
BEGIN
postgres=# LOCK TABLE test01 in EXCLUSIVE MODE;
ERROR: cannot execute LOCK TABLE during recovery
postgres=# END;
ROLLBACK
postgres=# LOCK TABLE test01 in ACCESS EXCLUSIVE MODE;
ERROR: LOCK TABLE can only be used in transaction blocks
postgres=# END;
WARNING: there is no transaction in progress
COMMIT

从上例中可以知道, 在Hot Standby备库上可以加以下类型的表锁:

  • ACCESS SHARE。
  • ROW SHARE。
  • ROW EXCLUSIVE MODE。

也就是说, 比ROW EXCLUSIVE MODE级别高的表锁都是不能执行的, 或者说, 自己和自己互斥的锁和SHARE类型的表锁都不能执行。

在Hot Standby备库上, 部分事务管理语句都可以执行, 如上面示例中的BEGIN、 END, 但下面的语句不能执行:

  • BEGIN READ WRITE,START TRANSACTION READ WRITE。
  • SET TRANSACTION READ WRITE,SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE。
  • SET transaction_read_only=off。

在Hot Standby备库上, 两阶段提交的命令也不能执行:

  • PREPARE TRANSACTION。
  • COMMIT PREPARED。
  • ROLLBACK PREPARED。

在Hot Standby备库中, 序列中会导致更新的函数也不能执行:

  • nextval()。
  • setval()。

在Hot Standby备库中, 消息通知的语句也不能执行:

  • LISTEN。
  • UNLISTEN。
  • NOTIFY。

但在通常的只读事务中, 序列的更新函数和消息通知的语句都是可以执行的, 也就是说, 在HOT Standby备库中执行SQL语句的限制比只读事务中的限制更多。
在Hot Standby备库中, 参数“transaction_read_only”总设置为“ON”, 而且不能改变。
可以使用“SHOW transaction_read_only”查看此参数的状态。

6.2 Hot Standby的查询冲突处理

主库上的一些操作会与Hot Standby备库上的查询产生冲突, 会导致正在执行的查询被取消并报如下错误:

ERROR: canceling statement due to conflict with recovery

导致冲突的原因有以下几个:

  • 主库上运行的VACUUM清理掉了备库上的查询需要的多版本数据。
  • 主库上执行LOCK命令或各种DDL语句会在表上产生Exclusive锁, 而在备库上对这些表进行查询时, 这两个操作之间会有冲突。
  • 在主库上删除了一个表空间, 而备库上的查询需要存放一些临时文件在此表空间中。
  • 在主库上删除了一个数据库, 而备库上有很多session还连接在该数据库上。

当发生冲突时, 处理的方法有以下几种:

  • 让备库上的应用WAL日志的过程等待一段时间, 等备库上的查询结束后再应用WAL日志。
  • 取消备库上正在执行的查询。

另外, 在主库上删除一个数据库时, 备库上连接到此数据库上的session都将被断开连接。

如果备库上的查询运行的时间很短, 可以让备库上WAL日志的应用过程等一会儿。
但是如果备库上的查询是一个大查询, 需要运行很长的时间, 让应用WAL日志的过程一直等待, 会导致备库延迟主库太多的问题, 因此PostgreSQL在postgresql.conf中增加了两个参数用于控制应用WAL日志的最长等待时间, 超过设定时间就会取消备库上正在执行的SQL查询。
这两个参数的说明如下:

  • max_standby_archive_delay: 备库从WAL归档中读取时的最大延迟。
    默认为30秒,如果设置为-1, 则会一直等待。
  • max_standby_streaming_delay: 备库从流复制中读取WAL时的最大延迟。
    默认为30秒, 如果设置为-1, 则会一直等待。

如果备库用作主库的高可用切换, 则可以把以上参数设置得小一些, 这样可以保证备库不会落后主库太多; 如果备库就是用来执行一些大查询的, 可以把这两个参数设置成较大的值。

大多数冲突发生的原因是主库上把备库需要的多版本数据给清理掉了, 这时可以通过在备库上的postgresql.conf中设置参数“hot_standby_feedback”为“true”来解决此问题。
设置此参数为“true”后, 备库会通知主库, 哪些多版本数据在备库上还需要, 这样主库上的AutoVacuum就不会清理掉这些数据, 就能大大减少冲突的发生。

当然还有一个办法是把主库上的参数“vacuum_defer_cleanup_age”的值调得大一些, 以延迟清理多版本数据。

当然即使设置了hot_standby_feedback等参数, 仍然会有一些查询因为冲突而被取消执行, 所以连接到备库的应用程序最好能检测到这个错误并能再次执行被取消的查询。
在备库上因为冲突而被取消执行的SQL命令的数量可以在视图“pg_stat_database_conflicts”中查询到。

7.恢复配置详解

在PostgreSQL 12之前的版本中, 一些专门的恢复配置项是放在一个单独的配置文件“recovery.conf”中的, 从PostreSQL 12版本开始去除了这个文件。
如果在PostgreSQL 12版本的数据目录中存在recovery.conf文件会导致数据库无法启动:

[postgres@pg02 log]$ cat postgresql-Sun.log
2020-02-09 12:17:53.280 CST [3770] LOG: database system was shut down at 2020-02-09 12:16:58 CST
2020-02-09 12:17:53.280 CST [3770] FATAL: using recovery command file "recovery.conf" is not supported
2020-02-09 12:17:53.284 CST [3768] LOG: startup process (PID 3770) exited with exit code 1
2020-02-09 12:17:53.284 CST [3768] LOG: aborting startup due to startup process failure
2020-02-09 12:17:53.285 CST [3768] LOG: database system is shut down

7.1 归档恢复配置项

归档恢复配置项主要有以下3个, 这几项在PostgreSQL12版本之前是配置在recovery.conf文件中的, 而自PostgreSQL12版本开始就合并到了postgresql.conf文件中。

  • restore_command: 指定Standby如何获得WAL日志文件, 通常是配置一个拷贝命令, 从备份目录中把WAL日志文件拷贝过来。
  • archive_cleanup_command: 清理Standby数据库机器上不需要的WAL日志文件。
  • recovery_end_command: 恢复完成后, 可以执行一个命令。

使用这几个配置项就可以搭建起一个从归档日志文件中恢复的Standby数据库。
例如, 在主库上配置archive_command参数, 把WAL文件复制到Standby库的一个目录中, 命令如下:

archive_command = 'scp %p 192.168.1.52:/data/archivedir/%f.mid && ssh 192.168.1.52 "mv /data/archivedir/%f.mid /data/archivedir/%f"'

然后在Standby数据库中的recovery.conf中配置restore_command参数, 命令如下:

restore_command = 'cp /data/archivedir /%f "%p"'

另两个参数“archive_cleanup_command”“recovery_end_command”是可选的, 其中archive_cleanup_command参数可以用来清理上面示例中“/data/archivedir”目录中的WAL日志文件。
从上面的示例中可以知道, 当主库不断地把WAL日志文件复制到Standby备库的“/data/archivedir”目录中时, 一定要有清理机制, 否则就会把此目录的空间填满。
清理的原则通常是清除Standby已使用完的WAL日志文件。
contrib目录中提供了一个命令行的工具“pg_archivecleanup”以便实现清理工作, archive_cleanup_comand参数的配置内容如下:

archive_cleanup_command = 'pg_archivecleanup /data/archivedir %r'

下面介绍主库上的归档配置项。
主库上的归档配置项有如下3个, 都是在postgresql.conf文件中配置:

  • archive_mode: 是否开启归档。 如果想以归档的方式搭建Standby数据库, 则此参数设置为“on”。
  • archive_command: 执行归档的命令。
  • archive_timeout: 如果主库在某段时间内比较闲, 可能会很长时间才产生WAL日志文件, 这会导致主库和Standby库之间有较大的延迟, 这时可以配置此参数。
    把此参数配置成一个整数(单位是秒) , 表示设定的秒数内会强制数据库切换一个WAL日志文件。
    注意, 被强制切换的WAL文件和正常WAL文件一样大。
    因此把archive_timeout设置成很小的值是不明智的, 会占用大量空间。

7.2 Recovery Target配置

通常Standby备库的恢复是一直进行的, 如果想让Standby恢复到一个指定的点后就暂停, 需要使用以下配置参数:

  • recovery_target: 目前此参数只能配置为空或“immediate”, 配置为“immediate”, 则Standby恢复到一个一致性的点时就立即停止恢复。
    该配置通常用在热备份中。
    完成一个热备份后, 如果想使用这个热备份, 希望在应用WAL日志把热备份恢复到一个可以打开的点时立即打开此数据库, 就需要配置此参数。
  • recovery_target_name: 这是9.1版本之后才提供的参数。
    在主库上可以创建一个恢复点, 然后让Standby恢复到这个恢复点, 此参数用来指定该恢复点的名称。
    创建恢复点是通过调用函数pg_create_restore_point()来完成的。
  • recovery_target_time: 这是9.1版本之后才提供的参数, 用于指定恢复到哪个时间点。
    恢复到设定时间点之前最近的一致点还是该时间点之后最近的一致点是由后面的参数“recovery_target_inclusive”来指定的。
  • recovery_target_xid: 这是9.1版本之后才提供的参数, 指定恢复到哪个指定的事务。
    注意, 事务ID是按顺序分配的, 但事务完成的顺序与分配的顺序是不一样的。
    后分配的ID的事务可能会先完成。
  • recovery_target_inclusive: 指定恢复到恢复目标(recovery target) 之后还是之前。
    默认为恢复目标之后, 即值为“true”。
  • recovery_target_timeline: 指定恢复的时间线。
    默认只恢复到当前的时间线, 而不会切换到新的时间线。
    通常需要把此参数设置为“latest”, 这样就会恢复到离当前最近的时间线。
  • pause_at_recovery_target: 指定到达恢复目标后, Standby数据库恢复是否暂停。
    默认为“true”。
    该参数用于检查当前Standby是否恢复到了需要的点。
    在恢复暂停后, 执行SQL语句来检查是否是需要的时间点, 如果不是, 可以停止Standby数据库, 然后重新配置“recovery_target_*”参数指定新的恢复目标点, 再进行恢复, 直到把Standby推到需要的时间点。
    到达该时间点后, 就可以使用pg_wal_replay_resume()继续进行恢复。

这些配置项在PostgreSQL 12版本之前都是在recovery.conf中配置的, PostgreSQL 12版本之后合并到了postgresql.conf文件中。

7.3 Standby Server配置

备库中还有用于配置Standby Server的以下参数, 各参数的说明如下:

  • standby_mode: 是否运行在Standby模式下。
    只有PostgreSQL 12之前的版本中才有此配置项。
    PostgreSQL 12版本之后用文件“standby.signal”表示是否运行在Standby模式下。
  • primary_conninfo: 在流复制中, 指定如何连接主库, 是一个标准的libpq连接串。
  • primary_slot_name: 指定复制槽(Replication Slot) 。
    这是PostgreSQL 9.4版本之后增加的参数, 是一个可选参数。
  • promote_trigger_file: 指定激活Standby的触发文件。
    Standby数据库发现存在此文件时, 就会把Standby激活为主库。
    不配置此项也没有关系, 可以使用pg_ctl promote来激活Standby数据库。
    在PostgreSQL12版本之前, 此配置项的名称为“trigger_file”, 配置在recovery.conf中。
  • recovery_min_apply_delay: PostgreSQL 9.4版本之后增加的参数, 此参数可以让Standby落后主库一段时间。
    在PostgreSQL 9.4版本之前, 很难让Standby落后主库指定的时间。
    例如, 有如下场景, 创建了一个Standby库用于防止逻辑误删除操作, 如果该库被设置为即时与主库同步, 而有人恰巧不小心删除了某一张表, 那可能就会导致Standby上的这张表也很快被删除, 这时如果让Standby延迟恢复一段时间, 那就可以在设定的延迟时间内从Standby数据库中恢复这张表的数据。 该参数指定一个时间值, 如“5min”。
    设置此参数后, hot_standby_feedback也会相应被延迟。

这些配置项在PostgreSQL 12版本之前都是在recovery.conf中配置的, PostgreSQL 12版本之后合并到了postgresql.conf文件中。

8.流复制的注意事项

8.1 min_wal_size参数的配置

使用流复制建好备库后, 如果由于各种原因备库接收日志的速度较慢, 而主库产生日志的速度很快, 这容易导致主库上的WAL日志还没有传递到备库就会被覆盖, 如果被覆盖的WAL日志文件又没有归档备份, 那么备库就再也无法与主库同步了, 这会导致备库需要重新搭建。
为了避免这种情况发生, PostgreSQL提供了一个配置参数“wal_keep_segments”。
该参数的含义是, 无论如何都要在主库上保留wal_keep_segements个WAL日志文件。
默认此参数为“0”, 表示并不专门为Standby保留WAL日志文件。
通常需要把此参数配置成一个安全的值, 如“64”, 表示将为Standby保留64个WAL日志文件。
当然保留WAL日志文件会占用一定的磁盘空间, 每个WAL日志文件的大小通常是16MB, 如果设置为“64”,就可能会多占用64×16MB=1G空间。
所以如果磁盘空间允许, 可以把此参数设置得大一些, 这样, WAL日志来不及传输到备库导致的备库需要重新搭建的风险就会小一些。
PostgreSQL 10之后的版本中提供了一个更容易理解的参数“min_wal_size”, 该参数表示至少要保留多少空间的WAL日志, 而wal_keep_segments参数需要根据每个WAL文件的大小才能算出为WAL保留的空间, 而WAL文件的大小不同的数据库可能不一样, 这增加了计算的难度。

8.2 vacuum_defer_cleanup_age参数的配置

在主库上, Vacuum进程知道哪些旧版本的数据会被当前数据库中的查询使用, 从而不清理这些数据。
但对于Hot Standby上的查询的数据需要, 主库是不知道的, 所以主库上的Vacuum可能会把Hot Standby上的查询还需要的旧版本数据清理掉, 这会导致Standby上的查询失败。
为了降低Hot Standby因为这个原因失败的概率, 可以设置vacuum_defer_cleanup_age参数, 让主库延迟清理。
该参数的含义是延迟清理多少个事务, 当然也可以通过在备库上设置参数“hot_standby_feedback”为“true”来减少此问题的发生。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论