9 29
Postgresql主从异步流复制方案

数据库的备份工作在日常生产中极为重要,如果你咨询一个DBA如何才能设计出高可用的数据备份与恢复方案,相信很多人都会从架构上给出很多容灾的意见。但归根到底,如果业务环节中数据库还牵涉到分布式环境,我认为一个好的方案需要达到三大要求:

  • 多副本
  • 持久化
  • 一致性

日常架构设计中,我们不仅要保证数据额的成功备份,还要保证备份的数据可以快速恢复。在众多备份恢复可靠性方案中 主从复制 技术,可以说是最常见的实现,本文主要是介绍postgresql主备数据库的异步流复制的环境搭建与主备切换的操作实践,除了能把一些基础的原理运用在日常的数据库运维中,也可以加深对Postgresql数据库的底层知识了解。

下面的学习与实践主要针对PostgreSQL的异步流复制(本文没有涉及到同步复制、逻辑复制等,如果大家想了解其它的备份方案,可以阅读相关官方文档或其他资料介绍)。

异步流复制的中心思想是:主库上提交事务时不需要等待备库接收WAL日志流并写入到备库WAL日志文件时便返回成功,因此异步流复制的TPS会相对同步流复制要高,延迟更低。

环境准备

操作系统 服务器IP 节点名称 角色
centos 7.2 172.17.0.2 pghost1 主库
centos 7.2 172.17.0.5 pghost2 备库

主要目录规范:

  • 数据目录: /data/pg10/pg_root

  • 表空间目录: /data/pg10/pg_tbs

  • 应用程序目录: /apps/svr/pgsql

要注意的是:编译安装Pg我们使用的是root账户,但是一般情况下,我们对数据库的部署操作等应该使用非root的pg超级管理员账户,所以需要我们预先创建相关用户和目录,并设置相关权限:

$ groupadd postgres

$ useradd postgres -g postgres

$ passwd postgres

$ mkdir -p /data/pg10/pg_root

$ mkdir -p /data/pg10/tbs

$ chown -R postgres:postgres /data/pg10

实验用的postgresql为10.0版本

pghost1 和 pghost2 分别下载该版本的源码安装包

wget https://ftp.postgresql.org/pub/source/v10.0/postgresql-10.0.tar.gz

下载后进行解压

tar -zxvf postgresql-10.0.tar.gz

安装前依赖

由于 configure过程中依赖操作系统包zlib、readline等,所以我实用yum预先安装:

yum groupinstall "Development tools”

yum install -y bison flex readline readline-devel zlib zlib-devel

主备库数据库安装

安装前,我们先分别对pghost1 和 pghost2创建postgresql的偏好环境变量

vi /etc/profile.d/pgsql.sh

追加以下内容:

export PGPORT=1921
export PGUSER=postgres
export PGDATA=/data/pg10/pg_root
export LANG=en_US.utf8
export PGHOME=/apps/svr/pgsql
export LD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib
export PATH=$PGHOME/bin:$PATH:.
export MANPATH=$PGHOME/share/man:$MANPATH

alias rm='rm -i'
alias ll='ls -lh'

保存文件,并让环境变量生效:

source /etc/profile.d/pgsql.sh

再进入刚刚解压的 postgresql-10.0 目录中,执行以下命令:

./configure —prefix=/apps/svr/pgsql_10.0/ --with-pgport=1921

之后进行编译安装:

gmake

gmake install

安装完成后,我们可以使用以下命令确认是否安装成功:

$ postgres --version

postgres (PostgreSQL) 10.0

复制功能部署

在启动数据库服务搭建主从结构前,有几个比较重要的配置文件需要我们额外地进行创建与设置的,它们分别是:

  • postgreql.conf

  • pg_hba.conf

  • recovery.conf

  • .pgpass

下面我们会在实践中,具体地对上述的文件的配置进行相关说明

上一节,我们编译安装好了postgresql,我们接下来切换操作用户

su postgresql

然后使用initdb工具初始化数据库:


echo "123456" >> /data/pg10/pgpass

initdb -D /data/pg10/pg_root -E UTF8 --locale=C -U postgres --pwfile=/data/pg10/pgpass

执行上述命令后,在/data/pg10/pg_root目录下会产生系统数据文件,

PG_VERSION    pg_dynshmem    pg_multixact  pg_snapshots  pg_tblspc    postgresql.auto.conf
base          pg_hba.conf    pg_notify     pg_stat       pg_twophase  postgresql.conf
global        pg_ident.conf  pg_replslot   pg_stat_tmp   pg_wal
pg_commit_ts  pg_logical     pg_serial     pg_subtrans   pg_xact

之后我们开始配置 /data/pg10/pg_root/postgresql.conf,修改以下几个关键项:

listen_addresses = '*'
wal_level = replica             
archive_mode = on            
archive_command = '/bin/date'  
max_wal_senders = 10         
wal_keep_segments = 512
hot_standby = on

注:主库和备库的 /data/pg10/pg_root/postgresql.conf 配置建议完全一致

接下来我们在 备库 上配置 /data/pg10/pg_root/pg_hba.conf

host    replication     repuser          172.17.0.2/32            md5
host    replication     repuser          172.17.0.5/32            md5

其实最好主库也配置一份,因为主库和备库的角色不是静止的,在手动或库出现故障情况下,它们的角色会互相更换。

之后,我们先启动主库 pghost1了 (记得切换到postgres用户):

$ pg_ctl start -D $PGDATA

...
...
database system is ready to accept connections
 done
server started

使用PostgreSQL的超级管理员postgres登录到创建流复制用户repuser,流复制用户需要有 REPLICATION权限和LOGIN权限

$ psql -U postgres -p 1921
psql (10.0)
Type "help" for help.

postgres=# CREATE USER repuser REPLICATION  LOGIN CONNECTION LIMIT 5 ENCRYPTED PASSWORD 'domac123';

CREATE ROLE

以上命令基本完成主库上的配置,接下来我们需要热备生成一个备库,制作备库过程中主库仍然可以读写,不影响业务,我们在主库上创建备份任务:

postgres=# select pg_start_backup('domacli_bak');

 pg_start_backup
-----------------
 0/2000060
(1 row)

pg_start_backup() 函数会在主库上发起一个在线备份,命令执行后,将数据文件压缩拷贝到备份节点上:

$ tar czvf pg_root.tar.gz pg_root --exclude=pg_root/pg_wal

$ scp pg_root.tar.gz postgres@172.17.0.5:/data/pg10

pg_wal目录不是必须复制的,可以排除这个目录,以节省空间,然后我们回到备库的/data/pg10下,执行主库备份文件的解压:

$ tar xvf pg_root.tar.gz

解压后,我们回到主节点,执行停止备份命令,结束这次备份流程

postgres=# select pg_stop_backup();

NOTICE:  pg_stop_backup complete, all required WAL segments have been archived
 pg_stop_backup
----------------
 0/2000168
(1 row)

以上的命令表示完成在线备份,但备库上扔需要做一些配置,我们回到备库上,配置 /data/pg10/pg_root/recovery.conf文件,如果该文件不存在,可以执行以下命令,在软件目录中复制一个:

cp $PGHOME/share/recovery.conf.sample /data/pg10/pg_root/recovery.conf

备库的 recovery.conf 配置以下参数

recovery_target_timeline = 'latest'
standby_mode = on
primary_conninfo = 'host=172.17.0.2  port=1921 user=repuser'

主要观察recovery.conf中的参数primary_conninfo 中的 user=repuser, 还记得我们前面在主库上创建的流传输用户repuser吗?由于主备直接数据同步需要在用户下执行操作,而主库上我们创建repuser的时候,为了安全我设置了密码, 但recovery.conf我们没有配置明文密码,那么程序的密码如何获得呢?

我们建议把密码设置在 ~/.pgpass中:

你也可以直接在上面的recovery.conf 设置 primary_conninfo = ‘host=172.17.0.2 port=1921 user=repuser password=domac123’, 但这样会有安全风险

$ cd ~
$ touch .pgpass
$ chmod 0600 .pgpass

填写以下内容:

172.17.0.2:1921:replication:repuser:domac123
172.17.0.5:1921:replication:repuser:domac123

好了,当这些备注都就绪之后,我们可以开始启动我们的备库了:

$ pg_ctl start

...

database system is ready to accept read only connections
 done
server started

如果备库正常启动,我们可以在主备两库上观察WAL发生与接收进程是否都同时工作,以确认异步流工作是否正常工作

主库上:

ps -ef | grep wal

postgres  6939  6935  0 23:16 ?        00:00:00 postgres: wal writer process
postgres  6983  6935  0 23:42 ?        00:00:00 postgres: wal sender process repuser 172.17.0.5(45910) streaming 0/3000140

备库上:

ps -ef | grep wal

postgres 26481 26479  0 23:42 ?        00:00:00 postgres: wal receiver process   streaming 0/3000140
postgres 26486 26448  0 23:42 ?        00:00:00 grep --color=auto wal

使用 pg_basebackup 方式部署流复制

接下来,介绍一种操作相对简洁的方式,上述我们配置操作所牵涉到的主要步骤有:

  • pg_start_backup

  • 两台服务器之间的数据拷贝

  • pg_stop_backup

以上三个步骤可以合成一步完成,PostgreSQL提供内置的pg_basebackup命令行工具支持对主库发起一个在线基准备份,并自动进入备份模式进行数据库基准备份,备份完成后自动从备份模式退出,不需要执行额外的pg_start_backup 和pg_stop_backup 命令显式地声明进入备份模式和退出备份模式,pg_basebackup工具是对数据库实例级进行的物理备份,因此这个工具通常作为备份工具对据库进行基准备份

pg_basebackup工具发起备份需要超级用户权限或REPLICATION权限,注意max_wal_senders参数配置,因为pg_basebackup工具将消耗至少一个WAL发送进程。本节将演示通过pg_basebackup工具部署异步流复制,之前已经在pghost2上部署了一个备库,我们先将这个备库删除,之后通过pg_basebackup工具重新做一次备库,删除pghost2上的备库只需要先停备库之后删除备库数据库数据文件即可,如下所示:

进入pghost2服务器上(172.17.0.5)

$ pg_ctl stop -m fast
waiting for server to shut down.... done
server stopped

$ rm -rf $PGDATA
$ rm -rf /data/pg10/pg_tbs

接下来,在pghost2上,使用pg_basebackup触发基准备份

pg_basebackup -D $PGDATA -Fp -Xs -v -P -h 172.17.0.2 -p 1921  -U repuser -W

执行后,会看到相关的日志输出

pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/20007A8 on timeline 1
pg_basebackup: starting background WAL receiver
22655/22655 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/2000888
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed

从以上日志信息看出pg_basebackup命令首先对数据库做一次checkpoint,之后基于时间点做一个全库基准备份,全备过程中会拷贝$PGDATA数据文件和表空间文件到备库节点对应目录

最后,跟之前使用pg_start_backup的方式一样,备库记得配置recovery.conf

recovery_target_timeline = 'latest'
standby_mode = on
primary_conninfo = 'host=172.17.0.2  port=1921 user=repuser password=domac123'

如果也配置了pgpass文件,可以使用下属的配置:

recovery_target_timeline = 'latest'
standby_mode = on
primary_conninfo = 'host=172.17.0.2  port=1921 user=repuser'

到此为止,主备的配置基本完成,当然,稳妥起见,我们最好多动手动手,尝试在主库上创建并插入数据,观察备库上是否同步这些操作,我们再主库上创建一张表:

postgres=# create table test_ms(id int4);

CREATE TABLE

postgres=# insert into test_ms values(6);

INSERT 0 1

主库上,我们创建test_ms表,并插入了一条数据,我们就可以在备库上进行查询观察是否同步成功:

postgres=# select * from test_ms;
 id
----
  6
(1 row)

接下来,我们再主库上,再操作

postgres=# insert into test_ms values(9);
INSERT 0 1

postgres=# delete from test_ms where id=6;
DELETE 1

这个时候,我们发现备库的数据也都正常同步上了:

postgres=# select * from test_ms;
 id
----
  9
(1 row)

那么我们如果在备份上进行数据操作,情况会怎样呢?我们再备份上执行:

postgres=# insert into test_ms values(6);

ERROR:  cannot execute INSERT in a read-only transaction
STATEMENT:  insert into test_ms values(6);
ERROR:  cannot execute INSERT in a read-only transaction

观察这些错误日志,我们可以了解到,异步流主从结构中,作为从节点的备库目前处于的是只读状态,它不能进行任何写入操作。

主备切换

前面介绍了流复制的部署,但要注意的是主库和备库的角色不是静态存在的,在维护过程中可以对两者的进行角色的切换,举个例子,当主库挂掉的时候,需要迅速进行主备切换,让备库升级为主库,原主库降级到备库,主备切换是PostgreSQL高可用的基础,下面就介绍相关的操作。

postgresql 9.0版本流复制只能通过创建文件方式进行主备切换,9.1后,开始支持使用pg_ctl promote触发方式,相比文件触发方式操作更方便

操作前,我们先介绍一个系统函数查用来判断主备角色的方法:

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

如果返回 f 说明是主库,返回 t 说明是备库

pg_ctl promote 切换方式

我们使用以下的步骤进行主备切换:

1、关闭主库,建议使用 -m fast 模式关闭

$ pg_ctl stop -m fast

2、在备库上执行pg_ctl promote命令激活备库,如果recovery.conf变成recovery.done表示备库已切换成主库

pg_ctl promote -D $PGDATA
waiting for server to promote....2018-09-30 00:10:30.222 UTC [26480] LOG:  received promote request
LOG:  redo done at 0/4000028
LOG:  last completed transaction was at log time 2018-09-29 23:50:52.502513+00
LOG:  selected new timeline ID: 2
LOG:  archive recovery complete
LOG:  database system is ready to accept connections
Sun Sep 30 00:10:30 UTC 2018
Sun Sep 30 00:10:30 UTC 2018
 done
server promoted

命令执行后,如果原来的 recovery.conf 更名为 recovery.done, 表示切换成功

3、这时如果需要将老的主库切换成备库,在老的主库的$PGDATA目录下也创建recovery.conf文件(创建方式跟之前介绍的一样,内容可以和原从库pghost2的一样,只是primary_conninfo的IP换成对端pghost2的IP)

例如,主库上的 recovery.conf 设置为:

recovery_target_timeline = 'latest'
standby_mode = on
primary_conninfo = 'host=172.17.0.5  port=1921 user=repuser password=domac123'

如果要求更高的安全性,可以参考如下配置:

recovery_target_timeline = 'latest'
standby_mode = on
primary_conninfo = 'host=172.17.0.5  port=1921 user=repuser'

与此同时,和原备库pghost2一样,我们建议把repuser的密码设置在pghost1 ~/.pgpass中:

$ cd ~
$ touch .pgpass
$ chmod 0600 .pgpass

填写以下内容:

172.17.0.2:1921:replication:repuser:domac123
172.17.0.5:1921:replication:repuser:domac123

4、启动老的主库pghost1,这时观察主、备进行是否正常,严格点可以在新的主库上对刚才的test_ms表进行操作,观察数据是否同步成功。

pg_ctl start

我们在新主库(pghost2)上执行:

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

发现它目前的角色已经是主库了, 在新备库(pghost1)上继续执行:

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

发现它目前的角色也已经切换为备库了

我们再pghost2上,执行数据插入操作:

postgres=# insert into test_ms values(11);
INSERT 0 1

这时,pghost1上也观察到数据同步成功:

postgres=# select * from test_ms;
 id
----
  9
 11
(2 rows)

到这里为止,主从切换的演练基本完成了

总结

异步流复制模式中,主库提交的事务不会等待备库接收WAL日志流并返回确认信息,因此异步流复制模式下主库与备库的数据版本上会存在一定的处理延迟,延迟的时间主要受主库压力、备库主机性能、网络带宽等影响,当正常情况下,主备的延迟通常在毫秒级的范围内,当主库宕机,这个延迟就主要受到故障发现与切换时间的影响而拉长,不过虽然如此,这些数据延迟的问题,可以从架构或相关自动化运维手段不断优化设置。