在本篇博文中,我們將重點介紹複製和更具體的雙向複製。要在 PostgreSQL 中實現雙向複製,我們需要模組 pglogical。您可能想知道邏輯解碼和 pglogical 之間的區別。
本質上,邏輯解碼起源於PgLocigal。將 PgLocial 視為功能更強大的模組,而邏輯解碼嵌入到 PostgreSQL 發行版中。
我們將建立自定義 PostgreSQL Docker 映像並安裝 PgLogical。
# Use the official PostgreSQL image as base FROM postgres:15 USER root RUN apt-get update; apt-get install postgresql-15-pglogical -y USER postgres
|
我們還需要一個 PostgreSQL 配置,以啟用 PgLogical 複製和衝突解決。
listen_addresses = '*' port = 5432 max_connections = 20 shared_buffers = 128MB temp_buffers = 8MB work_mem = 4MB wal_level = logical max_wal_senders = 3 track_commit_timestamp = on shared_preload_libraries = 'pglogical' pglogical.conflict_resolution = 'first_update_wins'
|
讓我們來分析一下。我們新增了 pglogical 並啟用了 track_commit_timestamp。透過啟用此引數,PostgreSQL 會跟蹤事務的提交時間。這對於衝突解決策略至關重要。
現在讓我們看看衝突解決。我們選擇了“first_update_wins”,因此如果兩個事務在同一行上操作,則將考慮最先完成的事務。
在表上設定雙向複製。由於我們使用Docker,因此我們將為 PostgreSQL 提供初始化指令碼。
該指令碼將:
- 啟用 pglogical
- 建立表
- 新增目標節點
- 插入要進行測試的行
#!/bin/bash set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL ALTER SYSTEM RESET shared_preload_libraries; CREATE EXTENSION pglogical; create schema test_schema; create table test_schema.employee( id SERIAL PRIMARY KEY, firstname TEXT NOT NULL, lastname TEXT NOT NULL, email TEXT not null, age INT NOT NULL, salary real, unique(email) ); SELECT pglogical.create_node( node_name := '$TARGET', dsn := 'host=$TARGET port=5432 dbname=$POSTGRES_DB user=$POSTGRES_USER password=$POSTGRES_PASSWORD'); SELECT pglogical.replication_set_add_table('default', 'test_schema.employee', true); insert into test_schema.employee (id,firstname,lastname,email,age,salary) values (1,'John','Doe 1','john1@doe.com',18,1234.23);
EOSQL
|
現在讓我們使用 docker compose 建立例項。version: '3.1' services: postgres-a: build: ./pglogicalimage restart: always environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres TARGET: postgres-b volumes: - ./config/postgresql.conf:/etc/postgresql/postgresql.conf - ./init:/docker-entrypoint-initdb.d command: - "-c" - "config_file=/etc/postgresql/postgresql.conf" ports: - 5431:5432 postgres-b: build: ./pglogicalimage restart: always environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres TARGET: postgres-a volumes: - ./config/postgresql.conf:/etc/postgresql/postgresql.conf - ./init:/docker-entrypoint-initdb.d command: - "-c" - "config_file=/etc/postgresql/postgresql.conf" ports: - 5432:5432
|
我們可以透過執行以下命令來啟動並執行例項docker compose
由於兩個例項都已啟動並正在執行,因此我們需要啟用複製。因此,我們將節點相互訂閱。
在第一個節點上執行
SELECT pglogical.create_subscription( subscription_name := 'postgres_b', provider_dsn := 'host=postgres-b port=5432 dbname=postgres user=postgres password=postgres', synchronize_data := false, forward_origins := '{}' );
|
在第二個節點執行SELECT pglogical.create_subscription( subscription_name := 'postgres_a', provider_dsn := 'host=postgres-a port=5432 dbname=postgres user=postgres password=postgres', synchronize_data := false, forward_origins := '{}' );
|
您可以使用任何適合您的 PostgreSQL 客戶端。或者,您也可以使用 Docker 映象中附帶的 psql 客戶端。
例如:
登入第一個節點
docker compose exec postgres-a psql --username postgres --dbname postgres
登入第二個節點
docker compose exec postgres-b psql --username postgres --dbname postgres
現在讓我們看看衝突解決如何進行。
在第一個節點上,我們將執行以下程式碼片段
BEGIN; UPDATE test_schema.employee SET lastname='first wins'; before committing start transaction on postgres-b COMMIT;
|
不要立即按提交,而是花點時間在提交事務之前在第二個節點上啟動以下事務。
BEGIN; UPDATE test_schema.employee SET lastname='second looses'; make sure transaction on node postgres-a is committed first. COMMIT;
|
該事務將在 postgres-a 中發生的事務之後提交。讓我們檢查一下 postgres-a-1 上的日誌
postgres-a-1 | 2024-05-01 07:10:45.128 GMT [70] LOG: CONFLICT: remote UPDATE on relation test_schema.employee (local index employee_pkey). Resolution: keep_local. postgres-a-1 | 2024-05-01 07:10:45.128 GMT [70] DETAIL: existing local tuple {id[int4]:1 firstname[text]:John lastname[text]:first wins email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} xid=748,origin=0,timestamp=2024-05-01 07:10:42.269227+00; remote tuple {id[int4]:1 firstname[text]:John lastname[text]:second looses email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} in xact origin=1,timestamp=2024-05-01 07:10:45.125791+00,commit_lsn=0/16181C0 postgres-a-1 | 2024-05-01 07:10:45.128 GMT [70] CONTEXT: apply UPDATE from remote relation test_schema.employee in commit before 0/16181C0, xid 747 committed at 2024-05-01 07:10:45.125791+00 (action #2) from node replorigin 1
|
- postgres-a 上發生的事務首先完成。
- Postgres-a 從節點 postgres-b 的事務接收了複製資料。
- 對提交時間戳進行了比較,因為 postgres-a 上事務的提交時間戳較早,
因此解決方案是保留本地更改。我們可以在 postgres-b 上看到反向
postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] LOG: CONFLICT: remote UPDATE on relation test_schema.employee (local index employee_pkey). Resolution: apply_remote. postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] DETAIL: existing local tuple {id[int4]:1 firstname[text]:John lastname[text]:second looses email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} xid=747,origin=0,timestamp=2024-05-01 07:10:45.125791+00; remote tuple {id[int4]:1 firstname[text]:John lastname[text]:first wins email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} in xact origin=1,timestamp=2024-05-01 07:10:42.269227+00,commit_lsn=0/1618488 postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] CONTEXT: apply UPDATE from remote relation test_schema.employee in commit before 0/1618488, xid 748 committed at 2024-05-01 07:10:42.269227+00 (action #2) from node replorigin 1
|
讓我們在資料庫中檢查一下結果。
postgres=# SELECT*FROM test_schema.employee; id | firstname | lastname | email | age | salary ----+-----------+------------+---------------+-----+--------- 1 | John | first wins | john1@doe.com | 18 | 1234.23
|
正如預期的那樣,第一筆事務被保留了下來。
總結一下:
- 我們同時開始了兩項事務
- 我們改變了同一行
- 我們接受了最先完成的事務的變更
就是這樣。希望您玩得開心,現在您有了另一個滿足您需求的工具。