什么是逻辑复制
我们前面讲的都是基于物理的流复制,接下来我们来讲讲逻辑复制。pg 逻辑复制基于逻辑解析,将 wal 日志流解析成一定格式输出,从节点收到解析后的数据进行应用。
逻辑复制基于发布(Publisher)与订阅(Subscription)模型。
一个发布者可以有多个发布,一个订阅者上可以有多哥订阅。
一个发布可被多个订阅者订阅,一个订阅只能订阅一个发布者,但可订阅同发布者上的多个不同发布。
逻辑复制的典型用途是:
迁移,跨PostgreSQL大版本,跨操作系统平台进行复制。
CDC,收集数据库(或数据库的一个子集)中的增量变更,在订阅者上为增量变更触发触发器执行定制逻辑。
分拆,将多个数据库集成为一个,或者将一个数据库拆分为多个,进行精细的分拆集成与访问控制。
逻辑复制不同于流复制(物理复制)基于实例级别主从库物理结构上就是一样,逻辑复制可以基于表级别选择性复制。
逻辑复制(Logical Replication)在官方文档里专指”复制-订阅“模式,其实有许多工具可以基于逻辑解析做异构数据库间数据同步。
pg9.4 pglogical插件可以支持逻辑复制(https://github.com/2ndQuadrant/pglogical),pg10开始原生支持逻辑复制。
复制标识
一个被纳入发布中的表,必须带有 复制标识(Replica Identity),只有这样才可以在订阅者一侧定位到需要更新的行,完成UPDATE
与DELETE
操作的复制。
默认情况下,主键 (Primary Key)是表的复制标识,非空列上的唯一索引 (UNIQUE NOT NULL)也可以用作复制标识。如果这两个都没有的话,可以把复制标识设置为 FULL,也就是把整个行当作复制表示,效率很低,每一行修改都需要在订阅者上执行全表扫描,很容易把订阅者拖垮。
INSERT
操作总是可以无视 复制标识 直接进行(因为插入一条新记录,在订阅者上并不需要定位任何现有记录;而删除和更新则需要通过复制标识 定位到需要操作的记录)。如果一个没有 复制标识 的表被加入到带有UPDATE
和DELETE
的发布中,后续的UPDATE
和DELETE
会导致发布者上报错。
表的复制标识模式可以查阅pg_class.relreplident
获取,可以通过ALTER TABLE
进行修改。
ALTER TABLE tbl REPLICA IDENTITY
{ DEFAULT | USING INDEX index_name | FULL | NOTHING };
发布者
一个 发布(Publication) 可以在物理复制主库 上定义。创建发布的节点被称为 发布者(Publisher) 。
一个 发布 是 由一组表构成的变更集合。也可以被视作一个 变更集(change set) 或 复制集(Replication Set) 。每个发布都只能在一个 数据库(Database) 中存在。
发布不同于模式(Schema),不会影响表的访问方式。(表纳不纳入发布,自身访问不受影响)
发布目前只能包含表(即:索引,序列号,物化视图这些不会被发布),每个表可以添加到多个发布中。
除非针对ALL TABLES
创建发布,否则发布中的对象(表)只能(通过ALTER PUBLICATION ADD TABLE
)被显式添加。
发布可以筛选所需的变更类型:包括INSERT
、UPDATE
、DELETE
和TRUNCATE
的任意组合,类似触发器事件,默认所有变更都会被发布。
CREATE PUBLICATION
用于创建发布,DROP PUBLICATION
用于移除发布,ALTER PUBLICATION
用于修改发布。
创建发布
发布创建之后,可以通过ALTER PUBLICATION
动态地向发布中添加或移除表,这些操作都是事务性的。
CREATE PUBLICATION name
[ FOR TABLE [ ONLY ] table_name [ * ] [, ...]
| FOR ALL TABLES ]
[ WITH ( publication_parameter [= value] [, ... ] ) ]
ALTER PUBLICATION name ADD TABLE [ ONLY ] table_name [ * ] [, ...]
ALTER PUBLICATION name SET TABLE [ ONLY ] table_name [ * ] [, ...]
ALTER PUBLICATION name DROP TABLE [ ONLY ] table_name [ * ] [, ...]
ALTER PUBLICATION name SET ( publication_parameter [= value] [, ... ] )
ALTER PUBLICATION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION name RENAME TO new_name
DROP PUBLICATION [ IF EXISTS ] name [, ...];
#创建一个发布,发布两个表中所有更改。
CREATE PUBLICATION mypublication FOR TABLE users, departments;
#创建一个发布,发布所有表中的所有更改。
CREATE PUBLICATION alltables FOR ALL TABLES;
#创建一个发布,只发布一个表中的INSERT操作。
CREATE PUBLICATION insert_only FOR TABLE mydata WITH (publish = 'insert');
#修改发布的动作。
ALTER PUBLICATION insert_only SET (publish='insert,update,delete');
#向发布中添加表。
ALTER PUBLICATION insert_only ADD TABLE mydata2;
#删除发布。
DROP PUBLICATION insert_only;
publication_parameter
主要包括两个选项:
publish
:定义要发布的变更操作类型,逗号分隔的字符串,默认为insert, update, delete, truncate
。publish_via_partition_root
:13后的新选项,如果为真,分区表将使用根分区的复制标识进行逻辑复制。
订阅
订阅(Subscription) 是逻辑复制的下游。定义订阅的节点被称为 订阅者(Subscriber) 。
订阅定义了:如何连接到另一个数据库,以及需要订阅目标发布者上的哪些发布。
逻辑订阅者的行为与一个普通的PostgreSQL实例(主库)无异,逻辑订阅者也可以创建自己的发布,拥有自己的订阅者。
每个订阅者,都会通过一个 复制槽(Replication) 来接收变更,在初始数据复制阶段,可能会需要更多的临时复制槽。
逻辑复制订阅可以作为同步复制的备库,备库的名字默认就是订阅的名字,也可以通过在连接信息中设置application_name
来使用别的名字。
只有超级用户才可以用pg_dump
转储订阅的定义,因为只有超级用户才可以访问pg_subscription
视图,普通用户尝试转储时会跳过并打印警告信息。
逻辑复制不会复制DDL变更,因此发布集中的表必须已经存在于订阅端上。只有普通表上的变更会被复制,视图、物化视图、序列号,索引这些都不会被复制。
发布与订阅端的表是通过完整限定名(如public.table
)进行匹配的,不支持把变更复制到一个名称不同的表上。
发布与订阅端的表的列也是通过名称匹配的。列的顺序无关紧要,数据类型也不一定非得一致,只要两个列的文本表示兼容即可,即数据的文本表示可以转换为目标列的类型。订阅端的表可以包含有发布端没有的列,这些新列都会使用默认值填充。
pg_publication_tables
是由pg_publication
,pg_class
和pg_namespace
拼合而成的视图,记录了发布中包含的表信息。
管理订阅
CREATE SUBSCRIPTION
用于创建订阅,DROP SUBSCRIPTION
用于移除订阅,ALTER SUBSCRIPTION
用于修改订阅。
订阅创建之后,可以通过ALTER SUBSCRIPTION
随时暂停与恢复订阅。
移除并重建订阅会导致同步信息丢失,这意味着相关数据需要重新进行同步。
CREATE SUBSCRIPTION subscription_name
CONNECTION 'conninfo'
PUBLICATION publication_name [, ...]
[ WITH ( subscription_parameter [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name CONNECTION 'conninfo'
ALTER SUBSCRIPTION name SET PUBLICATION publication_name [, ...] [ WITH ( set_publication_option [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name REFRESH PUBLICATION [ WITH ( refresh_option [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name ENABLE
ALTER SUBSCRIPTION name DISABLE
ALTER SUBSCRIPTION name SET ( subscription_parameter [= value] [, ... ] )
ALTER SUBSCRIPTION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
ALTER SUBSCRIPTION name RENAME TO new_name
DROP SUBSCRIPTION [ IF EXISTS ] name;
#创建一个到远程服务器的订阅,复制发布mypublication和insert_only中的表,并在提交时立即开始复制。
CREATE SUBSCRIPTION mysub
CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb password=xxxx'
PUBLICATION mypublication, insert_only;
#创建一个到远程服务器的订阅,复制insert_only发布中的表, 并且不开始复制直到稍后启用复制。
CREATE SUBSCRIPTION mysub
CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb password=xxxx '
PUBLICATION insert_only
WITH (enabled = false);
#修改订阅的连接信息。
ALTER SUBSCRIPTION mysub CONNECTION 'host=192.168.1.51 port=5432 user=foo dbname=foodb password=xxxx';
#激活订阅。
ALTER SUBSCRIPTION mysub SET(enabled=true);
#删除订阅。
DROP SUBSCRIPTION mysub;
subscription_parameter
定义了订阅的一些选项,包括:
copy_data(bool)
:复制开始后,是否拷贝数据,默认为真create_slot(bool)
:是否在发布者上创建复制槽,默认为真enabled(bool)
:是否启用该订阅,默认为真connect(bool)
:是否尝试连接到发布者,默认为真,置为假会把上面几个选项强制设置为假。synchronous_commit(bool)
:是否启用同步提交,向主库上报自己的进度信息。slot_name
:订阅所关联的复制槽名称,设置为空会取消订阅与复制槽的关联。
管理复制槽
每个活跃的订阅都会通过复制槽 从远程发布者接受变更。
通常这个远端的复制槽是自动管理的,在CREATE SUBSCRIPTION
时自动创建,在DROP SUBSCRIPTION
时自动删除。
在特定场景下,可能需要分别操作订阅与底层的复制槽:
创建订阅时,所需的复制槽已经存在。则可以通过
create_slot = false
关联已有复制槽。创建订阅时,远端不可达或状态不明朗,则可以通过
connect = false
不访问远程主机,pg_dump
就是这么做的。这种情况下,您必须在远端手工创建复制槽后,才能在本地启用该订阅。移除订阅时,需要保留复制槽。这种情况通常是订阅者要搬到另一台机器上去,希望在那里重新开始订阅。这种情况下需要先通过
ALTER SUBSCRIPTION
解除订阅与复制槽点关联移除订阅时,远端不可达。这种情况下,需要在删除订阅之前使用
ALTER SUBSCRIPTION
解除复制槽与订阅的关联。
如果远端实例不再使用那么没事,然而如果远端实例只是暂时不可达,那就应该手动删除其上的复制槽;否则它将继续保留WAL,并可能导致磁盘撑爆。
复制冲突
逻辑复制的行为类似于正常的DML操作,即使数据在用户节点上的本地发生了变化,数据也会被更新。如果复制来的数据违反了任何约束,复制就会停止,这种现象被称为 冲突(Conflict) 。
当复制UPDATE
或DELETE
操作时,缺失数据(即要更新/删除的数据已经不存在)不会产生冲突,此类操作直接跳过。
冲突会导致错误,并中止逻辑复制,逻辑复制管理进程会以5秒为间隔不断重试。冲突不会阻塞订阅端对复制集中表上的SQL。关于冲突的细节可以在用户的服务器日志中找到,冲突必须由用户手动解决。
参考链接:管理 | Pigsty
评论