这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

服务接入

PostgreSQL 服务的抽象与接入:让应用不用关心数据库在哪里。

如果您只有一分钟,请记住这张图:

flowchart TB
    subgraph App["💻 应用代码"]
        A1["// 只需知道服务名,不用关心具体实例<br/>db.connect('postgres://user@pg-test:5433/mydb')  // 读写服务<br/>db.connect('postgres://user@pg-test:5434/mydb')  // 只读服务"]
    end

    App --> HAProxy

    subgraph HAProxy["🔀 HAProxy 服务层"]
        direction LR
        S1[":5433 primary<br/>读写业务"]
        S2[":5434 replica<br/>只读查询"]
        S3[":5436 default<br/>管理操作"]
        S4[":5438 offline<br/>ETL分析"]
    end

    HAProxy --> Cluster

    subgraph Cluster["🐘 底层集群(对应用透明)"]
        direction LR
        C1["pg-test-1<br/>(Primary)"] --> C2["pg-test-2<br/>(Replica)"] --> C3["pg-test-3<br/>(Offline)"]
    end

    Value["✅ 价值:主库故障?自动切换,应用无需改代码。读写分离?改端口即可,无需改逻辑。"]

核心价值:应用只需连接"服务",不用知道具体实例在哪。主库切换、从库扩容、故障恢复——对应用完全透明。


本章内容

章节 说明 核心问题
服务抽象 服务的定义、类型与配置 什么是服务?有哪些类型?
接入方式 DNS、VIP、端口多种接入方式 应用如何连接数据库?

为什么需要服务

直连 IP 的问题

flowchart TB
    subgraph Problems["❌ 硬编码 IP 的困境"]
        Config["应用配置:db.host = '10.10.10.11:5432'"]

        P1["❌ 主库故障<br/>• 10.10.10.11 挂了,新主库在 10.10.10.12<br/>• 需要改配置、重启应用<br/>• 凌晨3点被叫醒改配置"]

        P2["❌ 读写分离<br/>• 想让读请求走从库,需要改代码<br/>• 每增加一个从库都要改配置<br/>• 负载均衡逻辑自己写"]

        P3["❌ 扩容困难<br/>• 加了新从库,应用不知道<br/>• 要一个个通知应用改配置<br/>• 运维负担重"]
    end

服务抽象的解决方案

flowchart TB
    subgraph Solutions["✅ 服务抽象的优势"]
        Config2["应用配置:db.host = 'pg-test:5433'"]

        S1["✅ 故障透明<br/>• 主库故障?HAProxy 自动路由到新主库<br/>• 应用无需改配置,无需重启<br/>• 继续睡觉,系统自己处理"]

        S2["✅ 读写分离简单<br/>• 读写用 :5433,只读用 :5434<br/>• 改端口就完成读写分离<br/>• 无需修改业务代码"]

        S3["✅ 扩容透明<br/>• 加从库?HAProxy 自动发现并加入负载均衡<br/>• 应用完全无感知<br/>• 运维和开发解耦"]
    end

四种默认服务

每个 PostgreSQL 集群自动创建四种服务:

flowchart TB
    subgraph Services["🐘 pg-test 集群服务"]
        subgraph Primary[":5433 pg-test-primary 读写服务"]
            PR1["用途:核心业务读写、OLTP<br/>路由:→ 主库 Pgbouncer → 主库 PostgreSQL<br/>目标:有且仅有一个(当前主库)"]
        end

        subgraph Replica[":5434 pg-test-replica 只读服务"]
            RP1["用途:只读查询、读扩展、报表<br/>路由:→ 从库 Pgbouncer → 从库 PostgreSQL(负载均衡)<br/>目标:所有从库(无从库时回落到主库)"]
        end

        subgraph Default[":5436 pg-test-default 直连服务"]
            DF1["用途:DDL、管理操作、大事务<br/>路由:→ 主库 PostgreSQL(绕过连接池)<br/>特点:不经过 Pgbouncer,适合需要持久连接的场景"]
        end

        subgraph Offline[":5438 pg-test-offline 离线服务"]
            OF1["用途:ETL、数据分析、慢查询<br/>路由:→ 离线库 PostgreSQL(不影响在线业务)<br/>目标:标记为 offline 或 pg_offline_query=true 的实例"]
        end
    end

服务对比

服务 端口 目标 连接池 典型用途
primary 5433 主库 核心业务读写
replica 5434 从库 只读查询、报表
default 5436 主库 DDL、管理操作
offline 5438 离线库 ETL、分析查询

接入方式

DNS 接入(推荐)

通过集群域名访问,最简洁的方式:

# 读写业务
psql postgres://app_user:password@pg-test:5433/mydb

# 只读查询
psql postgres://app_user:password@pg-test:5434/mydb

# 管理操作
psql postgres://dba_user:password@pg-test:5436/mydb

# ETL 分析
psql postgres://etl_user:password@pg-test:5438/mydb

VIP 接入

通过虚拟 IP 访问,适合传统应用:

# VIP 自动绑定到主库节点
psql postgres://app_user:password@10.10.10.3:5433/mydb

直连实例

直接访问特定实例,适合调试:

# 通过实例域名
psql postgres://app_user:password@pg-test-1:5433/mydb

# 通过 IP 直连
psql postgres://app_user:password@10.10.10.11:5432/mydb

端口规划

flowchart TB
    subgraph Ports["📋 端口分配"]
        subgraph DBLayer["🐘 数据库层"]
            DB1["5432  PostgreSQL 直连端口"]
            DB2["6432  Pgbouncer 连接池端口"]
        end

        subgraph ServiceLayer["🔀 服务层(HAProxy)"]
            SL1["5433  primary 服务(读写,经连接池)"]
            SL2["5434  replica 服务(只读,经连接池)"]
            SL3["5436  default 服务(直连,绕过连接池)"]
            SL4["5438  offline 服务(离线,绕过连接池)"]
        end

        subgraph MgmtPorts["⚙️ 管理端口"]
            MP1["8008  Patroni REST API(健康检查)"]
            MP2["9101  HAProxy 管理页面"]
        end
    end

连接池考量

何时使用连接池(5433/5434)

flowchart TB
    subgraph PoolScenario["✅ 适合连接池的场景"]
        PS1["• 高频短连接:Web 应用的 API 请求"]
        PS2["• 连接数多:数百上千个客户端连接"]
        PS3["• 连接复用:减少数据库连接开销"]
        PS4["📋 使用端口:5433(读写)/ 5434(只读)"]
    end

何时绕过连接池(5436/5438)

flowchart TB
    subgraph DirectScenario["⚠️ 需要绕过连接池的场景"]
        DS1["• DDL 操作:CREATE TABLE、ALTER TABLE"]
        DS2["• 大事务:长时间运行的事务"]
        DS3["• Session 变量:SET 命令、临时表"]
        DS4["• 管理操作:备份、复制槽管理"]
        DS5["📋 使用端口:5436(直连主库)/ 5438(离线库)"]
    end

实际场景

标准业务接入

# 应用配置
database:
  # 主连接 - 读写业务
  primary:
    url: postgres://app_user:password@pg-prod:5433/appdb
    pool_size: 20

  # 只读连接 - 查询业务
  replica:
    url: postgres://app_user:password@pg-prod:5434/appdb
    pool_size: 50

ETL 数据抽取

# 使用离线服务,避免影响在线业务
pg_dump -h pg-prod -p 5438 -U etl_user appdb > backup.sql

DBA 管理操作

# 使用直连服务执行 DDL
psql -h pg-prod -p 5436 -U dba_user -c "CREATE INDEX ..."

故障切换流程

flowchart TB
    subgraph Normal["✅ 正常状态"]
        N1["应用 → pg-test:5433 → HAProxy → pg-test-1 (Primary)"]
    end

    Normal --> |pg-test-1 故障| Failover

    subgraph Failover["🔄 故障切换过程"]
        F1["1. Patroni 检测到主库故障"]
        F2["2. pg-test-2 提升为新主库"]
        F3["3. HAProxy 健康检查发现角色变化"]
        F4["4. 流量自动路由到 pg-test-2"]
        F1 --> F2 --> F3 --> F4
    end

    Failover --> After

    subgraph After["✅ 故障后"]
        A1["应用 → pg-test:5433 → HAProxy → pg-test-2 (Primary)"]
        A2["🎉 应用无需修改连接串,无需重启"]
    end

接下来

深入了解服务的细节:

相关话题:

1 - 服务抽象

服务的定义、类型与配置方式。

服务是集群能力的命名抽象,通过 HAProxy 端口对外暴露,实现流量路由与负载均衡。


服务定义

什么是服务

服务(Service)是对数据库访问能力的抽象:

  • 命名:通过名称标识,如 pg-test-primary
  • 端口:通过端口区分,如 :5433
  • 路由:根据规则将流量路由到目标实例
  • 封装:隐藏底层集群的复杂性

服务的价值

传统方式:                         服务方式:
客户端 → IP:Port → 单个实例        客户端 → 服务 → 多个实例
                                              ↓
                                     自动选择健康实例
                                     故障自动切换
                                     读写自动分离

默认服务类型

primary 服务

读写服务,将流量路由到主库。

┌─────────────────────────────────────────────────────────────┐
│  pg-test-primary (:5433)                                     │
│                                                              │
│  客户端 → HAProxy:5433                                       │
│              │                                               │
│              ▼ (健康检查: 是否为主库)                         │
│         Pgbouncer:6432                                       │
│              │                                               │
│              ▼                                               │
│         PostgreSQL:5432 (Primary)                            │
└─────────────────────────────────────────────────────────────┘

特点

  • 有且仅有一个目标(主库)
  • 通过 Patroni 健康检查确定主库
  • 经过 Pgbouncer 连接池

replica 服务

只读服务,将流量路由到从库。

┌─────────────────────────────────────────────────────────────┐
│  pg-test-replica (:5434)                                     │
│                                                              │
│  客户端 → HAProxy:5434                                       │
│              │                                               │
│              ▼ (健康检查: 是否为从库)                         │
│       ┌──────┴──────┐                                        │
│       ▼             ▼                                        │
│  Pgbouncer:6432  Pgbouncer:6432                             │
│       │             │                                        │
│       ▼             ▼                                        │
│  PostgreSQL     PostgreSQL                                   │
│  (Replica 1)    (Replica 2)                                 │
└─────────────────────────────────────────────────────────────┘

特点

  • 可有多个目标(所有从库)
  • 轮询负载均衡
  • 无从库时可回落到主库

default 服务

直连服务,绕过连接池直接访问主库。

┌─────────────────────────────────────────────────────────────┐
│  pg-test-default (:5436)                                     │
│                                                              │
│  客户端 → HAProxy:5436                                       │
│              │                                               │
│              ▼ (绕过 Pgbouncer)                              │
│         PostgreSQL:5432 (Primary)                            │
└─────────────────────────────────────────────────────────────┘

用途

  • DDL 操作
  • 管理维护
  • 需要持久连接的场景

offline 服务

离线服务,访问不承载在线流量的实例。

┌─────────────────────────────────────────────────────────────┐
│  pg-test-offline (:5438)                                     │
│                                                              │
│  客户端 → HAProxy:5438                                       │
│              │                                               │
│              ▼ (选择 offline 角色或 pg_offline_query=true)   │
│         PostgreSQL:5432 (Offline)                            │
└─────────────────────────────────────────────────────────────┘

用途

  • ETL 抽取
  • 分析查询
  • 报表生成

服务配置

默认服务定义

pg_default_services:
  - name: primary
    port: 5433
    dest: primary
    check: /primary
    selector: "[]"
    haproxy:
      maxconn: 3000
      balance: roundrobin
      options:
        - option httpchk
        - option http-keep-alive

  - name: replica
    port: 5434
    dest: replica
    check: /replica
    selector: "[]"
    backup: "[? pg_role == `primary`]"

  - name: default
    port: 5436
    dest: postgres
    check: /primary
    selector: "[]"

  - name: offline
    port: 5438
    dest: offline
    check: /replica
    selector: "[? pg_role == `offline` || pg_offline_query ]"

自定义服务

pg_services:
  - name: standby
    port: 5435
    dest: replica
    check: /sync    # 只路由到同步从库
    selector: "[? pg_role == `replica` && sync_state == `sync` ]"

  - name: delayed
    port: 5439
    dest: postgres
    check: /replica
    selector: "[? pg_role == `delayed` ]"

服务参数

参数 说明 示例
name 服务名后缀 primary
port 服务端口 5433
dest 目标类型 primary, replica, postgres, offline
check 健康检查路径 /primary, /replica
selector 实例选择器 JMESPath 表达式
backup 备份实例选择器 JMESPath 表达式

健康检查

HAProxy 通过 Patroni REST API 进行健康检查:

端点 说明 响应
/primary 是否为主库 200/503
/replica 是否为从库 200/503
/sync 是否为同步从库 200/503
/async 是否为异步从库 200/503
/health 实例是否健康 200/503
HAProxy → GET http://patroni:8008/primary
        ← 200 OK (是主库,可接收流量)
        ← 503 Service Unavailable (不是主库)

服务发现

服务信息自动注册到监控系统:

# /etc/prometheus/targets/pgsql.yml
- labels:
    cls: pg-test
    ins: pg-test-1
  targets:
    - 10.10.10.11:9630    # pg_exporter
    - 10.10.10.11:9631    # pgbouncer_exporter
    - 10.10.10.11:8008    # patroni

HAProxy 统计信息通过 9101 端口暴露。

2 - 接入方式

通过 DNS、VIP、IP 多种方式接入数据库服务。

Pigsty 提供多种服务接入方式,满足不同场景的需求。


接入架构

┌─────────────────────────────────────────────────────────────────────┐
│                           接入层次                                   │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  DNS 域名                                                    │    │
│  │  pg-test → 解析到 VIP 或主库 IP                              │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                              │                                       │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  VIP 地址(可选)                                            │    │
│  │  10.10.10.3 → 绑定到主库节点                                 │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                              │                                       │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  HAProxy 服务端口                                            │    │
│  │  :5433 primary  :5434 replica  :5436 default  :5438 offline  │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                              │                                       │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  数据库实例                                                  │    │
│  │  PostgreSQL :5432  Pgbouncer :6432                          │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘

DNS 接入

通过集群域名访问,由 DNSMASQ 提供解析。

域名格式

集群域名:${pg_cluster}
实例域名:${pg_cluster}-${pg_seq}

使用示例

# 集群域名 - 解析到 VIP 或主库
psql postgres://test@pg-test:5433/testdb

# 实例域名 - 解析到特定实例
psql postgres://test@pg-test-1:5433/testdb
psql postgres://test@pg-test-2:5434/testdb

配置

pg_dns_suffix: ""           # DNS 后缀(可选)
pg_dns_target: auto         # 解析目标:auto/vip/primary/ip

VIP 接入

通过 L2 虚拟 IP 访问,由 vip-manager 管理。

工作原理

┌─────────────────────────────────────────────────────────────┐
│                    vip-manager                               │
│                                                              │
│  监视 ETCD 领导者信息                                        │
│         │                                                    │
│         ▼                                                    │
│  当前节点是主库?                                             │
│    是 → 绑定 VIP 到本节点                                    │
│    否 → 释放 VIP                                             │
│                                                              │
│  ┌─────────────┐  故障切换  ┌─────────────┐                 │
│  │  Node 1     │  ────────► │  Node 2     │                 │
│  │  (Primary)  │            │  (Primary)  │                 │
│  │  VIP ✓      │            │  VIP ✓      │                 │
│  └─────────────┘            └─────────────┘                 │
└─────────────────────────────────────────────────────────────┘

使用示例

# 通过 VIP 访问
psql postgres://test@10.10.10.3:5433/testdb

配置

pg_vip_enabled: true              # 启用 VIP
pg_vip_address: 10.10.10.3/24     # VIP 地址
pg_vip_interface: eth0            # 绑定网卡

注意事项

  • 需要 L2 网络可达
  • 所有节点必须在同一 VLAN
  • VIP 地址不能与现有 IP 冲突

端口接入

直接通过 IP + 端口访问。

端口列表

端口 组件 说明
5432 PostgreSQL 数据库直连
6432 Pgbouncer 连接池直连
5433 HAProxy primary 服务
5434 HAProxy replica 服务
5436 HAProxy default 服务
5438 HAProxy offline 服务

使用示例

# PostgreSQL 直连
psql postgres://test@10.10.10.11:5432/testdb

# 通过连接池
psql postgres://test@10.10.10.11:6432/testdb

# 通过 HAProxy 服务
psql postgres://test@10.10.10.11:5433/testdb  # 读写
psql postgres://test@10.10.10.11:5434/testdb  # 只读

智能客户端

使用 libpq 的多主机连接和目标属性。

读写分离

# 多主机 + target_session_attrs
postgres://test@10.10.10.11:6432,10.10.10.12:6432,10.10.10.13:6432/testdb?target_session_attrs=primary

# 只连接主库
?target_session_attrs=primary

# 优先连接从库
?target_session_attrs=prefer-standby

# 任意可用节点
?target_session_attrs=any

优点

  • 客户端自动选择合适节点
  • 无需 VIP 或额外代理
  • 故障自动切换

接入对比

方式 优点 缺点 适用场景
DNS 易于记忆、统一入口 依赖 DNS 服务 通用场景
VIP 透明切换、无需改配置 需要 L2 网络 传统应用
IP:Port 简单直接 硬编码、不灵活 测试开发
智能客户端 原生支持、无额外组件 需要客户端支持 云原生应用

连接字符串示例

生产环境推荐

# 读写业务 - 通过 DNS + primary 服务
postgres://app_user:password@pg-prod:5433/appdb

# 只读业务 - 通过 DNS + replica 服务
postgres://app_user:password@pg-prod:5434/appdb

# 管理操作 - 通过 DNS + default 服务
postgres://dba_user:password@pg-prod:5436/appdb

开发环境

# 直连单机实例
postgres://dev_user:password@10.10.10.10:5432/devdb

ETL 任务

# 使用离线服务,避免影响线上
postgres://etl_user:password@pg-prod:5438/appdb

连接池考量

何时使用连接池

  • 高频短连接场景
  • 连接数超过数据库限制
  • 需要连接复用

何时绕过连接池

  • DDL 操作
  • 大事务、长连接
  • 需要 session 变量
# 通过 5433/5434 经过连接池
postgres://user@host:5433/db

# 通过 5436/5438 绕过连接池
postgres://user@host:5436/db