accounts/docs/pglogical.md
2025-10-08 17:56:42 +08:00

19 KiB
Raw Blame History

pglogical 双向逻辑复制部署指南

本文档介绍如何在两台 PostgreSQL 16 数据库之间部署 pglogical 扩展,实现支持 TLS 的异步双向逻辑复制,并包含冲突处理、监控及与 Bucardo 的对比。

目录

  1. pglogical 简介
  2. 环境准备
  3. 安装 pglogical 扩展
  4. 配置 PostgreSQL 参数
  5. 创建复制用户
  6. 创建节点与复制集
  7. 建立双向订阅
  8. 验证复制状态
  9. 冲突解决策略
  10. TLS 配置示例
  11. 常用维护命令
  12. 监控指标
  13. 性能与延迟优化建议
  14. 优缺点总结
  15. 推荐部署参数模板
  16. 与 Bucardo 的对比
  17. 附录SQL 脚本模板

pglogical 简介

特性 说明
类型 基于 WAL 的逻辑复制扩展(由 2ndQuadrant 开发,后并入 EDB/PGDG
复制粒度 表级 / 库级,支持选择性复制
拓扑 单向、一主多从、多主(双向)均可
延迟 秒级(异步逻辑流式复制)
冲突 可配置(默认“先到先得”,支持自定义冲突解决)
DDL 支持 不自动复制 DDL需两端结构一致
安全 继承 PostgreSQL 的 TLS / SCRAM / 证书机制
推荐版本 PostgreSQL 13~17pglogical 2.x/3.x

与 Bucardo 相比pglogical 更现代、稳定、性能更高且原生支持异步双向复制multi-master和 TLS 加密。

环境准备

假设部署架构如下:

节点 主机名 数据库 角色
A cn-homepage.svc.plus account node_cn
B global-homepage.svc.plus account node_global

两台节点均运行 PostgreSQL 16并且网络互通。

安装 pglogical 扩展

在两台节点上安装 pglogical 软件包:

  • Ubuntu / Debian

    sudo apt install postgresql-16-pglogical
    
  • Red Hat / CentOS

    sudo yum install pglogical_16
    

安装完成后,在 account 数据库中创建扩展验证:

psql -d account -c "CREATE EXTENSION IF NOT EXISTS pglogical;"

创建 repl_user基础复制用户

在主库Publisher执行以下操作 sudo -u postgres psql

执行 SQL

-- 创建用于逻辑/物理复制的底层用户
CREATE ROLE repl_user WITH LOGIN REPLICATION PASSWORD 'StrongPassword123!';
-- 确认创建成功
\du repl_user

输出应包含:

Role name | Attributes
-----------+-------------------------------
repl_user  | Replication, Login

创建 pglogical逻辑复制应用用户

仍在 PostgreSQL 中执行:

-- 创建逻辑复制用的应用账户
CREATE ROLE pglogical WITH LOGIN REPLICATION PASSWORD 'StrongPass';
-- 授权访问业务数据库(假设名为 account
GRANT ALL PRIVILEGES ON DATABASE account TO pglogical;

⚠️ 注意pglogical 账号仅需复制与读写权限,无需 SUPERUSER。 生产环境建议使用强密码、并限制来源 IP。

配置 PostgreSQL 参数

在两台节点的 /etc/postgresql/16/main/postgresql.conf 中设置逻辑复制所需参数:

# 修改 PostgreSQL 监听地址
listen_addresses = '*'

# 逻辑复制基础
wal_level = logical
max_wal_senders = 10
max_replication_slots = 10
max_worker_processes = 10
max_logical_replication_workers = 8

# 建议优化
shared_preload_libraries = 'pglogical'
track_commit_timestamp = on

配置访问控制pg_hba.conf

编辑主库Publisher上的 /etc/postgresql/16/main/pg_hba.conf 限定允许的远程节点:

# 本地管理
local   all             postgres                                peer
host    all             all             127.0.0.1/32            md5

# 允许复制与逻辑复制(加密连接)
hostssl replication     repl_user       <peer_ip>/32            scram-sha-256
hostssl all             pglogical       <peer_ip>/32            scram-sha-256

其中 <peer_ip> 为另一台数据库节点的 IP 地址或域名。

启用 TLSpostgresql.conf

scripts/generate-postgres-tls.sh

#!/usr/bin/env bash
set -e

# ============================================================
# PostgreSQL 专用 TLS 证书生成脚本(含 *.svc.plus + 双 IP
# 作者SVC.PLUS PostgreSQL Server TLS Generator
# ============================================================

TLS_DIR="/etc/postgres-tls"
CA_DIR="$TLS_DIR/ca"
SERVER_DIR="$TLS_DIR/server"

echo ">>> [1/6] 创建目录结构 ..."
sudo mkdir -p "$CA_DIR" "$SERVER_DIR"
cd "$TLS_DIR"

# ============================================================
# 1. 创建私有 CA 根证书
# ============================================================
echo ">>> [2/6] 生成 PostgreSQL 专用私有 CA ..."
sudo openssl genrsa -aes256 -out "$CA_DIR/ca.key.pem" 4096
sudo chmod 600 "$CA_DIR/ca.key.pem"

sudo openssl req -x509 -new -nodes -key "$CA_DIR/ca.key.pem" -sha256 -days 3650 \
  -subj "/C=CN/O=SVC.PLUS PostgreSQL Authority/OU=DB Security/CN=SVC.PLUS PostgreSQL Root CA" \
  -out "$CA_DIR/ca.cert.pem"

# ============================================================
# 2. 生成服务器证书
# ============================================================
echo ">>> [3/6] 生成服务器私钥与 CSR ..."
sudo openssl genrsa -out "$SERVER_DIR/server.key.pem" 2048
sudo chmod 600 "$SERVER_DIR/server.key.pem"

sudo openssl req -new -key "$SERVER_DIR/server.key.pem" \
  -subj "/C=CN/O=SVC.PLUS PostgreSQL Server/OU=DB/CN=global-homepage.svc.plus" \
  -out "$SERVER_DIR/server.csr.pem"

# SAN 扩展配置
cat <<EOF | sudo tee "$SERVER_DIR/server.ext" >/dev/null
basicConstraints=CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.svc.plus
DNS.2 = svc.plus
DNS.3 = global-homepage.svc.plus
DNS.4 = cn-homepage.svc.plus
IP.1  = 167.179.72.223
IP.2  = 47.120.61.35
EOF

# 签发服务器证书2年有效
echo ">>> [4/6] 使用 SVC.PLUS PostgreSQL Root CA 签发服务器证书 ..."
sudo openssl x509 -req -in "$SERVER_DIR/server.csr.pem" \
  -CA "$CA_DIR/ca.cert.pem" -CAkey "$CA_DIR/ca.key.pem" \
  -CAcreateserial -out "$SERVER_DIR/server.cert.pem" \
  -days 730 -sha256 -extfile "$SERVER_DIR/server.ext"

# fullchain
sudo cat "$SERVER_DIR/server.cert.pem" "$CA_DIR/ca.cert.pem" | sudo tee "$SERVER_DIR/server.fullchain.pem" >/dev/null

# ============================================================
# 3. 安装到 PostgreSQL 标准路径
# ============================================================
echo ">>> [5/6] 安装证书到 PostgreSQL SSL 目录 ..."
sudo install -o postgres -g postgres -m 600 "$SERVER_DIR/server.key.pem" /etc/ssl/private/svc.plus-postgres.key
sudo install -o postgres -g postgres -m 644 "$SERVER_DIR/server.fullchain.pem" /etc/ssl/certs/svc.plus-postgres.crt
sudo install -o postgres -g postgres -m 644 "$CA_DIR/ca.cert.pem" /etc/ssl/certs/svc.plus-postgres-ca.crt

# ============================================================
# 4. 输出后续操作提示
# ============================================================
echo "==============================================================="
echo "✅ [SVC.PLUS PostgreSQL TLS] 已生成并安装完成"
echo ""
echo "请在 /etc/postgresql/16/main/postgresql.conf 中添加或确认以下配置:"
echo ""
echo "  ssl = on"
echo "  ssl_cert_file = '/etc/ssl/certs/svc.plus-postgres.crt'"
echo "  ssl_key_file  = '/etc/ssl/private/svc.plus-postgres.key'"
echo "  ssl_ca_file   = '/etc/ssl/certs/svc.plus-postgres-ca.crt'"
echo ""
echo "⚙️  然后执行: sudo systemctl restart postgresql"
echo ""
echo "📦 客户端(订阅端)请复制 CA 根证书:"
echo "  /etc/postgres-tls/ca/ca.cert.pem"
echo "至客户端路径:"
echo "  /var/lib/postgresql/.postgresql/root.crt"
echo "权限600属主 postgres"
echo ""
echo "🔍 验证命令示例:"
echo "  openssl s_client -connect 167.179.72.223:5432 -starttls postgres -servername global-homepage.svc.plus"
echo ""
echo "👑 证书主题SVC.PLUS PostgreSQL Server"
echo "包含 SAN: *.svc.plus, global-homepage, cn-homepage, IP(167.179.72.223, 47.120.61.35)"
echo "==============================================================="

创建好的证书分发到在两台节点上

两台节点上执行:

PostgreSQL 客户端必须存在一份 受信任 CA 根证书文件, 路径固定为 /var/lib/postgresql/.postgresql/root.crt (属于 postgres 用户,权限必须是 600

# 1⃣ 创建目录 
sudo -u postgres mkdir -p /var/lib/postgresql/.postgresql

# 2⃣ 从 global-homepage 拉取服务器的 CA 根证书
cp /etc/ssl/certs/svc.plus-postgres-ca.crt /var/lib/postgresql/.postgresql/root.crt

# 3⃣ 设置权限
sudo chown postgres:postgres /var/lib/postgresql/.postgresql/root.crt
sudo chmod 600 /var/lib/postgresql/.postgresql/root.crt

在两台节点上执行:编辑 /etc/postgresql/16/main/postgresql.conf 检查下面配置是否存在

ssl = on
ssl_cert_file = '/etc/ssl/certs/svc.plus.crt'
ssl_key_file  = '/etc/ssl/private/svc.plus.key'

重启 PostgreSQL 生效

sudo systemctl restart postgresql@16-main 或(简写方式): sudo systemctl restart postgresql

验证角色与访问

1 查看角色列表 sudo -u postgres psql -c "\du" 应看到:

repl_user | Replication, Login pglogical | Replication, Login

2 订阅端测试连接

在另一台节点测试 TLS 登录: psql "host=<publisher_ip> user=pglogical password=StrongPass dbname=account sslmode=require"

成功进入 account=> 提示符表示逻辑复制用户配置完毕

创建节点与复制集

双向架构概览

┌───────────────────────────┐
│   🌍 global-homepage      │
│   node_name = node_global │
│   publishes → node_cn     │
│   subscribes ← node_cn    │
└───────────────────────────┘
               ▲  │
               │  ▼
┌───────────────────────────┐
│   🇨🇳 cn-homepage          │
│   node_name = node_cn     │
│   publishes → node_global │
│   subscribes ← node_global│
└───────────────────────────┘

两个节点都:

  • 拥有 pglogical 扩展;
  • 注册自己的 node
  • 定义相同的 replication_set
  • 创建互为订阅create_subscription

准备阶段

两台主机global 与 cn都要具备相同的数据库结构和 schema,在两台机器上都执行.

现在进入目标数据库account sudo -u postgres psql -d account

CREATE EXTENSION IF NOT EXISTS pglogical;
SELECT pglogical.create_replication_set('rep_all');
SELECT pglogical.replication_set_add_all_tables('rep_all', ARRAY['public']);

如果提示 “already exists” 可以忽略。

在 global-homepage 创建节点与订阅

-- 注册 global 节点Provider
SELECT pglogical.create_node(
    node_name := 'node_global',
    dsn := 'host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full'
);

-- 创建对 cn-homepage 的订阅
SELECT pglogical.create_subscription(
    subscription_name := 'sub_from_cn',
    provider_dsn := 'host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full',
    replication_sets := ARRAY['rep_all'],
    synchronize_structure := false,
    synchronize_data := false,
    forward_origins := '{}'
);

在 cn-homepage 创建节点与订阅

-- 注册 cn-homepage 节点Provider
SELECT pglogical.create_node(
    node_name := 'node_cn',
    dsn := 'host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full'
);

-- 创建对 global-homepage 的订阅
SELECT pglogical.create_subscription(
    subscription_name := 'sub_from_global',
    provider_dsn := 'host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full',
    replication_sets := ARRAY['rep_all'],
    synchronize_structure := false,
    synchronize_data := true,
    forward_origins := '{}'
);

参数说明:

  • synchronize_structure = false:假设双方的表结构已对齐。
  • synchronize_data = true:初次创建时自动进行数据同步。
  • forward_origins = '{}':防止回环复制,避免来自对端的写入再次回传。
  • sslmode = verify-full:开启 TLS 并校验证书。

验证复制状态

常用验证命令:

SELECT * FROM pglogical.show_subscription_status();
SELECT * FROM pglogical.show_node_info();

查看流复制进度:

SELECT application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;

日志中出现如下信息表明表同步完成:

pglogical: initial copy of table "public.users" finished

冲突解决策略

pglogical 默认策略为 “first commit wins”。在开启 track_commit_timestamp = on 后,可以使用以下策略:

策略 含义
error 发生冲突时报错并终止复制
apply_remote 使用远端数据覆盖本地
keep_local 保留本地数据,忽略远端变更
latest_commit 保留提交时间更晚的行
custom 调用自定义函数处理冲突

示例:

SELECT pglogical.alter_subscription_options(
  subscription_name := 'sub_from_b',
  options := '{conflict_resolution=latest_commit}'
);

TLS 配置示例

使用 libpq 连接参数即可启用 TLS

SELECT pglogical.create_subscription(
    subscription_name := 'sub_from_b',
    provider_dsn := 'host=pgB.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full sslrootcert=/etc/ssl/rootCA.crt sslcert=/etc/ssl/client.crt sslkey=/etc/ssl/client.key',
    replication_sets := ARRAY['rep_all']
);

sslmode 支持 requireverify-caverify-full,推荐使用 verify-full 并确保证书 CN/SAN 与主机名匹配。

常用维护命令

操作 SQL 命令
暂停订阅 SELECT pglogical.alter_subscription_disable('sub_from_global');
恢复订阅 SELECT pglogical.alter_subscription_enable('sub_from_global', true);
删除订阅 SELECT pglogical.drop_subscription('sub_from_global');
删除节点 SELECT pglogical.drop_node('node_cn');

监控指标

表 / 视图 说明
pglogical.show_subscription_status() 订阅状态(延迟、复制槽、错误)
pg_stat_replication WAL 流复制进度
pglogical.replication_set 当前同步的表集合
pglogical.local_sync_status 同步阶段initial / catching-up / ready

性能与延迟优化建议

参数 推荐值 说明
max_replication_slots ≥ 10 允许更多并发订阅
max_wal_senders ≥ 10 支持更多并发流复制连接
maintenance_work_mem ≥ 128MB 提高初始数据复制效率
synchronous_commit off 降低写入延迟(异步复制场景)
wal_compression on 降低网络传输量
subscription_apply_delay 060 秒 可配置延迟重放,满足业务需求

优缺点总结

优点 缺点
原生逻辑复制,性能远优于 Bucardo 不复制 DDL需保证结构一致
支持 TLS / SCRAM / 双向复制 需要安装扩展(非纯 SQL
冲突处理策略灵活(latest_commit / custom 不适合同一行的高并发双写场景
延迟低(秒级) 不支持系统表复制

推荐部署参数模板

配置
节点 A/B PostgreSQL 16 + pglogical 3.6
通道 TLS (sslmode=verify-full)
复制方向 双向
延迟 210 秒
冲突策略 latest_commit
初始同步 synchronize_data = true
同步集 业务表(usersidentitiessessions
DDL 管理 GitOps + 同步迁移脚本
监控 Grafana + pg_stat_replication + pglogical 状态视图

与 Bucardo 的对比

维度 pglogical Bucardo
复制机制 WAL 逻辑流 触发器 + 队列
延迟 秒级 秒级至分钟级
性能
冲突控制 内置多策略 Perl 自定义
安全 原生支持 TLS 依赖 libpq TLS
部署复杂度 中(需扩展) Perl 脚本)
推荐场景 跨 Region 双向 / 实时异步复制 异地多活、低写负载场景

附录SQL 脚本模板

可将上述配置整理为以下 SQL 脚本:

setup-node-a.sql

-- 节点 A 初始化
CREATE EXTENSION IF NOT EXISTS pglogical;
SELECT pglogical.create_node(
    node_name := 'node_a',
    dsn := 'host=pgA.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full'
);
SELECT pglogical.create_replication_set('rep_all');
SELECT pglogical.replication_set_add_all_tables('rep_all', ARRAY['public']);
SELECT pglogical.create_subscription(
    subscription_name := 'sub_from_b',
    provider_dsn := 'host=pgB.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full',
    replication_sets := ARRAY['rep_all'],
    synchronize_structure := false,
    synchronize_data := true,
    forward_origins := '{}'
);

setup-node-b.sql

-- 节点 B 初始化
CREATE EXTENSION IF NOT EXISTS pglogical;
SELECT pglogical.create_node(
    node_name := 'node_b',
    dsn := 'host=pgB.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full'
);
SELECT pglogical.create_replication_set('rep_all');
SELECT pglogical.replication_set_add_all_tables('rep_all', ARRAY['public']);
SELECT pglogical.create_subscription(
    subscription_name := 'sub_from_a',
    provider_dsn := 'host=pgA.svc.plus port=5432 dbname=account user=pglogical password=StrongPass sslmode=verify-full',
    replication_sets := ARRAY['rep_all'],
    synchronize_structure := false,
    synchronize_data := true,
    forward_origins := '{}'
);

verify-replication.sql

-- 验证订阅状态
SELECT * FROM pglogical.show_subscription_status();
SELECT * FROM pglogical.show_node_info();

-- 检查复制进度
SELECT application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;

以上脚本可根据实际业务需要调整数据库名称、节点信息及复制集内容。