文章
问答
冒泡
Linux 搭建PostgreSQL集群 - 原理篇(四)

本篇我们来聊聊PG主从复制的一些原理和参数的作用,PS: 主要是填坑!


本篇因为干货较多,建议配合前几篇一起观看

一、概述和历史

PG 的主从随着版本的更新也一直在做调整,PG 9.1是一个较大的调整版本 ,另一个调整较大的版本是 PG12


9.1 版本之前没有流复制, 复制的逻辑是基于 wal 日志, 大致的逻辑就是主库将更新写进wal日志中,一个日志文件写完之后,主库将日志发送给从库,从库重放日志完成同步,这个阶段主从同步延迟非常大,我们暂时不做讨论。


9.1 引入了主从流复制,把复制的颗粒度从 wal 日志文件变成了记录,所以这个版本开始,PG就可以进行同步复制了。


同时这个版本引入了hot_standby


这个功能开启就意味着,即使在进行主从复制的时候,从库也可以提供查询服务,而如果关闭的话则从库无法在主从复制时提供该服务。

二、主从复制的简易流程

  1. 从库启动之后 会有两个线程用于主从 一个是walreceiver, 一个是 startup,

    walreceiver线程的作用,是用来接受主库的记录并将记录刷新到从库的本地缓存,

    startup线程是将刷新的数据真正在从库上重放,实现从库和主库的数据一致。

  2. 从库启动之后呢,就会从从库的postgresql.auto.conf中获取主库的信息,然后walreceiver开始通过配置中的信息和主库进行tcp的握手连接,

    值得注意的是,这里的握手呢其实是个周期性动作,如果一次连不上后面还是会不停的尝试去握手的

还记得上一节我们停掉主库的时候,一直在提示报错信息吗?

[postgres@localhost data]$ pg_ctl stop2023-02-07 14:30:05.606 CST [89828] FATAL:  could not connect to the primary server: could not connect to server: Connection refused
                Is the server running on host "127.0.0.1" and accepting
                TCP/IP connections on port 6100?

这个就是因为我们停掉主库以后, walsender没了, walreceiver和他保持的连接断了,自然就开始尝试重连,但是连不上,所以就报错啦!而且还是周期性报错!

  1. 主库启动的时候,会启动walsender进程,这个进程就是和从库的walreceiver进行握手保持连接,从而进行数据交换的进程

  2. 当walreceiver 和 walsender握手成功之后, walreceiver 会把最新的lsn 发送给主库,lsn 大家可以类比mysql 中的gtid,这个东西的作用是记录位置,一般的来说,执行事务或者配置变动,lsn都会增长,因为从库是从主库那边backup来的,所以,在没有同步之前,lsn对应的每个节点执行的命令都是一样的,这样如果两个库的lsn不一致,就说明主从之间数据还没有同步。

    从库把 lsn 发给主库, 主库检查一下,和自己的lsn差距多少,然后把差距的那些发给备库,备库就开始与主库进行数据同步,这个阶段呢因为量可能比较大,所以他是一个追赶的阶段。

  3. 一直到lsn追上了之后,主从直接就开始正式的进入流复制阶段了

三、流复制的简易流程

  1. 开始进入流复制阶段之后呢, 如果主库接收了新事务, 就会把数据写入wal日志中

  2. walsender将新记录发送给walreceiver

  3. 如果是异步复制,发送完成,主库直接执行commit, 不管从库是不是执行成功了

  4. 如果是同步复制,发送完成,主库会通过 syncRepWaitForLSN() 函数等待从库回复

  5. walreceiver将主库发送过来的记录写到wal缓存中,返回一个可达标识ACK给主库,表示告诉主库你发送的记录我收到了

  6. 如果主库的 synchronous_commit 参数配置的是 off 那么主库接收到第五步中的ACK的标识就会直接提交commit,但是如果是on ,主库还得再等等。

#synchronous_commit = on                # synchronization level;
                                        # off, local, remote_write, remote_apply, or on
  1. walreceiver 将缓存中的数据真正刷新到wal文件中(真正的落盘), 然后会通知startup去进行数据重放,此时返回一个ACK可达标识给主库,表示你的数据我不仅收到了,还落盘了。

  2. 如果主库的 synchronous_commit 参数配置是 on , 那么在收到这个ACK标识之后会执行提交commit。

注意:


为保证效率,如果主库每次都要等所有从库都返回ACK再提交那么 主库的写效率会非常的低,所以通常情况下,主库是只等待 sync_state是 sync 的从库返回了ACK标识之后,就会直接执行commit。


也就是说,如果你的状态是这样的:

postgres=# select application_name, sync_priority, sync_state from pg_stat_replication;
 application_name | sync_priority | sync_state 
------------------+---------------+------------
 ldr              |             1 | sync
 rdr              |             2 | potential
(2 rows)

主库发送record等待的是ldr的回复,如果ldr因为网络波动暂时没有用回复,但是rdr他回复了,此时主库会执行commit吗?


答案是不会,主库一定是等待sync状态的实例返回可达标识才会执行commit。


所以,大家明白,如果你的从库都是sync, 那意味着你要等每个从库都有反馈才能commit, 如果但凡一个从库出问题了,主库就一直阻塞,不能提供写服务了,这肯定是不行的。

四、部分配置和参数的解读

1. 配置

synchronous_standby_names = ''

用来配置同步以及优先级的,可以将同步的从库的cluster_name 写入这个参数中,例如 synchronous_standby_names = 'ldr'

当你写了多个实例的时候,如果没有用()当成一个整体,则就是优先级配置,第一个实例是sync,后面的则是potential,例如 synchronous_standby_names = 'ldr, rdr'

如果想强制配多个同步,PG提供了两个函数 FIRST ANY

FIRST 指定一种基于优先的同步复制并且让事务提交等待,直到它们的WAL记录被复制到基于优先级选中的所要求数量的同步后备上为止。在列表中出现较早的后备被给予较高的优先级,并且将被考虑为同步后备。其他在这个列表中位置靠后的后备服务器表示可能的同步后备。如果任何当前的同步后备由于任何原因断开连接,它将立刻被下一个最高优先级的后备所替代。


举个例子:

synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'

在这个例子中,如果有四个后备服务器s1s2s3s4在运行,两个后备服务器s1s2将被选中为同步后备,因为它们出现在后备服务器名称列表的前部。s3是一个潜在的同步后备,当s1s2中的任何一个失效, 它就会取而代之。s4则是一个异步后备因为它的名字不在列表中。

方法ANY指定一种基于规定数量的同步复制并且让事务提交等待,直到它们的WAL记录至少被复制到列表中所要求数量的同步后备上为止。


举个例子:

synchronous_standby_names = 'ANY 2 (s1, s2, s3)'

在这个例子中,如果有四台后备服务器s1s2s3以及s4正在运行,事务提交将会等待来自至少其中任意两台后备服务器的回复。s4是一台异步后备,因为它的名字不在该列表中。

synchronous_commit = on

用来配置主库在同步复制时释放latch 执行commit的时机 on 则意味着sync状态的从库已经将写操作持久化到存储中了, 默认是on, off则表示数据只刷新到了缓存中,存在丢失的可能。

2. 参数

pg_stat_replication 表:

sync_state : 表示一台后备服务器的(从节点)的服务状态
    async: 这台后备服务器是异步的。
    sync: 这台后备服务器是同步的。
    potential: 这台后备服务器现在是异步的,但可能在当前的同步后备失效时变成同步的。
    quorum: 这台后备服务器被当做规定数量后备服务器的候选。
sync_priority :  

在基于优先的同步复制中,这台后备服务器被选为同步后备的优先级。在基于规定数量的同步复制中,这个值没有效果。
优先级越高,数字越小,0是一种特殊状态,表示异步

第二个参数说的很明白了,这个优先级呢是针对备胎选择优先级,这个参数要配合sync_state字段才有意义, 当你有多个potential备胎的时候,如果正房sync无了,那么这些备胎怎么转正呢?就要依赖这个优先级参数了。


但是如果正房还在,备胎的优先级就没啥意义,永远是备胎。

postgresql

关于作者

chason
只要学不死,就往死里学
获得点赞
文章被阅读