服务接入

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

接下来

深入了解服务的细节:

相关话题:


服务抽象

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

接入方式

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