PostgreSQL的数据备份
首先,备份的目的是为了防止数据丢失。
1 逻辑备份
创建一个由SQL命令组成的文件,服务器利用SQL命令重建与转储时状态一样的数据库。通过pg_dump
工具实现:
$ pg_dump testdb01 > db01.sql
$ pg_restore testdb02 < db01.sql
2 物理备份
PG数据库建立在文件系统之上,物理备份即是文件系统级别的备份。
2.1 冷备份
最简单直接的方法,即数据库停下来时完成文件备份,拷贝数据库的PGDATA目录即可。
$ tar -cf backup.tar /usr/local/pgsql/data
冷备份的缺点也就是备份时必须停止数据库。
2.2 热备份
在不停止数据库的情况下完成备份。有两种方式:
- 通过文件系统或块设备级别的一致性快照实现。但如果数据库跨越多个文件系统,无法对所有卷获得完全同步的快照。
- 基于时间点的备份(PITR)。
这里重点介绍PITR。
2.2.1 PITR
数据库通过WAL日志保证数据不丢失,日志中记录数据文件的每一次改变。数据库异常崩溃后,通过重放最后一次Checkpoint点之后的日志,可以还原数据库状态,这就是PITR的原理。
Checkpoint是WAL日志中的位点,保证该位点之前所有shared buffer中的脏页均被刷盘。
Checkpoint操作会向WAL日志写位点,其步骤如下:
- 首先记录下Checkpoint开始的位置,记录为redo point。
- 将shared buffer中的数据刷盘。
- 此时数据库接收一条SQL INSERT。
- Checkpoint刷脏结束,redo point之前的数据均已被刷盘,在WAL日志中记录Checkpoint位点。
- 将最新的Checkpoint位点记录在pg_control文件中。
进行数据库恢复时,从pg_control文件中找到最新的Checkpoint位置,再从Checkpoint找到redo point位置,开始重放日志。数据1和2已经刷盘,只有INSERT 3需要重放。
基于WAL日志机制,可以先把数据库以文件系统的方式备份出来,同时把相应的WAL日志也备份出来。虽然直接复制数据文件会导致文件不一致(如复制多个文件不是同一时间),且一个数据块也可能不一致(刚复制完前4KB部分,数据块又写了整个8KB的块,此时复制的前4KB和后4KB不是一个完整的数据块),但通过重放WAL日志,依然可以把数据推到一致状态。
PostgreSQL的主从复制
基于上述的PG备份恢复原理,可以将基础备份恢复到一台新机器作为从库,并不停从原始数据库机器上接收WAL日志,在新机器上持续重放WAL,追上主库。
这里把WAL日志传送到另一台机器上的方法有两种。
1 WAL日志归档
日志归档会把在线的已写完的WAL日志移动到指定的归档目录。
在postgresql.conf
文件中做如下配置即可:
archive_mode = on
archive_command = 'cp %p /backup/pgarch/%f'
其中%p
表示WAL日志文件的全路径名,%f
表示不包括路径的WAL日志文件名。
也可以通过如scp
命令把WAL日志复制到其他机器上:
archive_mode = on
archive_command = 'scp %p postgres@192.168.1.100:/backup/pgarch/%f'
但是由于已写完的日志文件才会被归档,因此使用这种方法会导致备库落后主库一个WAL日志文件。
2 流复制
主库的WAL日志一产生则马上传递到从库,有异步/同步两种方式。
从库一直等待新的WAL日志,有新的日志则自动重放,直到主库宕机。类似MySQL的主从binlog复制。
3 主从部署实例:异步流复制Hot Standby数据库部署
Hot Standby数据库即可以提供只读服务的从库。
主从部署步骤如下:
3.1 主库准备工作
创建用户并授权,用于复制数据:
CREATE ROLE replica LOGIN;
ALTER ROLE replica REPLICATION;
同时还需要在pg_hba.conf
中修改访问控制参数。
3.2 从库生成基础备份
使用pg_basebackup工具把整个数据库实例的数据物理地复制出来。
$ pg_basebackup -h 127.0.0.1 -p 5432 -U replica -F p -P -X stream -R -D /usr/local/pgsql/backupdata -l pgbackup202307241034
其中的参数含义:
- -h host:指定连接的数据库的IP地址。
- -p port:指定连接的端口。
- -U username:指定连接的用户名。
- -F p:指定输出格式为plain。
- -P:在备份过程中实时显示进度。
- -X stream:表示备份开始后启动一个流复制连接从主库接受WAL日志。
- -R:是否生成recovery.conf文件。
- -D directory:指定备份的目标目录。
- -l label:指定备份标识。
3.3 启动从库
设置从库hot_standby
参数为on
,让备库作为Hot Standby,可以对外提供只读服务。
通过pg_ctl启动从库后,自动连接上主库。
4 故障切换
进行故障切换前,首先需要修改数据库的wal_log_hints
参数或打开checksum
参数。之后才能使用pg_rewind
命令进行恢复。
wal_log_hints
参数和full_page_writes
参数一样,目的是为了防止块折断。
数据库和操作系统的块大小不一致。PG的一个块大小是8KB,而Linux文件块大小为4KB。当数据库脏块刷盘时,底层由两个OS IO组成,第一次IO刷盘,而第二次发生故障,导致磁盘中数据块不完整。
参数设置为ON后,Checkpoint后一个块的第一次变脏后就要整块写入WAL日志,此后继续修改该块则只用记录修改的信息。如果发生块折断,以全页写入的块为基础恢复磁盘上的折断块。
MySQL使用double_write
解决。可以从磁盘共享表空间恢复数据页。
-
buffer pool脏页刷盘时先写入内存中的doublewrite buffer。
-
从doublewrite buffer写入磁盘共享表空间,连续存储,顺序写。
-
再从doublewrite buffer写入实际表空间文件。
4.1 故障切换流程
-
停主库(模拟故障)。
-
停其他从库。
-
将一个从库升级为主库:
$ pg_ctl promote -D backupdata01/
-
原主库和其他从库上执行
pg_rewind
:$ pg_rewind -D data/ --source-server='host=127.0.0.1 port=5433 user=postgres'
-
为原主库创建standby.signal文件,并启动原主库和其他从库。
4.2 pg_rewind原理
异步主从发生角色切换后,原主库的WAL目录中可能还有未同步到从库的内容,因此原主库无法直接切换为新主库的从库。使用pg_rewind
可以修复原主库,不需要重建整个从库。
pg_rewind对比两个实例的差异点并找到差异点之前的最后一次Checkpoint,通过Checkpoint之后的WAL日志将备库推到和主库一致的状态,修复过程中会涉及到时间线修复。
4.3 时间线修复
先恢复到周三,运行一段时间后又需要恢复到周四。若没有时间线,数据库运行时会产生与旧WAL日志同名文件并覆盖旧日志,导致无法恢复到周四。
引入时间线,每次归档文件恢复完成后,创建一个新的时间线来区别新生成的WAL日志。时间线ID号是WAL文件名组成部分,不会覆盖旧时间线的日志。