-
本文发出主要共同探讨目的。找出问题和其他,配置稳定到极限 非常遗憾PostgreSQL官方只支持流复制数据同步,不支持故障检测切换。所以需要第三方来实现。在网上寻找方案过程中发现 Postgresql 流复制/patroni/etcd 组合 这个方案用的挺多。PostgreSQL流复制(数据同步) - 官方Patroni - 第三方开源etcd - 第三方开源整体工作逻辑架构拓扑PostgreSQL + Patroni项目说明部署建议Patroni 与 PostgreSQL 同机部署,以便使用本地控制命令(如 pg_ctl promote),降低网络依赖与权限复杂度。节点规模最少 3 台,最多 9 个节点节点扩展影响从库数量增加会导致主库 WAL 发送压力上升、网络带宽消耗增加,并可能引起复制延迟(replication lag)上升,需合理规划规模。PostgreSQL 角色数据库引擎(负责数据存储与读写)Patroni 角色高可用控制器(负责集群管理、主从选举与故障切换)数据同步方式PostgreSQL 原生流复制(Streaming Replication)核心能力Leader 选举、健康检查、自动故障切换(Failover)、集群状态一致性维护切换机制通过 pg_ctl promote 或等价机制完成主库提升状态依赖依赖 DCS(如 etcd)进行分布式一致性协调与选举仲裁etcd 分布式配置存储(DCS)项目说明角色定位分布式配置存储(DCS),用于为 Patroni 提供一致性状态管理与选举仲裁能力。最少数量3 台最多数量5 台或 7 台(节点过多会增加 Raft 写入确认开销,影响性能;建议使用奇数节点以保证多数派选举效率。)部署建议建议独立部署 etcd,以避免与数据库资源竞争和故障耦合,保证 Raft 选举的稳定性与低延迟。核心职责作为 Patroni 的仲裁与状态存储组件,用于维护集群状态与 Leader 信息,并通过 Raft 协议保证一致性;同时需以集群方式部署以避免单点故障。HAProxy + Keepalived项目说明HAProxy 角色作为四层负载均衡组件,基于 Patroni 提供的健康检查 API 识别主备角色,实现写请求转发至主库、读请求分发至备库。HAProxy 数量2 台(主备模式)即可满足高可用需求Keepalived 角色通过 VRRP 为 HAProxy 提供 VIP,实现主备切换以避免单点故障Keepalived 部署建议和HAProxy 同机部署 方案1(不推荐)概率性逻辑缺陷非技术上无法实现服务器组件其他主 - 服务器-01PostgreSQL + Patroni + etcd(Leader)HAProxy实现读写分离,读负载均衡,识别后端健康节点不推荐etcd和其他组件混合原因纯硬件故障:主设备完全故障,etcd 立刻知道它的状态,其他节点快速接管,虽然中断但逻辑清晰。(此过程无问题)性能缺失:主节点还正常,但(CPU,RAM,DISK)资源占满,etcd 无法正常判断当前正常还是故障 → 进入逻辑混乱状态。后果:混乱导致脑裂、双主、数据冲突,恢复时间从分钟级变成小时级,且可能丢数据。(此过程会有问题)降低资源沾满导致混乱问题技巧(只能降低,无法彻底解决)通过postgresql.conf 配置限制使用内存上线80%通过一种手段限制postgresql数据库CPU使用上线到80%剩余20% 资源留给操作系统和patroni,etcd使用从 - 服务器-02PostgreSQL + Patroni + etcd(Follower)从 - 服务器-03PostgreSQL + Patroni + etcd(Follower)主 - 服务器-04HAProxy + Keepalived备 - 服务器-05HAProxy + Keepalived方案2(不推荐,比方案1优)服务器组件其他主 - 服务器-01PostgreSQL + PatroniHAProxy实现读写分离,读负载均衡,识别后端健康节点HAProxy 占用资源100% 机率比PostgreSQL少很多,所以这个组合比方案1靠谱 仍然存在HAProxy 资源100% 引起集群混乱概率仍需要通过一种手段把每个组件资源使用率限制不要让他占用100%影响其他组件从 - 服务器-02PostgreSQL + Patroni从 - 服务器-03PostgreSQL + PatroniLeader - 服务器-04etcdFollower - 服务器-05etcd + 主HAProxy + KeepalivedFollower - 服务器-06etcd + 备HAProxy + Keepalived方案3(强烈最低配推荐)服务器组件其他主 - 服务器-01PostgreSQL + PatroniHAProxy实现读写分离,读负载均衡,识别后端健康节点为什么PostgreSQL + Patroni 可以混合一台机器比如node1 当前主 Postgresql 引起资源占用 100% 导致 Patroni 无法工作,此时因为Patroni无法给etcd续约当前正常信号,所以集群认为故障直接切换到新节点提升主从 - 服务器-02PostgreSQL + Patroni从 - 服务器-03PostgreSQL + PatroniLeader - 服务器-04etcdFollower - 服务器-05etcdFollower - 服务器-06etcd主 - 服务器-07HAProxy + Keepalived备 - 服务器-08HAProxy + KeepalivedPostgreSQL 此案例服务器规划 HostnameIP组件操作系统数据库版本组件版本pgsql-node-01.itxinxi.net10.10.1.101PostgreSQL + PatroniRocky Linux9 64bitPostgreSQL v17.7Patroni v4.1.2pgsql-node-02.itxinxi.net10.10.1.102PostgreSQL + PatroniRocky Linux9 64bitPostgreSQL v17.7Patroni v4.1.2pgsql-node-03.itxinxi.net10.10.1.103PostgreSQL + PatroniRocky Linux9 64bitPostgreSQL v17.7Patroni v4.1.2etcd-node-01.itxinxi.net10.10.1.104etcdRocky Linux9 64bit-etcd v3.6.11etcd-node-02.itxinxi.net10.10.1.105etcdRocky Linux9 64bit-etcd v3.6.11etcd-node-03.itxinxi.net10.10.1.106etcdRocky Linux9 64bit-etcd v3.6.11haproxy-node-01.itxinxi.net10.10.1.107HAProxy + KeepalivedRocky Linux9 64bit-HAProxy 3.3.x / Keepalived v2.3.4haproxy-node-02.itxinxi.net10.10.1.108HAProxy + KeepalivedRocky Linux9 64bit-HAProxy 3.3.x / Keepalived v2.3.4PostgreSQL安装之后无需手动启动(Patroni 接管postgresql.conf配置参数,初始化,启动服务)[root@pgsql-node-01 ~]# dnf install -y gcc make readline-devel zlib-devel flex bison libxml2-devel libxslt-devel openssl-devel systemd-devel perl lz4-devel krb5-devel pam-devel[root@pgsql-node-01 ~]# wget https://ftp.postgresql.org/pub/source/v17.7/postgresql-17.7.tar.gz[root@pgsql-node-01 ~]# tar zxvf postgresql-17.7.tar.gz[root@pgsql-node-01 ~]# cd postgresql-17.7[root@pgsql-node-01 postgresql-17.7]# ./configure --prefix=/usr/local/postgresql --with-openssl --with-libxml --with-systemd --without-icu --with-lz4 --with-zstd --with-gssapi --with-pam[root@pgsql-node-01 postgresql-17.7]# make -j $(nproc)[root@pgsql-node-01 postgresql-17.7]# make install[root@pgsql-node-01 postgresql-17.7]# useradd postgres[root@pgsql-node-01 postgresql-17.7]# chown -R postgres:postgres /usr/local/postgresql/[root@pgsql-node-01 ~]# echo 'export PATH=/usr/local/postgresql/bin:$PATH' >> /etc/profile[root@pgsql-node-01 ~]# source /etc/profile[root@pgsql-node-01 ~]# psql -Vpsql (PostgreSQL) 17.7所有节点配置hosts[root@pgsql-node-01 ~]# vim /etc/hosts10.10.1.101 pgsql-node-01.itxinxi.net10.10.1.102 pgsql-node-02.itxinxi.net10.10.1.103 pgsql-node-03.itxinxi.net10.10.1.104 etcd-node-01.itxinxi.net10.10.1.105 etcd-node-02.itxinxi.net10.10.1.106 etcd-node-03.itxinxi.net10.10.1.107 haproxy-node-01.itxinxi.net10.10.1.108 haproxy-node-02.itxinxi.netPatroni 源码初始安装(在pgsql-node-01,02,03安装)[root@pgsql-node-01 ~]# wget https://files.pythonhosted.org/packages/14/84/1dea5b4a178d294e47ac4aa9c2b6727dc55fc4d1d292f2beac59a00b3838/patroni-4.1.2.tar.gz[root@pgsql-node-01 ~]# dnf install -y python3-devel postgresql-libs postgresql-devel[root@pgsql-node-01 ~]# cd patroni-4.1.2/[root@pgsql-node-01 patroni-4.1.2]# pip3 install wheel[root@pgsql-node-01 patroni-4.1.2]# pip3 install .[etcd3,psycopg2]patroni.yml 配置内容[root@pgsql-node-01 ~]# mkdir -p /usr/local/postgresql/logs /usr/local/postgresql/patroni/logs /usr/local/postgresql/ssl /usr/local/postgresql/patroni/ssl[root@pgsql-node-01 ~]# vim /usr/local/postgresql/patroni/patroni.yml# =====================================================# Patroni PostgreSQL HA 集群配置# 节点: pgsql-node-01.itxinxi.net (初始主节点)# =====================================================scope: postgres-clustername: pgsql-node-01.itxinxi.netnamespace: /service/# =====================================================# REST API 配置# =====================================================restapi: listen: 0.0.0.0:8008 connect_address: pgsql-node-01.itxinxi.net:8008# =====================================================# DCS 配置 (etcd)# =====================================================etcd3: hosts: - 'etcd-node-01.itxinxi.net:2379' - 'etcd-node-02.itxinxi.net:2379' - 'etcd-node-03.itxinxi.net:2379' protocol: http request_timeout: 30 connect_timeout: 10 host_check_interval: 15# =====================================================# Bootstrap 配置(仅首次初始化使用)# =====================================================bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 master_start_timeout: 300 maximum_lag_on_failover: 1048576 synchronous_mode: true synchronous_mode_strict: true synchronous_node_count: 1 postgresql: use_pg_rewind: true use_slots: true # ========== 集群全局参数(通过 DCS 管理)========== parameters: max_connections: 400 wal_level: replica max_wal_senders: 20 max_replication_slots: 15 # ========== 安全 ========== password_encryption: 'scram-sha-256' # ========== 内存配置 ========== shared_buffers: '8GB' # ========== 复制配置 ========== wal_keep_size: '4GB' # ========== 查询优化 ========== max_worker_processes: 16 pg_hba: - local replication replicator peer - local all all peer - host all all 127.0.0.1/32 scram-sha-256 - host replication replicator 10.10.1.0/24 scram-sha-256 - host all rewind 10.10.1.0/24 scram-sha-256 - host all all 0.0.0.0/0 scram-sha-256 initdb: - encoding: UTF8 - data-checksums - auth: scram-sha-256 - auth-host: scram-sha-256# =====================================================# PostgreSQL 运行时配置# =====================================================postgresql: listen: 0.0.0.0:5432 connect_address: pgsql-node-01.itxinxi.net:5432 data_dir: /usr/local/postgresql/data bin_dir: /usr/local/postgresql/bin pgpass: /usr/local/postgresql/.pgpass failover_priority: 100 authentication: replication: username: replicator password: '234234.com' superuser: username: postgres password: '123123.com' rewind: username: rewind password: '345345.com' parameters: # ========== 连接配置 ========== superuser_reserved_connections: 5 unix_socket_directories: '/tmp' # ========== 内存配置 ========== huge_pages: 'try' work_mem: '8MB' maintenance_work_mem: '512MB' wal_buffers: '16MB' effective_cache_size: '24GB' dynamic_shared_memory_type: 'posix' # ========== WAL 配置 ========== fsync: 'on' wal_log_hints: 'on' max_wal_size: '8GB' min_wal_size: '4GB' checkpoint_completion_target: 0.8 commit_delay: 0 commit_siblings: 5 # ========== 复制配置 ========== hot_standby: 'on' hot_standby_feedback: 'on' max_slot_wal_keep_size: '8GB' wal_sender_timeout: '30s' wal_receiver_timeout: '30s' synchronous_commit: 'on' # ========== 查询优化 ========== random_page_cost: 4.0 default_statistics_target: 200 effective_io_concurrency: 2 max_parallel_workers_per_gather: 2 # ========== 日志配置 ========== logging_collector: 'on' log_directory: '/usr/local/postgresql/logs' log_filename: 'postgresql-%a.log' log_truncate_on_rotation: 'on' log_rotation_age: '1d' log_rotation_size: 0 log_statement: 'ddl' log_line_prefix: '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h ' log_timezone: 'Asia/Shanghai' # ========== 时区 ========== timezone: 'Asia/Shanghai' lc_messages: 'en_US.UTF-8' lc_monetary: 'en_US.UTF-8' lc_numeric: 'en_US.UTF-8' lc_time: 'en_US.UTF-8' default_text_search_config: 'pg_catalog.english' tags: nofailover: false noloadbalance: false clonefrom: false nosync: false# =====================================================# Patroni 日志配置# =====================================================log: dir: /usr/local/postgresql/patroni/logs file_num: 10 file_size: 10485760 log_level: INFO format: '%(asctime)s %(levelname)s: %(message)s' date_format: '%Y-%m-%d %H:%M:%S'[root@pgsql-node-01 ~]# chown -R postgres:postgres /usr/local/postgresql/Patroni 自动启动设置(在pgsql-node-01,02,03)[root@pgsql-node-01 ~]# cat > /etc/systemd/system/patroni.service << 'EOF'[Unit]Description=Patroni - PostgreSQL High AvailabilityAfter=network-online.targetAfter=etcd.serviceWants=network-online.target[Service]Type=simpleUser=postgresGroup=postgresExecStart=/usr/local/bin/patroni /usr/local/postgresql/patroni/patroni.ymlExecReload=/bin/kill -HUP $MAINPID# ========== 重启策略 ==========Restart=on-failure # 异常退出时重启RestartSec=15 # 等待15秒后重启StartLimitInterval=300 # 5分钟内StartLimitBurst=5 # 最多重启5次# ========== 进程管理 ==========KillMode=process # 只杀Patroni,不杀PostgreSQLKillSignal=SIGTERMTimeoutStopSec=90# ========== 资源限制 ==========LimitNOFILE=65536 # 文件句柄限制LimitNPROC=65536 # 进程数限制# ========== 日志 ==========StandardOutput=journalStandardError=journalSyslogIdentifier=patroni[Install]WantedBy=multi-user.targetEOF[root@pgsql-node-01 ~]# systemctl daemon-reload[root@pgsql-node-01 ~]# systemctl start patroni[root@pgsql-node-01 ~]# systemctl enable patronietcd 源码初始安装(在pgsql-node-04,05,06安装)[root@etcd-node-01 ~]# wget https://github.com/etcd-io/etcd/releases/download/v3.6.11/etcd-v3.6.11-linux-amd64.tar.gz[root@etcd-node-01 ~]# tar zxvf etcd-v3.6.11-linux-amd64.tar.gz[root@etcd-node-01 ~]# mkdir -p /usr/local/etcd/{data,bin,logs,conf,wal}[root@etcd-node-01 ~]# cp -r etcd-v3.6.11-linux-amd64/* /usr/local/etcd/bin/[root@etcd-node-01 ~]# echo 'export PATH=/usr/local/etcd/bin:$PATH' >> /etc/profile[root@etcd-node-01 ~]# source /etc/profile[root@etcd-node-01 ~]# useradd -r etcd -s /sbin/nologin[root@etcd-node-01 ~]# chmod 700 /usr/local/etcd/data /usr/local/etcd/wal[root@etcd-node-01 ~]# touch /usr/local/etcd/conf/etcd_conf.yml[root@etcd-node-01 ~]# chown -R etcd:etcd /usr/local/etcd/[root@etcd-node-01 ~]# vim /usr/local/etcd/conf/etcd_conf.yml# etcd 服务器配置文件# 节点的人类可读名称name: 'etcd-node-01.itxinxi.net'# 数据目录路径data-dir: /usr/local/etcd/data# 专用 wal 目录路径wal-dir: /usr/local/etcd/wal# 触发磁盘快照的已提交事务数snapshot-count: 10000# 心跳间隔时间(毫秒)heartbeat-interval: 100# 选举超时时间(毫秒)election-timeout: 1000# 当后端大小超过给定配额时触发告警。0 表示使用默认配额quota-backend-bytes: 8589934592# 用于监听节点间通信的 URL 列表,逗号分隔listen-peer-urls: http://0.0.0.0:2380# 用于监听客户端通信的 URL 列表,逗号分隔listen-client-urls: http://0.0.0.0:2379# 保留的快照文件最大数量(0 表示无限制)max-snapshots: 5# 保留的 wal 文件最大数量(0 表示无限制)max-wals: 5# 跨域资源共享的源白名单,逗号分隔cors:# 向集群其他成员广播的该节点对等点 URL 列表,需要是逗号分隔的列表initial-advertise-peer-urls: http://etcd-node-01.itxinxi.net:2380# 向公众广播的该节点客户端 URL 列表,需要是逗号分隔的列表advertise-client-urls: http://etcd-node-01.itxinxi.net:2379# 用于引导集群的发现 URLdiscovery:# 有效值包括 'exit'、'proxy'discovery-fallback: 'proxy'# 用于访问发现服务的 HTTP 代理discovery-proxy:# 用于引导初始集群的 DNS 域名discovery-srv:# 用于引导的初始集群配置的逗号分隔字符串# 示例: initial-cluster: "infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380"initial-cluster: 'etcd-node-01.itxinxi.net=http://etcd-node-01.itxinxi.net:2380,etcd-node-02.itxinxi.net=http://etcd-node-02.itxinxi.net:2380,etcd-node-03.itxinxi.net=http://etcd-node-03.itxinxi.net:2380'# 引导期间的初始集群令牌initial-cluster-token: 'patroni-cluster'# 初始集群状态:new(新建集群)/ existing(加入现有集群)initial-cluster-state: 'new'# 拒绝会导致法定人数丢失的重配置请求strict-reconfig-check: false# 通过 HTTP 服务器启用运行时性能分析数据enable-pprof: true# 有效值包括 'on'、'readonly'、'off'proxy: 'off'# 端点保持在失败状态的时间(毫秒)proxy-failure-wait: 5000# 端点刷新间隔时间(毫秒)proxy-refresh-interval: 30000# 拨号超时时间(毫秒)proxy-dial-timeout: 1000# 写入超时时间(毫秒)proxy-write-timeout: 5000# 读取超时时间(毫秒)proxy-read-timeout: 0# client-transport-security:# # 客户端服务器 TLS 证书文件路径# cert-file:# # # 客户端服务器 TLS 密钥文件路径# key-file:# # # 启用客户端证书认证# client-cert-auth: false# # # 客户端服务器 TLS 受信任的 CA 证书文件路径# trusted-ca-file:# # # 使用生成的证书进行客户端 TLS# auto-tls: false# peer-transport-security:# # 节点间服务器 TLS 证书文件路径# cert-file:# # # 节点间服务器 TLS 密钥文件路径# key-file:# # # 启用节点间客户端证书认证# client-cert-auth: false# # # 节点间服务器 TLS 受信任的 CA 证书文件路径# trusted-ca-file:# # # 使用生成的证书进行节点间 TLS# auto-tls: false# # # 节点间认证允许的 CN# allowed-cn:# # # 节点间认证允许的 TLS 主机名# allowed-hostname:# 自签名证书的有效期,单位为年self-signed-cert-validity: 1# etcd 的日志级别log-level: infologger: zaplog-outputs: [/usr/local/etcd/logs/etcd.log]# 启用日志轮换enable-log-rotation: truelog-rotation-config-json: '{"maxsize":100, "maxage":30, "maxbackups":10}'# 强制创建一个新的单成员集群force-new-cluster: falseauto-compaction-mode: periodicauto-compaction-retention: "24h"# 限制 etcd 使用特定的 TLS 密码套件# cipher-suites: [# TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,# TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384# ]# 限制 etcd 使用特定的 TLS 协议版本# tls-min-version: 'TLS1.2'# tls-max-version: 'TLS1.3'etcd 自动启动(在pgsql-node-04,05,06)[root@etcd-node-01 ~]# vim /etc/systemd/system/etcd.service[Unit]Description=etcd key-value storeAfter=network-online.targetWants=network-online.target[Service]Type=simpleUser=etcdGroup=etcdExecStart=/usr/local/etcd/bin/etcd --config-file=/usr/local/etcd/conf/etcd_conf.ymlRestart=on-failureRestartSec=10LimitNOFILE=65536[Install]WantedBy=multi-user.target[root@etcd-node-01 ~]# systemctl daemon-reload[root@etcd-node-01 ~]# systemctl start etcd[root@etcd-node-01 ~]# systemctl enable etcd到此 PostgreSQL + Patroni + etcd 部署结束 检查下来所有数据同步,故障切换,集群状态工作一切正常。
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
即将直播
热门标签