Postgresql主从异步流复制方案的深入探究
前言
数据库的备份工作在日常生产中极为重要,如果你咨询一个DBA如何才能设计出高可用的数据备份与恢复方案,相信很多人都会从架构上给出很多容灾的意见。但归根到底,如果业务环节中数据库还牵涉到分布式环境,我认为一个好的方案需要达到三大要求:
- 多副本
- 持久化
- 一致性
日常架构设计中,我们不仅要保证数据额的成功备份,还要保证备份的数据可以快速恢复。在众多备份恢复可靠性方案中主从复制技术,可以说是最常见的实现,本文主要是介绍postgresql主备数据库的异步流复制的环境搭建与主备切换的操作实践,除了能把一些基础的原理运用在日常的数据库运维中,也可以加深对Postgresql数据库的底层知识了解。
postgres在9.0之后引入了主从的流复制机制,所谓流复制,就是从服务器通过tcp流从主服务器中同步相应的数据。这样当主服务器数据丢失时从服务器中仍有备份。
与基于文件日志传送相比,流复制允许保持从服务器更新。从服务器连接主服务器,其产生的流WAL记录到从服务器,而不需要等待主服务器写完WAL文件。
PostgreSQL流复制默认是异步的。在主服务器上提交事务和从服务器上变化可见之间有一个小的延迟,这个延迟远小于基于文件日志传送,通常1秒能完成。如果主服务器突然崩溃,可能会有少量数据丢失。
同步复制必须等主服务器和从服务器都写完WAL后才能提交事务。这样在一定程度上会增加事务的响应时间。
下面的学习与实践主要针对PostgreSQL的异步流复制(本文没有涉及到同步复制、逻辑复制等,如果大家想了解其它的备份方案,可以阅读相关官方文档或其他资料介绍)。
异步流复制的中心思想是:主库上提交事务时不需要等待备库接收WAL日志流并写入到备库WAL日志文件时便返回成功,因此异步流复制的TPS会相对同步流复制要高,延迟更低。
环境准备
操作系统 | 服务器IP | 节点名称 | 角色 |
---|---|---|---|
centos7.2 | 172.17.0.2 | pghost1 | 主库 |
centos7.2 | 172.17.0.5 | pghost2 | 备库 |
主要目录规范:
- 数据目录:/data/pg10/pg_root
- 表空间目录:/data/pg10/pg_tbs
- 应用程序目录:/apps/svr/pgsql
要注意的是:编译安装Pg我们使用的是root账户,但是一般情况下,我们对数据库的部署操作等应该使用非root的pg超级管理员账户,所以需要我们预先创建相关用户和目录,并设置相关权限:
$groupaddpostgres $useraddpostgres-gpostgres $passwdpostgres $mkdir-p/data/pg10/pg_root $mkdir-p/data/pg10/tbs $chown-Rpostgres:postgres/data/pg10
实验用的postgresql为10.0版本
pghost1和pghost2分别下载该版本的源码安装包
wgethttps://ftp.postgresql.org/pub/source/v10.0/postgresql-10.0.tar.gz
下载后进行解压
tar-zxvfpostgresql-10.0.tar.gz
安装前依赖
由于configure过程中依赖操作系统包zlib、readline等,所以我实用yum预先安装:
yumgroupinstall"Developmenttools” yuminstall-ybisonflexreadlinereadline-develzlibzlib-devel
主备库数据库安装
安装前,我们先分别对pghost1和pghost2创建postgresql的偏好环境变量
vi/etc/profile.d/pgsql.sh
追加以下内容:
exportPGPORT=1921 exportPGUSER=postgres exportPGDATA=/data/pg10/pg_root exportLANG=en_US.utf8 exportPGHOME=/apps/svr/pgsql exportLD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib exportPATH=$PGHOME/bin:$PATH:. exportMANPATH=$PGHOME/share/man:$MANPATH aliasrm='rm-i' aliasll='ls-lh'
保存文件,并让环境变量生效:
source/etc/profile.d/pgsql.sh
再进入刚刚解压的postgresql-10.0目录中,执行以下命令:
./configure—prefix=/apps/svr/pgsql_10.0/--with-pgport=1921
之后进行编译安装:
gmake gmakeinstall
安装完成后,我们可以使用以下命令确认是否安装成功:
$postgres--version postgres(PostgreSQL)10.0
复制功能部署
在启动数据库服务搭建主从结构前,有几个比较重要的配置文件需要我们额外地进行创建与设置的,它们分别是:
- postgreql.conf
- pg_hba.conf
- recovery.conf
- .pgpass
下面我们会在实践中,具体地对上述的文件的配置进行相关说明
上一节,我们编译安装好了postgresql,我们接下来切换操作用户
supostgresql
然后使用initdb工具初始化数据库:
echo"123456">>/data/pg10/pgpass initdb-D/data/pg10/pg_root-EUTF8--locale=C-Upostgres--pwfile=/data/pg10/pgpass
执行上述命令后,在/data/pg10/pg_root目录下会产生系统数据文件,
PG_VERSIONpg_dynshmempg_multixactpg_snapshotspg_tblspcpostgresql.auto.conf basepg_hba.confpg_notifypg_statpg_twophasepostgresql.conf globalpg_ident.confpg_replslotpg_stat_tmppg_wal pg_commit_tspg_logicalpg_serialpg_subtranspg_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
hostreplicationrepuser172.17.0.2/32md5 hostreplicationrepuser172.17.0.5/32md5
其实最好主库也配置一份,因为主库和备库的角色不是静止的,在手动或库出现故障情况下,它们的角色会互相更换。
之后,我们先启动主库pghost1了(记得切换到postgres用户):
$pg_ctlstart-D$PGDATA ... ... databasesystemisreadytoacceptconnections done serverstarted
使用PostgreSQL的超级管理员postgres登录到创建流复制用户repuser,流复制用户需要有REPLICATION权限和LOGIN权限
$psql-Upostgres-p1921 psql(10.0) Type"help"forhelp. postgres=#CREATEUSERrepuserREPLICATIONLOGINCONNECTIONLIMIT5ENCRYPTEDPASSWORD'domac123'; CREATEROLE
以上命令基本完成主库上的配置,接下来我们需要热备生成一个备库,制作备库过程中主库仍然可以读写,不影响业务,我们在主库上创建备份任务:
postgres=#selectpg_start_backup('domacli_bak'); pg_start_backup ----------------- 0/2000060 (1row)
pg_start_backup()函数会在主库上发起一个在线备份,命令执行后,将数据文件压缩拷贝到备份节点上:
$tarczvfpg_root.tar.gzpg_root--exclude=pg_root/pg_wal $scppg_root.tar.gzpostgres@172.17.0.5:/data/pg10
pg_wal目录不是必须复制的,可以排除这个目录,以节省空间,然后我们回到备库的/data/pg10下,执行主库备份文件的解压:
$tarxvfpg_root.tar.gz
解压后,我们回到主节点,执行停止备份命令,结束这次备份流程
postgres=#selectpg_stop_backup(); NOTICE:pg_stop_backupcomplete,allrequiredWALsegmentshavebeenarchived pg_stop_backup ---------------- 0/2000168 (1row)
以上的命令表示完成在线备份,但备库上扔需要做一些配置,我们回到备库上,配置/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.2port=1921user=repuser'
主要观察recovery.conf中的参数primary_conninfo中的user=repuser,还记得我们前面在主库上创建的流传输用户repuser吗?由于主备直接数据同步需要在用户下执行操作,而主库上我们创建repuser的时候,为了安全我设置了密码,但recovery.conf我们没有配置明文密码,那么程序的密码如何获得呢?
我们建议把密码设置在~/.pgpass中:
你也可以直接在上面的recovery.conf设置primary_conninfo=‘host=172.17.0.2port=1921user=repuserpassword=domac123',但这样会有安全风险
$cd~ $touch.pgpass $chmod0600.pgpass
填写以下内容:
172.17.0.2:1921:replication:repuser:domac123 172.17.0.5:1921:replication:repuser:domac123
好了,当这些备注都就绪之后,我们可以开始启动我们的备库了:
$pg_ctlstart ... databasesystemisreadytoacceptreadonlyconnections done serverstarted
如果备库正常启动,我们可以在主备两库上观察WAL发生与接收进程是否都同时工作,以确认异步流工作是否正常工作
主库上:
ps-ef|grepwal postgres69396935023:16?00:00:00postgres:walwriterprocess postgres69836935023:42?00:00:00postgres:walsenderprocessrepuser172.17.0.5(45910)streaming0/3000140
备库上:
ps-ef|grepwal postgres2648126479023:42?00:00:00postgres:walreceiverprocessstreaming0/3000140 postgres2648626448023:42?00:00:00grep--color=autowal
使用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_ctlstop-mfast waitingforservertoshutdown....done serverstopped $rm-rf$PGDATA $rm-rf/data/pg10/pg_tbs
接下来,在pghost2上,使用pg_basebackup触发基准备份
pg_basebackup-D$PGDATA-Fp-Xs-v-P-h172.17.0.2-p1921-Urepuser-W
执行后,会看到相关的日志输出
pg_basebackup:initiatingbasebackup,waitingforcheckpointtocomplete pg_basebackup:checkpointcompleted pg_basebackup:write-aheadlogstartpoint:0/20007A8ontimeline1 pg_basebackup:startingbackgroundWALreceiver 22655/22655kB(100%),1/1tablespace pg_basebackup:write-aheadlogendpoint:0/2000888 pg_basebackup:waitingforbackgroundprocesstofinishstreaming... pg_basebackup:basebackupcompleted
从以上日志信息看出pg_basebackup命令首先对数据库做一次checkpoint,之后基于时间点做一个全库基准备份,全备过程中会拷贝$PGDATA数据文件和表空间文件到备库节点对应目录
最后,跟之前使用pg_start_backup的方式一样,备库记得配置recovery.conf
recovery_target_timeline='latest' standby_mode=on primary_conninfo='host=172.17.0.2port=1921user=repuserpassword=domac123'
如果也配置了pgpass文件,可以使用下属的配置:
recovery_target_timeline='latest' standby_mode=on primary_conninfo='host=172.17.0.2port=1921user=repuser'
到此为止,主备的配置基本完成,当然,稳妥起见,我们最好多动手动手,尝试在主库上创建并插入数据,观察备库上是否同步这些操作,我们再主库上创建一张表:
postgres=#createtabletest_ms(idint4); CREATETABLE postgres=#insertintotest_msvalues(6); INSERT01
主库上,我们创建test_ms表,并插入了一条数据,我们就可以在备库上进行查询观察是否同步成功:
postgres=#select*fromtest_ms; id ---- 6 (1row)
接下来,我们再主库上,再操作
postgres=#insertintotest_msvalues(9); INSERT01 postgres=#deletefromtest_mswhereid=6; DELETE1
这个时候,我们发现备库的数据也都正常同步上了:
postgres=#select*fromtest_ms; id ---- 9 (1row)
那么我们如果在备份上进行数据操作,情况会怎样呢?我们再备份上执行:
postgres=#insertintotest_msvalues(6); ERROR:cannotexecuteINSERTinaread-onlytransaction STATEMENT:insertintotest_msvalues(6); ERROR:cannotexecuteINSERTinaread-onlytransaction
观察这些错误日志,我们可以了解到,异步流主从结构中,作为从节点的备库目前处于的是只读状态,它不能进行任何写入操作。
主备切换
前面介绍了流复制的部署,但要注意的是主库和备库的角色不是静态存在的,在维护过程中可以对两者的进行角色的切换,举个例子,当主库挂掉的时候,需要迅速进行主备切换,让备库升级为主库,原主库降级到备库,主备切换是PostgreSQL高可用的基础,下面就介绍相关的操作。
postgresql9.0版本流复制只能通过创建文件方式进行主备切换,9.1后,开始支持使用pg_ctlpromote触发方式,相比文件触发方式操作更方便
操作前,我们先介绍一个系统函数查用来判断主备角色的方法:
postgres=#selectpg_is_in_recovery(); pg_is_in_recovery ------------------- f (1row)
如果返回f说明是主库,返回t说明是备库
pg_ctlpromote切换方式
我们使用以下的步骤进行主备切换:
1、关闭主库,建议使用-mfast模式关闭
$pg_ctlstop-mfast
2、在备库上执行pg_ctlpromote命令激活备库,如果recovery.conf变成recovery.done表示备库已切换成主库
pg_ctlpromote-D$PGDATA waitingforservertopromote....2018-09-3000:10:30.222UTC[26480]LOG:receivedpromoterequest LOG:redodoneat0/4000028 LOG:lastcompletedtransactionwasatlogtime2018-09-2923:50:52.502513+00 LOG:selectednewtimelineID:2 LOG:archiverecoverycomplete LOG:databasesystemisreadytoacceptconnections SunSep3000:10:30UTC2018 SunSep3000:10:30UTC2018 done serverpromoted
命令执行后,如果原来的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.5port=1921user=repuserpassword=domac123'
如果要求更高的安全性,可以参考如下配置:
recovery_target_timeline='latest' standby_mode=on primary_conninfo='host=172.17.0.5port=1921user=repuser'
与此同时,和原备库pghost2一样,我们建议把repuser的密码设置在pghost1~/.pgpass中:
$cd~ $touch.pgpass $chmod0600.pgpass
填写以下内容:
172.17.0.2:1921:replication:repuser:domac123 172.17.0.5:1921:replication:repuser:domac123
4、启动老的主库pghost1,这时观察主、备进行是否正常,严格点可以在新的主库上对刚才的test_ms表进行操作,观察数据是否同步成功。
pg_ctlstart
我们在新主库(pghost2)上执行:
postgres=#selectpg_is_in_recovery(); pg_is_in_recovery ------------------- f (1row)
发现它目前的角色已经是主库了,在新备库(pghost1)上继续执行:
postgres=#selectpg_is_in_recovery(); pg_is_in_recovery ------------------- t (1row)
发现它目前的角色也已经切换为备库了
我们再pghost2上,执行数据插入操作:
postgres=#insertintotest_msvalues(11); INSERT01
这时,pghost1上也观察到数据同步成功:
postgres=#select*fromtest_ms; id ---- 9 11 (2rows)
到这里为止,主从切换的演练基本完成了
总结
异步流复制模式中,主库提交的事务不会等待备库接收WAL日志流并返回确认信息,因此异步流复制模式下主库与备库的数据版本上会存在一定的处理延迟,延迟的时间主要受主库压力、备库主机性能、网络带宽等影响,当正常情况下,主备的延迟通常在毫秒级的范围内,当主库宕机,这个延迟就主要受到故障发现与切换时间的影响而拉长,不过虽然如此,这些数据延迟的问题,可以从架构或相关自动化运维手段不断优化设置。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。