基于PostgreSQL流复制的容灾库架构设想及实现

网友投稿 755 2023-05-31

基于***流复制的容灾库架构设想及实现

基于***流复制的容灾库架构设想及实现

一、前言

这几天在对***流复制的架构进行深入研究,其中一个关键的参数:recovery_min_apply_delay引起了我的注意,设置该参数的大概意思是:在进行流复制的时候,备库会延迟主库recovery_min_apply_delay的时间进行应用。比如说,我们在主库上insert10条数据,不会立即在备库上生效,而是在recovery_min_apply_delay的时间后,备库才能完成应用。

另外,我们知道在***中,其mvcc机制并不像***或者MySQL一样,将旧版本数据存放在另外的空间中,而是通过对事务号(xid)的控制对旧版本数据不可见的方式进行实现。所以***中无法实现类似于***的闪回机制。

在日常操作过程中,对表进行delete、truncate、drop等误操作都不能通过闪回来快速恢复。不怕一万,就怕万一,在做数据库维护的6年多里,遇到过的误操作还是很多。那么在***这种无法实现闪回的数据库中,如果出现误操作如何快速恢复呢?

二、架构简介

对于***数据库这种无法进行闪回的数据库来讲,最常用的办法就是通过备份+归档的方式进行数据恢复。但是这种恢复方式也有弊端,当数据库非常大时,恢复全量备份也会非常的慢,而且如果全量备份是一周前或者更久前的,那么恢复归档也会需要比较长的时间。这段时间内,可能业务就会长时间停摆,造成一定的损失。

如果通过流复制延迟特性作为生产数据库的容灾库,则可以从一定程度上解决该问题,其简单架构如下:

三、恢复步骤

***流复制容灾库架构的误操作恢复步骤如下:

1.主库出现误操作,查看流复制的replay状态;

2.在recovery_min_apply_delay时间内,暂停备库的replay;

3.判断主库出现的误操作类型(delete/truncate/drop);

4.根据主库误操作类型,对备库进行相应的操作;

5.通过pg_dump将误操作表导出;

6.在主库对pg_dump出的表进行恢复。

假设当前备库与主库相差10min,则误操作可以分为以下两个场景:

1)delete操作:

首先我们需要知道的是,针对delete操作,***会给相关表加一个ROW EXCLUSIVE锁,而该锁不会对select等dql操作进行阻塞。

所以当我们在主库进行delete误操作后,备库则会晚10min中进行replay。且此时可以对该表进行查询和pg_dump的导出。针对于主库delete误操作,恢复步骤如下:

第一步,查看流复制replay的状态,重点关注replay_lsn字段:

select * from pg_stat_replication; postgres=# select * from pg_stat_replication; -[ RECORD 1 ]----+------------------------------ pid              | 55694 usesysid         | 24746 usename          | repl application_name | walreceiver client_addr      | 192.168.18.82 client_hostname  |  client_port      | 31550 backend_start    | 2021-01-20 09:54:57.039779+08 backend_xmin     |  state            | streaming sent_lsn         | 6/D2A17120 write_lsn        | 6/D2A17120 flush_lsn        | 6/D2A17120 replay_lsn       | 6/D2A170B8 write_lag        | 00:00:00.000119 flush_lag        | 00:00:00.000239 replay_lag       | 00:00:50.653858 sync_priority    | 0 sync_state       | async reply_time       | 2021-01-20 14:11:31.704194+08

此时可以发现数据库中的replay_lsn字段的lsn值要比sent_lsn/write_lsn/flush_lsn都要小;

第二步,为了防止处理或者导出时间过慢而导致的数据同步,立即暂停备库的replay:

select * from pg_wal_replay_pause();

查看同步状态:

postgres=# select * from pg_is_wal_replay_paused();     pg_is_wal_replay_paused  -------------------------  t (1 row)

第三步,在备库查看数据是否存在:

select * from wangxin1;

第四步,通过pg_dump,将表内容导出:

pg_dump -h 192.168.18.182 -p 18802 -d postgres -U postgres -t wangxin1 --data-only --inserts -f wangxin1_data_only.sql

第五步,在主库执行sql文件,将数据重新插入:

psql -p 18801 \i wangxin1_data_only.sql

恢复即完成。

2)truncate和drop:

这里首先需要知道的是,truncate和drop操作会给表加上一个access exclusive锁,该类型锁是***数据库中最严重的锁。如果表上有该锁,则会阻止所有对该此表的访问操作,其中也包括select和pg_dump操作。

所以说,在我们对主库中的某张表进行truncate或者drop后,同样,备库会由于recovery_min_apply_delay参数比主库晚完成truncate或drop动作10min(从参数理论上是这样理解的,但实际并不是)。

那么针对truncate和drop的恢复过程我们也参考delete的方式来进行:

-[ RECORD 2 ]----+------------------------------ pid              | 67008 usesysid         | 24746 usename          | repl application_name | walreceiver client_addr      | 192.168.18.82 client_hostname  |  client_port      | 32122 backend_start    | 2021-01-20 23:33:05.538858+08 backend_xmin     |  state            | streaming sent_lsn         | 7/3F0593E0 write_lsn        | 7/3F0593E0 flush_lsn        | 7/3F0593E0 replay_lsn       | 7/3F059330 write_lag        | 00:00:00.000141 flush_lag        | 00:00:00.000324 replay_lag       | 00:00:11.471699 sync_priority    | 0 sync_state       | async reply_time       | 2021-01-20 23:33:58.303686+08

接下来,为防止处理或导出时间过慢而导致的数据同步,应立即暂停备库的replay:

select * from pg_wal_replay_pause();

查看同步状态:

postgres=# select * from pg_is_wal_replay_paused();     pg_is_wal_replay_paused  -------------------------  t (1 row)

接着,在备库查看数据是否存在:

select * from wangxin1;

但是,此时就会发现问题:数据无法select出来,整个select进程会卡住(pg_dump也一样):

^CCancel request sent ERROR:  canceling statement due to user request

此时,可以对备库上的锁信息进行查询:

select s.pid, s.datname, s.usename, l.relation::regclass, s.client_addr, now()-s.query_start, s.wait_event, s.wait_event_type, l.granted, l.mode, s.query from pg_stat_activity s ,pg_locks l where s.pid<>pg_backend_pid() and s.pid=l.pid;    pid  | datname | usename | relation | client_addr | ?column? |     wait_event     | wait_event_type | granted |        mode         | query  -------+---------+---------+----------+-------------+----------+--------------------+-----------------+---------+---------------------+-------  55689 |         |         |          |             |          | RecoveryApplyDelay | Timeout         | t       | ExclusiveLock       |   55689 |         |         | wangxin1 |             |          | RecoveryApplyDelay | Timeout         | t       | AccessExclusiveLock |  (2 rows)

发现此时truncate的表被锁住了,而pid进程则是备库的recover进程,所以此时我们根本无法访问该表,也就无法做pg_dump操作了。

因此,想要恢复则必须想办法将数据库还原到锁表之前的操作。于是对***的wal日志进行分析查看:

而这一系列操作,我们则可以认为是truncate一张表的正常操作。

由于我们知道checkpoint点是数据库的恢复起始点,那么我们是否可以将数据库恢复到这一点的lsn呢?此时的lsn肯定不会对表进行lock操作,那么我们就可以对该表进行pg_dump操作了。

想法是好的,但是实际操作则没那么顺利。我们可以通过对备库***的配置文件进行修改,加入参数:

recovery_target_lsn= ‘7/3F0547C8’recovery_target_action= ‘pause’

重启数据库。

此时却发现数据库无法启动,通过对日志查看,发现原因竟然是:

这个恢复点,是一致性恢复点之前的点,所以无法正常恢复。

此时就出现了令我们奇怪的点,我们知道checkpoint的两个主要作用是:将脏数据进行刷盘;将wal日志的checkpoint进行记录。此时,肯定是数据库一致的点,但是为什么会报不一致呢?

最后,只能通过一种方式,即pg_resetwal的方式,强制指定备库恢复到我们想要的lsn点:

pg_resetwal -D data1 -x 559 Write-ahead log reset

再进行pg_dump即可。

但是,此时***的主备流复制关系已经被破坏,只能重新搭建或者以其他方式进行恢复(比如pg_rewind)。

四、问题分析

再次返回到进行truncate或drop的恢复步骤中,我们可以发现一个问题,为什么在checkpoint点后、truncate点前,无法将数据库恢复到一致点呢?为什么会报错呢?

按照常理来讲,checkpoint点就是恢复数据库的起始点,也是一致点,但是却无法恢复了。

继续进行详细的探究后发现一个现象:

更形象的来说,此时备库类似于我执行以下命令:

begin;  truncate table;

也就是说,此时我并没有提交,而备库也正在等待我进行提交,所以此时误操作表会被锁定。

为了验证想法,在大佬的帮助下,又对***的源码进行查看,发现猜想原因确实没错:

在/src/backend/access/transam/xlog.c中,对于recovery_min_apply_delay参数有以下的一段描述:

/* * Is it a COMMIT record? * * We deliberately choose not to delay aborts since they have no effect on * MVCC. We already allow replay of records that don't have a timestamp, * so there is already opportunity for issues caused by early conflicts on * standbys. */

不过在实际的生产环境中,我们通常会把recovery_min_apply_delay参数设置的较大,而在这之间,一般都会有一些其他的事务进行操作,当主库出现误操作(哪怕说truncate/drop),只要及时发现,我们可以暂停replay的步骤,停在正常的事务操作下,此时误操作的表的事务还没有执行,那么这个容灾库还是比较有作用的。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SQL Server什么时候启用“锁定内存页”选项 (Windows)
下一篇:Redis中一个String类型引发的惨案
相关文章