PostgreSQL数据库复制

这篇博客的行文可能有点“官方”,因为是之前为了发期刊而写的初稿。

postgersql是一款功能强大的开源数据库系统。它基于并扩展了SQL语言,使之支持更复杂的数据类型。它提供了丰富的接口,可以很容易地扩展它的功能,比如最知名的地理空间数据库扩展PostGIS。PostgreSQL以其稳定性、可扩展性以及软件背后开源社区的奉献精神而赢得了良好的声誉,并得到了广泛的应用。

数据库复制是指在多个数据库节点之间完整一致地共享数据的过程。不论其具体使用场景如何,数据库复制的目的都是为了实现数据库的高可用性与负载均衡。高可用性是指当主节点失效的时候,备用节点能快速接手工作;负载均衡是指将工作分流给多个节点,以平衡每个节点的压力。

我们希望多个数据库节点可以无缝地协同工作,但是实际上,只有用做只读服务器的节点能够相对容易的结合在一起。大部分情况下,数据库收到的请求都是读写混合的,对于任意节点的一次写入动作必须实时的传播给其他节点,这样才能保证后续对任意节点的读取请求能够返回一致的结果。如何保证多个节点的数据一致性是数据库复制技术的难题,也是数据库协同工作的核心。PostgreSQL支持多种实现数据库复制的技术方案,每种方案都各有其优缺点,都是为了满足特定环境下的特定需求。

有的方案只允许在一个节点上进行写操作,这个可写的节点称为主节点(主服务器)。其他从主节点复制数据的则称为从节点(从服务器)。另外,从节点又根据其是否可读分为温备与热备。温备是指从节点只有当被提升为主节点的时候才允许用户连接进行读写操作,热备是指从节点任何时候都可以处理读取请求。除了主从复制方案,有时候我们会需要双向复制方案,来实现任意节点都可读写的需求。

有的方案是同步复制的,即一个数据修改事务只有到所有节点均提交了该事务后才能被认为是提交成功的。相反有些方案是异步的,即允许事务在某个节点上的提交与它被传播到其他节点之间存在延迟。异步复制会导致在某些时刻两个节点的数据不一致,但是它对于网络延迟的要求比同步复制宽容的多。

有的方案只能对整个数据库节点进行复制,有的则允许细分到每个库,甚至每张表的复制。

1 流复制

1.1 预写式日志(WAL)

预写式日志(WAL, Write Ahead Log)是PostgreSQL内置复制技术的基石,其作用与Oracle数据库的重做日志类似,都是用于保证数据的一致性以及事务的完整性。WAL的核心概念是数据文件的修改必须在这些动作被日志记录之后才被写入[1]。只要有了数据库的一个早期基础备份,以及后续的WAL文件,那么不论是在系统崩溃需要恢复的时候,还是在另一台机器重建数据库的时候,都可以很容易的通过重放WAL中的日志项来还原数据库,达到WAL记录中任意时间点的状态。我们将这种恢复数据库的方案叫做时间点恢复(PITR, Point In Time Recovery),这也是PostgreSQL流复制与逻辑复制的基本原理。

WAL文件被存储在数据库目录的pg_wal子目录下,默认大小为16MB。可以通过postgresql.conf配置文件中的wal_level配置项来决定有多少日志信息输出到WAL中。wal_level分为三个等级,minimal表示只输出最基本的日志信息,以供系统崩溃或异常停机时候恢复数据所用;replica表示输出足够的日志信息以支持WAL归档和复制,这是WAL的默认等级;logical表示增加支持逻辑解码所需的信息,这是PostgreSQL内置的逻辑复制需要的等级。

1.2 异步流复制

流复制是从PostgreSQL 9.0之后新增的一项主从复制功能。流复制即WAL的流式复制,它将先前PITR那种手工备份WAL文件并发送到备份节点进行恢复的工作内化到了数据库系统中。首先在主从节点之间建立一个TCP连接,然后主节点在WAL记录生成时即将它们以流式发送到从节点,而不必等到记录写入到WAL文件[2]

PostgreSQL的流复制默认使用的是异步方式,主节点上某个事务的提交,不必等待日志传输到从节点便可返回成功。这有可能导致从节点上的数据变更会短暂滞后于主节点,此时如果在从节点上进行查询,就会返回与主节点不一致的结果。但是异步流复制仍可以保证数据的最终一致性,因为从节点作为热备只接受读取请求,不会发生数据冲突导致复制失败的情况。另外正是由于不需要等待从节点的确认,所以很大程度上保证了主节点的写入性能。在网络延迟较高、主从数据一致性要求较低的环境中,异步流复制方案是一个很好的选择。

假设主节点ip为192.168.58.128, 从节点ip为192.168.58.130。使用管理员账号postgres进行复制连接,异步流复制的配置大概如下:

1.修改主节点的pg_hba.conf文件,追加一行以允许复制连接

host replication postgres 192.168.58.130/32 trust

2.修改主节点postgresql.conf文件中如下几个配置

listen_addresses = ‘*’
wal_level = replica
wal_keep_segments = 64

3.重启主节点,然后在从节点执行pg_basebackup命令初始化数据库,并使用-R参数自动生成recovery.conf文件

pg_basebackup -h 192.168.58.128 -U postgres -D /var/lib/pgsql/10/data -P –R

4.修改从节点的postgresql.conf文件然后重启

hot_standby = on

 

至此,异步流复制即配置完成。可在主库上的pg_stat_replication视图中查询复制连接的状态。

1.3 同步流复制

除了异步方式,PostgreSQL还支持同步流复制。在这种方式下,主节点上每一个写事务的提交都将一直等待,直到确认该事务已经被写入主节点和从节点磁盘上的WAL文件中。当然,只读事务不需要等待。同步流复制能够保证主从节点之间任何时刻的数据一致性,但是降低了主节点的写入性能。

在配置好异步流复制的情况下,只需要修改两个配置参数即可切换成同步流复制:

synchronous_commit = on # 开启WAL的同步提交,默认on

synchronous_standby_names = 'slave001' # 启用同步流复制的从库名字,该名字在从库的recovery.conf文件中配置。

 

2 逻辑复制

逻辑复制是从PostgreSQL 10开始引入的新特性。原有的流复制是基于WAL的物理复制,从节点直接接收主节点的WAL数据流。而逻辑复制是基于对WAL的逻辑解码,它将二进制存储级别的日志信息解码成更易懂的形式,比如SQL语句流,然后将解码后的数据流传输到从节点上进行恢复。逻辑复制是异步的。相比于物理复制(即流复制),逻辑复制有着以下几个特点:

  • 逻辑复制可以细分到选择部分表进行复制,流复制只能对整个数据库实例进行复制。因此逻辑复制经常用来将多个数据库的部分内容整合到一个更大的数据仓库。
  • 逻辑复制可以选择只复制DML的某种操作(INSERT、UPDATE和UPDATE)或者他们的任意组合,即使这样会导致数据不一致。
  • 逻辑复制可以在不同版本的PostgreSQL之间进行复制,流复制必须保证主版本号相同。
  • 逻辑复制只支持对表的复制,不支持视图、外部表等对象。
  • 逻辑复制不支持复制DDL操作,即CREATE、TRUNCATE、DROP等这些操作是无法通过逻辑复制进行同步的。

逻辑复制使用发布/订阅模型。主节点即发布节点,可以创建多个发布,允许将多个表注册到一个发布中。加入发布的表通常需要配置有复制标识(Replica Identity,是表结构中的一个属性,可以通过ALTER TABLE命令进行修改,默认使用主键作为复制标识。),其作用是用来标记哪一行数据被UPDATE或DELETE。如果加入发布的表没有指定复制标识,表上的UPDATE/DELETE将会报错[3]。INSERT操作不需要复制标识。

每个发布可以被多个订阅节点(从节点)订阅。每个订阅节点可以定义多个订阅,但是需要确保多个订阅不会包含重复的发布对象。每个订阅定义了到发布节点的数据库连接以及它想要订阅的一组发布。订阅节点也可以创建发布,以支持级联的逻辑复制。需要注意的是,订阅节点默认是可写的,建议最好在订阅节点上关闭订阅表的写权限,避免由于订阅表的意外写入导致复制失败。

逻辑复制的配置大体只需要如下几个步骤:

1.修改发布节点的postgresql.conf文件中的配置项,并重启数据库。

wal_level = logical

2.在发布节点上创建一个发布:

CREATE PUBLICATION pub_name FOR TABLE table1, table2;

3.在订阅节点上创建相同的表结构table1与table2。(实际上,订阅表的表结构允许大于发布表,只需要保证包含发布表的所有列并且多余的列都有默认值或允许为null。)

4.在订阅节点上创建一个订阅:

CREATE SUBSCRIPTION sub_name CONNECTION 'dbname=demo host=192.168.58.10 user=postgres password=postgres' PUBLICATION pub_name;

如果想要检查逻辑复制的状态,可以在发布节点上执行语句:SELECT * from pg_publication; 也可以在订阅节点上执行:SELECT * from pg_stat_subscription; 。

3 双向复制

流复制与逻辑复制是PostgreSQL内置的复制技术,他们都属于主从复制方式,主节点支持读写操作,从节点作为热备只处理读操作。但是在某些情况下,我们希望从节点也能执行写操作,并且能将数据变更写回主节点。我们称之为双向复制(Bi-Directional Replication),也叫多主复制(Multi-Master Replication, Master-Master Replication)。PostgreSQL原生并不支持双向复制,幸运的是,由于PostgreSQL良好的可扩展性,有不少第三方厂家提供了支持双向复制的技术方案,Bucardo与Postgres-BDR是其中应用最为广泛的。

3.1 Bucardo

Bucardo是由End Point Corporation提供的开源异步复制方案,它能很好的支持双向复制,并且支持多于两个主节点的架构。Bucardo的同步通过PostgreSQL触发器来记录变化,并利用“NOTIFY”消息通知机制实现高效同步[4]。Bucardo的核心是一个由Perl语言编写的后台守护进程,它负责监听数据库发出的NOTIFY请求,这个NOTIFY消息由触发器触发,然后Bucardo守护进程连接数据库并将修改的数据应用到其他节点。

使用Bucardo的时候,需要在一个主节点上创建bucardo数据库,守护进程需要的所有定义信息都存储在主bucardo数据库中,包括复制中涉及的所有数据库连接,所有要复制的表以及如何复制每个表。除了主bucardo数据库外,Bucardo还会在每个需要复制的数据库下面创建一个bucardo模式,该模式中的表包含了所有跟数据修改相关的信息。

Bucardo有两种同步模式,pushdelta和fullcopy。默认为pushdelta,该方式只推送修改过的行,这要求表结构中必须存在主键或者唯一索引。否则就必须使用fullcopy,fullcopy表示全表复制,每次复制都需要先清空目标表,然后再使用COPY命令导入数据。因此fullcopy的效率要低很多,对于经常修改的表或者数据量大的表,不建议使用fullcopy模式。

由于Bucardo的复制机制是基于触发器的,所以可以细分到表级别的复制,而不需要整个数据库甚至整个实例复制。Bucardo不支持复制DDL操作,因为PostgreSQL不支持DDL触发器。

在使用Bucardo复制方案之前,需要先从其官方下载Bucardo的源码包进行编译安装。虽然Bucardo是双向复制的,每个节点都是主节点,但是Bucardo程序只需要部署在一个节点上,bucardo库也是。我们将这个节点称作B节点。

1.修改B节点的pg_hba.conf文件,追加一行以允许bucardo用户登录。

local all bucardo trust

2.在B节点上执行如下命令,创建bucardo数据库与bucardo用户。

bucardo install

3.假定要复制的数据库名字为demo,两个节点的demo库下有相同的表结构。

4.在B节点上执行如下命令,添加目标数据库。

bucardo add database master1 dbname=demo host=192.168.58.128
bucardo add database master2 dbname=demo host=192.168.58.130

5.在B节点上执行如下命令,添加目标数据库的所有表结构,并创建herd。herd是Bucardo中表的集合,用来作为同步的源。

bucardo add all tables --herd=h1_2 db=master1
bucardo add all tables --herd=h2_1 db=master2

6.在B节点上执行如下命令,创建Bucardo同步。

bucardo add sync sync_1_2 relgroup=h1_2 db=master1,master2
bucardo add sync sync_2_1 relgroup=h2_1 db=master2,master1

7.启动Bucardo
bucardo start

8.检查Bucardo状态
bucardo status

 

3.2 Postgres-BDR

Postgres-BDR是由2ndQuadrant提供的异步双向复制方案,它的实现基于PostgreSQL 9.4引入的逻辑复制的特性,实际上,这项特性正是由BDR项目组研发的。Postgres-BDR从版本2后变为闭源的商业软件,因此本文所论讨的仅限于BDR1。BDR1可以看作是PostgreSQL 9.4的补丁,安装BDR1时需要删除原版的PostgreSQL,重新安装打过“BDR1补丁”的版本:postgresql-bdr94-bdr。

与基于触发器的复制方案相比,BDR不存在“写入放大”的情况。这是基于触发器的复制固有的缺陷,以Bucardo为例,每次数据修改除了要写入原始的数据表外,还需要将数据写入到Bucardo的记录表中。BDR复制依赖的是读取与解析WAL,不需要额外记录数据修改

另外,基于触发器的复制方案都需要有一个外部守护进程负责监听数据变化并传递数据。BDR依赖于PostgreSQL自身的后台工作者进程(Background Worker Processes),不需要启动额外的程序。

与逻辑复制相比,BDR支持对DDL操作的复制。但是BDR不支持细分到表级别的选择,只支持数据库级别的复制。

关于BDR1的配置:

1.在两个节点安装好postgresql-bdr94-bdr后,需要修改postgresql.conf配置文件中的如下几个配置:

listen_addresses = '*'
wal_level = logical
shared_preload_libraries = 'bdr'
track_commit_timestamp = on

2.修改pg_hba.conf,允许postgres账号进行replication连接。然后重启两个节点。

host replication postgres 192.168.58.0/24 trust

3.假设需要复制的库名demo,在两个节点的demo库中执行如下语句,创建BDR扩展。

CREATE EXTENSION btree_gist;
CREATE EXTENSION bdr;

4.在节点1的demo库中执行如下语句,创建BDR组。

SELECT bdr.bdr_group_create( local_node_name := 'node1', node_external_dsn := 'port=5432 dbname=demo host=192.168.58.128 user=postgres password=postgres'

);

5.在节点2的demo库中执行如下语句,加入BDR组。

SELECT bdr.bdr_group_join(local_node_name := 'node2', node_external_dsn := 'port=5432 dbname=demo host=192.168.58.130 user=postgres password=postgres', join_using_dsn := 'port=5432 dbname=demo host=192.168.58.128 user=postgres password=postgres'

);

6.至此对demo库的BDR双向复制就配置成功了。可以使用如下语句来检查BDR复制状态。

select * from bdr.bdr_nodes;
select * from bdr.bdr_connections;

 

4 各方案对比

表1 文中几种复制方案的对比

 

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top