邏輯復制是基于邏輯解析,其核心原理是邏輯主庫將Publication中表的WAL日志解析成一定格式并發送給邏輯備庫,邏輯備庫Subscription接收到解析后的WAL日志后進行重做,從而實現表數據同步。

1、物理復制及邏輯復制對比
前文做了PostgreSQL物理復制的部署,其有如下主要優點
- 物理層面完全一致,是主要的復制方式,其類似于Oracle的DG。
- 延遲低,事務執行過程中產生REDO record,實時的在備庫apply,事務結束時,備庫立馬能見到數據。
- 物理復制的一致性、可靠性高,不必擔心數據邏輯層面不一致。
但是其又在實際使用的場景中存在一些無法滿足的需求,例如:
- 無法滿足指定庫或部分表的復制需求。
- 將多個數據庫實例的數據匯聚到同一個目標庫或將一個庫的數據分發到多個不同的庫。
- 不同的版本之間的復制。
- 不同庫名之間的不同步。
對于以上場景,物理復制是無法滿足的,因此邏輯復制應運而生了。
邏輯復制的復制架構圖如下:

圖片來源于《PostgreSQL實戰》
邏輯復制是基于邏輯解析,其核心原理是邏輯主庫將Publication中表的WAL日志解析成一定格式并發送給邏輯備庫,邏輯備庫Subscription接收到解析后的WAL日志后進行重做,從而實現表數據同步。
2、邏輯復制的部署
PS:以下的邏輯從庫可以在新的機器上部署,如在原先的從庫上修改,需停止原實例,并將recovery.conf文件刪除或重命名(如修改為recovery.conf.done)
(1)修改主庫的postgresql.conf
可以基于原先的物理復制的配置文件進行修改,配置邏輯復制主要需調整如下參數
wal_level = logical
max_wal_senders = 10
max_replication_slots = 8
參數簡要說明如下
- wal_level:設置成logical才支持邏輯復制.
- max_wal_senders:由于每個訂閱節點和流復制備庫在主庫上都會占用主庫上一個WAL發送進程,因此此參數設置值需大于max_replication_slots參數值加上物理備庫數量。
- max_replication_slots:設置值需大于訂閱節點的數量。
(2)修改邏輯從庫的postgresql.conf
邏輯從庫的postgresql.conf也可以在物理復制的基礎上修改,與主庫不同的是主要修改如下參數
wal_level = logical
max_replication_slots = 8
max_logical_replication_workers = 8
參數簡要說明:
- wal_level:設置成logical才支持邏輯復制,邏輯從庫可以視情況設置。
- max_replication_slots:設置數據庫復制槽數量,應大于訂閱節點的數量。
- max_logical_replication_workers:設置邏輯復制進程數,應大于訂閱節點的數量,并且給表同步預留一些進程數量,此參數默認值為4。
(3)創建邏輯復制賬號
postgres=# CREATE USER logical_repl REPLICATION LOGIN CONNECTION LIMIT 8 ENCRYPTED PASSWORD 'logical_repl';
CREATE ROLE
邏輯復制用戶需要REPLICATION權限即可,可以不需要SUPERUSER權限,之后需要在發布節點上將需要同步的表賦權給logical_repl用戶,使logical_repl賬號具有對這些表的讀權限。
(4)在邏輯主庫上創建庫及表
/** 創建用于邏輯復制的主庫 */
[postgres@PG32 data]$ createdb sourcedb
[postgres@PG32 data]$ psql -d sourcedb
psql (11.4)
Type "help" for help.
sourcedb=# create table logical_tb1(id int primary key,name varchar(20));
(5)邏輯從庫上創建庫及表
/** 在邏輯從庫上創建不同的庫 */
[postgres@PG33 data]$ createdb desdb
[postgres@PG33 data]$ psql -d desdb
psql (11.4)
Type "help" for help.
desdb=# create table logical_tb1(id int primary key,name varchar(20));
CREATE TABLE
desdb=#
注:邏輯復制的表結構需要手動在從庫創建
(6)在邏輯主庫上創建發布
/** 在發布主庫上創建發布pub1,注意實在sourcedb庫下執行 */
sourcedb=# CREATE PUBLICATION pub1 FOR TABLE logical_tb1;
CREATE PUBLICATION
如果需發布多張表 則表名間用逗號(,)分割,如果需發布所有庫,則將FOR TABLE 調整為FOR ALL TABLES。
此時可以查看到如下信息。
sourcedb=# SELECT * FROM pg_publication;
pubname | pubowner | puballtables | pubinsert | pubupdate | pubdelete | pubtruncate
---------+----------+--------------+-----------+-----------+-----------+-------------
pub1 | 10 | f | t | t | t | t
(1 row)
字段說明如下:
- pubname:指發布的名稱。
- pubowner:指發布的屬主,可以和pg_user視圖的usesysid字段關聯查詢得到屬主具體信息。
- puballtables:是否發布數據庫中的所有表,t表示發布數據庫中所有已存在的表和以后新建的表。
- pubinsert:t表示僅發布表上的INSERT操作。
- pubupdate:t表示僅發布表上的UPDATE操作。
- pubdelete:t表示僅發布表上的DELETE操作。
(7)在邏輯從庫上創建訂閱
desdb=# CREATE SUBSCRIPTION sub1 CONNECTION 'host=192.168.56.32 port=5432 user=logical_repl dbname=sourcedb password=logical_repl' PUBLICATION pub1;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
創建成功后,可以在邏輯主庫上查詢到如下信息:
sourcedb=# SELECT slot_name,plugin,slot_type,database,active,restart_lsn FROM pg_replication_slots where slot_name='sub1';
slot_name | plugin | slot_type | database | active | restart_lsn
-----------+----------+-----------+----------+--------+-------------
sub1 | pgoutput | logical | sourcedb | t | 0/6022D30
(1 row)
在邏輯從庫上可以查詢到如下信息:
desdb=# SELECT * FROM pg_subscription;
subdbid | subname | subowner | subenabled | subconninfo | subslotname | subsynccommit | subpublications
---------+---------+----------+------------+---------------------------------------------------------------------------------------+-------------+---------------+-----------------
24995 | sub1 | 10 | t | host=192.168.56.32 port=5432 user=logical_repl dbname=sourcedb password=logical_repl | sub1 | off | {pub1}
(1 row)
(8)給復制賬號授權
因為步驟2.3中只創建了復制賬號并未對需要復制的表進行授權,因此,此時邏輯從庫的日志中有如下錯誤
2019-10-10 15:57:21.847 CST [27443] ERROR: could not start initial contents copy for table "public.logical_tb1": ERROR: permission denied for table logical_tb1
2019-10-10 15:57:21.848 CST [24722] LOG: background worker "logical replication worker" (PID 27443) exited with exit code 1
因此 ,現在需要對復制用戶授權。
sourcedb=# GRANT USAGE ON SCHEMA public TO logical_repl;
GRANT
sourcedb=# GRANT SELECT ON logical_tb1 TO logical_repl;
GRANT
授權后,顯示正常。
2019-10-10 16:00:25.959 CST [28204] LOG: logical replication table synchronization worker for subscription "sub1", table "logical_tb1" has started
2019-10-10 16:00:25.967 CST [28204] LOG: logical replication table synchronization worker for subscription "sub1", table "logical_tb1" has finished
(9)測試數據同步
在邏輯主庫插入數據。
/** 在主庫插入數據 */
sourcedb=# insert into logical_tb1(id,name) values(1,'a'),(2,'bca');
INSERT 0 2
在邏輯從庫查看結果。
/** 查看數據是否同步完成 */
desdb=# select * from logical_tb1;
id | name
----+------
1 | a
2 | bca
(2 rows)
由此可見數據已同步完成。
(10)添加復制所需的表
在邏輯主庫和邏輯從庫均添加一張新表,并添加到發布列表中。
/** 主庫上創建表結構 */
sourcedb=# create table logical_tb2(id int primary key ,addr varchar(100));
CREATE TABLE
sourcedb=#
/** 從庫上創建表結構 */
desdb=# create table logical_tb2(id int primary key ,addr varchar(100));
CREATE TABLE
/** 在主庫上給邏輯復制賬號授權 */
sourcedb=# GRANT SELECT ON logical_tb2 TO logical_repl;
GRANT
/** 添加新表至發布列表 */
sourcedb=# ALTER PUBLICATION pub1 ADD TABLE logical_tb2;
ALTER PUBLICATION
/** 在主庫查看發布列表中的表名 */
sourcedb=# SELECT * FROM pg_publication_tables;
pubname | schemaname | tablename
---------+------------+-------------
pub1 | public | logical_tb1
pub1 | public | logical_tb2
(2 rows)
此時已加入一張表進入發布列表中。
此時在主庫寫入數據,查看從庫情況如下:
/** 主庫插入一條記錄 */
sourcedb=# insert into logical_tb2(id,addr) values(1,'beijing');
INSERT 0 1
/** 此時在邏輯從庫查看,結果卻沒有數據 */
desdb=# select * from logical_tb2;
id | addr
----+------
(0 rows)
因為還需要在從庫刷新一下訂閱。
/** 此時在從庫刷新訂閱 */
desdb=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION;
/** 刷新完成后再查數據已經有數據了 */
ALTER SUBSCRIPTION
desdb=# select * from logical_tb2;
id | addr
----+---------
1 | beijing
(1 row)
至此,PostgreSQL的邏輯復制也部署完畢。
3、物理復制與邏輯復制特點和應用場景
PostgreSQL的邏輯復制與物理復制的差異比較突出,在使用中可以根據其特點選擇使用哪種復制方式。
- 邏輯訂閱,適合于發布端與訂閱端都有讀寫的情況。
- 邏輯訂閱,更適合于小事務,或者低密度寫(輕度寫)的同步。如果有大事務、高密度寫,邏輯訂閱的延遲相比物理復制更高。
- 邏輯訂閱,適合于雙向,多向同步。
- 物理復制,適合于單向同步。
- 物理復制,適合于任意事務,任意密度寫(重度寫)的同步。
- 物理復制,適合于HA、容災、讀寫分離。
- 物理復制,適合于備庫沒有寫,只有讀的場景。