1. 手动主从切换
- 异步流复制下
- 先停主库,再停备库
- 删除备库的
standby.signal
,在原主库下创建一个standby.signal
- 在
pg_hba.conf
文件下加入以下参数(如果没有配0.0.0.0的话,每个备库ip都要单独配置)
# 流复制权限
host replication repl 10.0.0.2(备库ip)/24 scram-sha-256
# 配成这样更方便
host replication repl 0.0.0.0/0 scram-sha-256
- 在备库(原主库)的
postgresql.conf
文件下添加primary_conninfo
# 告诉备库(原主库),你现在的主库是谁
primary_conninfo = 'host=10.0.0.3 port=5432 user=repl password=XXXXXXXX'
# 恢复目标
recovery_target_timeline = 'latest'
- 启动主库(原备库),启动备库(原主库)
- 同步流复制下
- 先停主库,再停备库
- 删除备库的
standby.signal
,在原主库下创建一个standby.signal
- 在
pg_hba.conf
文件下加入以下参数(如果没有配0.0.0.0的话,每个备库ip都要单独配置)
# 流复制权限
host replication repl 10.0.0.2(备库ip)/24 scram-sha-256
# 配成这样更方便
host replication repl 0.0.0.0/0 scram-sha-256
- 在备库(原主库)的
postgresql.conf
文件下添加primary_conninfo
# 告诉备库(原主库),你现在的主库是谁
# 多加了一个参数application_name = slave0
primary_conninfo = 'application_name = slave0 host=10.0.0.3 port=5432 user=repl password=XXXXXXXXX'
# 恢复目标
recovery_target_timeline = 'latest'
- 在
postgresql.auto.conf
文件中加入以下参数(就是上文解决异步转同步失败的方法)
primary_conninfo = 'application_name = slave0 user=repl password=''XXXXXXXXX'' host=10.0.0.3 port=5432 sslmode=disable sslcompression=0 gssencmode=disable krbsrvname=postgres target_session_attrs=any'
- 新主库的postgresql.conf文件需要指定同步备库的application_name
synchronous_standby_names = 'slave0'
- 启动主库(原备库),启动备库(原主库)
primary_conninfo
参数
这个参数可能同时存在于postgresql.conf
和postgresql.auto.conf
文件
- 这个参数不影响主从切换,也就是不管是谁主还是谁从,这个参数可以不用注释掉
- 在
postgresql.conf
文件里的时候仅起到指定主库是谁的作用,application_name
这个配置无效 application_name
这个配置只有在postgresql.auto.conf
文件里才能起作用,用以指定同步备库是谁。postgresql.conf
和postgresql.auto.conf
的host地址需要同时修改,不能只修改一个文件的
- 问题:
可能遇到一些情况,参数没配好,但是又启动了数据库,可能会造成主库或备库出现问题,导致主库上检索不到备库的存在,主备流复制也没有启动。这个就是重搭流复制关系的操作失败(新的备库比新主库数据更超前)。
一般报错会提示:
- wal日志不存在,被removed了
- invalid checkpoint
- 等等一些报错信息
-- 主库上查询同步状态
lxj=# select application_name, state, sync_priority, sync_state from pg_stat_replication;
application_name | state | sync_priority | sync_state
------------------+-------+---------------+------------
(0 rows)
2. pg_rewind
在常见的PostgreSQL双节点高可用构架中,如果主库挂了且主备无延迟,高可用系统会提升老备库为新主库对外服务(pg_ctl promote
)。而对于老主库,则可以有很多处理策略,例如:
- 删掉,重搭新备库。
- 降级为备库,继续服务。
- 作为主库启动
相比来说第一种不是个很好的方案。当数据量比较大时,重搭备库的时间成本太高,系统的可用性降低。但是因为老的主库挂掉的原因多种多样,而老主库也有可能是在挂掉之后又重新作为主库启动起来,这个时候降级并重搭流复制关系的操作就有可能失败(新的备库比新主库数据更超前)。
从PostgreSQL 9.5版本开始提供pg_rewind
命令,它可以使基于同一集群复制的任意两个副本(集群)进行同步。pg_rewind 不需要去读那些未变化的文件块,所以当数据量比较大而变化较小的时候,pg_rewind的速度是很快的。
在同步复制场景下,如果因主库发生临时故障激活了其中一个备库,要想把原主库转换成新主库的备库,仍然需要用pg_rewind处理一下才行,这是因为虽然是同步复制,但并不是把主库的WAL日志完全同步地传输到备库,同步只是到事务提交时才保证其已经传输到了备库,一些未提交事务的WAL日志可能还没有传输到备库,因此激活备库时,还是会丢失一些WAL日志。当然对于用户来说,未提交事务的WAL日志丢失,并不会导致用户数据的丢失。
使用pg_rewind命令需要把参数“wal_log_hints”设置成“on”或主库在建数据库实例时打开checksum。数据库实例打开checksum参数的方法是,在用initdb命令初始化数据库实例时使用“-k”或“--data-checksums”参数。
如果没有把参数“wal_log_hints”或“checksum”打开,运行pg_rewind时会报错:
pg_rewind -D $PGDATA --sourceserver='host=10.0.0.3 user=postgres password=XXXXX'
target server needs to use either data checksums or "wal_log_hints = on"
Failure, exiting
3. 主备故障切换
主库:10.0.0.2
备库:10.0.0.3
使用pg_rewind命令之前,最好确保把参数“wal_log_hints”设置成“on”或主库在建数据库实例时打开了checksum,以避免不必要的错误。
-- 此时主库和备库的数据:
lxj=# select * from user_account;
id | name | age
----+----------------------------------+-----
1 | Tobi | 24
2 | Vincy | 42
3 | Oliver | 53
- 停掉主库(10.0.0.2)
- 在备库(10.0.0.3)上执行
pg_ctl promote
,提升备库为主库
- 新主库(原备库10.0.0.3)执行删除操作(模拟新主库继续工作)
-- 新主库(10.0.0.3)在表user_account上删除一条数据,新主库代替原主库继续工作
lxj=# delete from user_account where id = 3;
DELETE 1
-- 新主库现状
lxj=# select * from user_account;
id | name | age
----+----------------------------------+-----
1 | Tobi | 24
2 | Vincy | 42
(2 rows)
- 原主库(10.0.0.2)转换成新备库,先使用
pg_rewind
处理一下
pg_rewind -D $PGDATA --source-server='host=10.0.0.3 user=postgres password=XXXXXXXXX'
pg_rewind 不能使用root 用户运行,只能使用PostgreSQL superuser 用户运行,例如postgres,也不是使用拥有replication权限的用户。
可能出现target server needs to use either data checksums or "wal_log_hints = on"报错,说明可能没有把参数“wal_log_hints”设置成“on”或主库在建数据库实例时打开了checksum。
- 在新备库(10.0.0.2)上标识自己是备库
touch $PGDATA/standby.signal
- 在备库的
postgresql.conf
文件上标识谁是自己的主库
primary_conninfo = 'application_name = slave0 host=10.0.0.3 port=5432 user=repl password=XXXXXXXX'
# 恢复目标
recovery_target_timeline = 'latest'
- 对应的
postgresql.auto.conf
文件上也要改
primary_conninfo = 'application_name = slave0 user=repl password=''XXXXXXXXX'' host=10.0.0.3 port=5432 sslmode=disable sslcompression=0 gssencmode=disable krbsrvname=postgres target_session_attrs=any'
- 启动新备库(10.0.0.2),在新主库(10.0.0.3)上查看同步效果:
lxj=# select pid,state,client_addr,sync_priority,sync_state from pg_stat_replication;
pid | state | client_addr | sync_priority | sync_state
------+-----------+-------------+---------------+------------
1544 | streaming | 10.0.0.2 | 0 | async
- 在新从库(10.0.0.2)上查看同步效果
$ ps aux | grep receiver
lxj 19674 0.0 0.0 151140 2068 ? Ss 08:08 0:00 postgres: walreceiver streaming 0/27000C58
- 查看新备库(10.0.0.3)的数据
-- 新备库的数据与新主库达成一致,删掉了id为3的数据
lxj=# select * from user_account;
id | name | age
----+----------------------------------+-----
1 | Tobi | 24
2 | Vincy | 42
(2 rows)
- 新备库(10.0.0.2)与新主库(10.0.0.3)完成同步,搭建流复制成功。
问题与解决总结:
建议在执行上述故障切换操作的时候按照步骤来走:
- 开启wal_log_hints = on
- 执行pg_rewind
- 修改好 postgresql.conf 和 postgresql.auto.conf
- 创建standby.signal
- 启动新备库
避免后续出现各种问题,有些问题需要重新搭建备库才可以解决
4. 备机的数据恢复
当备机停止一段时间后再启动时无法搭建流复制。可能日志文件已经被清理:在备机停止期间,如果主机继续写入日志并清理旧的日志文件,那么备机在重新启动时可能无法找到原来时间线上的日志文件,从而无法进行流复制。确保备机重新启动时能够访问到主机的所有日志文件。
解决:
在主机上开启WAL日志持续归档,备机通过获取主机上归档目录中的WAL日志来进行恢复并搭建流复制。
- 主机把wal日志归档到本机目录中,
postgresql.conf
文件配置如下:
# 开启wal日志归档
archive_mode = on
# 配置wal归档命令,这是个shell命令
archive_command = 'test ! -f /home/lxj/ctywork/pgsql/pg_12/archivedir/%f && cp %p /home/lxj/ctywork/pgsql/pg_12/archivedir/%f'
# 恢复命令:主机从本地恢复
restore_command = 'cp /home/lxj/ctywork/pgsql/pg_12/archivedir/%f %p'
# 恢复目标
recovery_target_timeline = 'latest'
- 备机上配置恢复命令:
# postgresql.conf文件
# scp命令:表示从远程ip上获取目录为/home/lxj/ctywork/pgsql/pg_12/archivedir下的文件
restore_command = 'scp -P port username@ip:/home/lxj/ctywork/pgsql/pg_12/archivedir/%f %p'
scp命令需要用ssh命令登录到其他服务器上,因为这个scp命令是写在脚本里的,PostgreSQL在自动启用这个命令的时候不会让用户输入密码,所以这里需要做免密登录。
有两种方案进行:配置主备机的公私钥和使用sshpass在命令行上写入密码。
4.1. 配置主备机的公私钥
# 用postgres用户登录到主pgsql服务器
ssh-keygen -t rsa # 一路回车
scp /home/postgres/.ssh/id_rsa.pub postgres@10.10.22.152:/home/postgres/.ssh/authorized_keys
或者拷贝id_rsa.pub文件到从pgsql上,然后到从上执行以下命令
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
# 登录从pgsql服务器查看权限
chmod 700 /home/postgres/.ssh
chmod 600 /home/postgres/.ssh/authorized_keys
# 测试登录
ssh postgres@10.10.22.152
# 从pgsql库
ssh-keygen -t rsa # 一路回车
scp /home/postgres/.ssh/id_rsa.pub postgres@10.10.22.151:/home/postgres/.ssh/authorized_keys
# 登录从pgsql服务器查看权限
chmod 700 /home/postgres/.ssh
chmod 600 /home/postgres/.ssh/authorized_keys
# 测试登录
ssh postgres@10.10.22.152
4.2. 使用sshpass
安装sshpass
sudo yum -y install sshpass
备机上的恢复命令:
restore_command = 'sshpass -p 'passward' scp -P 7222 lxj@ip:/home/lxj/ctywork/pgsql/pg_12/archivedir/%f %p'
4.3. 测试备机恢复
按照上述配置好,启动备机。
报错:
ssh_askpass: exec(/usr/libexec/openssh/ssh-askpass): No such file or directory
Permission denied, please try again.
FATAL: could not receive data from WAL stream: ERROR: requested WAL segment 000000060000000200000051 has already been removed
Host key verification failed.
上述错误可能是.ssh和.ssh/authorized_keys的权限没有配置好,可以在控制台尝试ssh ip登录到目标服务器看卡看会不会需要输入密码。不行的话说明还是公私钥配置的问题。
恢复成功:
2023-05-18 14:01:24.594 GMT [31251] LOG: restored log file "000000060000000200000050" from archive
2023-05-18 14:01:24.746 GMT [31251] LOG: redo starts at 2/50000028
2023-05-18 14:01:24.746 GMT [31251] LOG: consistent recovery state reached at 2/51000000
2023-05-18 14:01:24.746 GMT [31249] LOG: database system is ready to accept read only connections
主机查看同步情况:
lxj=# select pid,state,client_addr,sync_priority,sync_state from pg_stat_replication;
pid | state | client_addr | sync_priority | sync_state
-------+-----------+-------------+---------------+------------
18053 | streaming | 10.0.0.3 | 1 | sync