PIGSTY

服务

通过负载均衡、代理、连接池提供可靠的服务访问

服务实现

在 Pigsty 中,服务通过节点上的 haproxy 实现,通过主机节点上的不同端口来区分。

每个节点都启用了 Haproxy 来暴露服务。从数据库角度来看,集群中的节点可能是主节点或副本节点,但从服务角度来看,所有节点都是相同的。这意味着即使您访问副本节点,只要使用正确的服务端口,您仍然可以使用主节点的读写服务。这种设计封装了复杂性:只要您能访问 PostgreSQL 集群上的任何实例,就可以完全访问所有服务。

这种设计类似于 Kubernetes 中的 NodePort 服务。同样,在 Pigsty 中,每个服务都包含这两个核心元素:

  1. 通过 NodePort 暴露的访问端点(端口号,从哪里访问?)
  2. 通过选择器选择的目标实例(实例列表,谁来处理?)

Pigsty 服务交付的边界止于集群的 HAProxy。用户可以通过各种方式访问这些负载均衡器。请参考访问服务

所有服务都通过配置文件声明。例如,默认的 PostgreSQL 服务由 pg_default_services 参数定义:

- { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
- { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
- { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
- { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}

您也可以在 pg_services 中定义新服务。pg_default_servicespg_services 都是服务定义的数组。


定义服务

默认服务在 pg_default_services 中定义。

您可以在全局或集群级别使用 pg_services 定义额外的 PostgreSQL 服务。

这两个参数都是服务对象的数组。每个服务定义将被渲染为 /etc/haproxy/<svcname>.cfg 中的 haproxy 配置,查看 service.j2 了解详细信息。

这里是一个额外服务定义的示例:standby

- name: standby                   # 必需,服务名称,实际服务名称将以 `pg_cluster` 为前缀,例如:pg-meta-standby
  port: 5435                      # 必需,服务暴露端口(作为 kubernetes 服务节点端口模式工作)
  ip: "*"                         # 可选,服务绑定 IP 地址,默认为所有 IP 的 `*`
  selector: "[]"                  # 必需,服务成员选择器,使用 JMESPath 过滤清单
  dest: default                   # 可选,目标端口,default|postgres|pgbouncer|<port_number>,默认为 'default'
  check: /sync                    # 可选,健康检查 URL 路径,默认为 /
  backup: "[? pg_role == `primary`]"  # 备份服务器选择器
  maxconn: 3000                   # 可选,最大允许前端连接数
  balance: roundrobin             # 可选,haproxy 负载均衡算法(默认为 roundrobin,其他:leastconn)
  options: 'inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100'

它将被转换为 haproxy 配置文件 /etc/haproxy/pg-test-standby.conf

#---------------------------------------------------------------------
# service: pg-test-standby @ 10.10.10.11:5435
#---------------------------------------------------------------------
# service instances 10.10.10.11, 10.10.10.13, 10.10.10.12
# service backups   10.10.10.11
listen pg-test-standby
    bind *:5435
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /sync  # <--- 对主节点和同步备节点为真
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:6432 check port 8008 weight 100 backup   # 主节点用作备份服务器
    server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

重新加载服务

当集群成员发生变化时,如添加/移除副本、切换/故障转移或调整相对权重,您必须重新加载服务以使更改生效。

bin/pgsql-svc <cls> [ip...]         # 为负载均衡集群或负载均衡实例重新加载服务
# ./pgsql.yml -t pg_service         # 重新加载服务的实际 ansible 任务

覆盖服务

您可以通过几种方式覆盖默认服务配置:

绕过 Pgbouncer

在定义服务时,如果 svc.dest='default',将使用参数 pg_default_service_dest 作为默认值。默认使用 pgbouncer,您可以改用 postgres,这样默认的主节点和副本服务将绕过 pgbouncer 并直接将流量路由到 postgres

如果您完全不需要连接池,可以将 pg_default_service_dest 更改为 postgres,并移除 defaultoffline 服务。

如果您不需要用于在线流量的只读副本,也可以从 pg_default_services 中移除 replica

pg_default_services:
  - { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
  - { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
  - { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
  - { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}

委托服务

Pigsty 在节点上使用 haproxy 暴露 PostgreSQL 服务。集群中的所有 haproxy 实例都配置了相同的服务定义。

然而,您可以将 pg 服务委托给特定的节点组(例如,专用的 haproxy 负载均衡集群)而不是集群成员。

要做到这一点,您需要使用 pg_default_services 覆盖默认服务定义,并将 pg_service_provider 设置为代理组名称。

例如,这个配置将在 haproxy 节点组 proxy 上使用端口 10013 暴露 pg 集群主服务。

pg_service_provider: proxy       # 使用组 `proxy` 上的负载均衡器和端口 10013
pg_default_services:  [{ name: primary ,port: 10013 ,dest: postgres  ,check: /primary   ,selector: "[]" }]

用户有责任确保每个委托服务端口在代理集群中是唯一的

分离读写,将流量路由到正确的位置,并实现对 PostgreSQL 集群的稳定可靠访问。

服务是一个抽象,用于封装底层集群的细节,特别是在集群故障转移/切换期间。


个人用户

对于个人用户,服务是没有意义的。您可以使用原始 IP 地址或任何您喜欢的方法访问数据库。

psql postgres://dbuser_dba:DBUser.DBA@10.10.10.10/meta     # dbsu 直接连接
psql postgres://dbuser_meta:DBUser.Meta@10.10.10.10/meta   # 默认业务管理员用户
psql postgres://dbuser_view:DBUser.View@pg-meta/meta       # 默认只读用户

服务概述

在现实世界的生产环境中,我们利用基于复制的 PostgreSQL 数据库集群。在集群内,只有一个实例是可以接受写入的领导者(主节点)。其他实例(副本)持续从领导者获取 WAL 以保持同步。此外,副本可以处理只读查询,并在读取密集、写入较少的场景中为主节点分担负载。因此,区分写入和只读请求是一种常见做法。

此外,我们通过连接池中间件(Pgbouncer)为高频、短期连接池化请求,以减少连接和后端进程创建的开销。而且,对于 ETL 和变更执行等场景,我们需要绕过连接池并直接访问数据库服务器。此外,高可用性集群可能在故障期间发生故障转移,导致集群领导权发生变化。因此,读写请求应该自动重新路由到新的领导者。

这些不同的需求(读写分离、池化与直接连接以及客户端请求故障转移)导致了服务概念的抽象。

通常,数据库集群必须提供这个基本服务:

  • 读写服务(主节点):可以读取和写入数据库。

对于生产数据库集群,至少应该提供这两个服务:

  • 读写服务(主节点):写入数据:只由主节点承载。
  • 只读服务(副本):读取数据:可以由副本承载,但如果没有可用的副本,则回退到主节点。

此外,可能还有其他服务,例如:

  • 直接访问服务(默认):允许(管理员)用户绕过连接池并直接访问数据库。
  • 离线副本服务(离线):专用副本,不处理在线读取流量,用于 ETL 和分析查询。
  • 同步副本服务(备用):无复制延迟的只读服务,由同步备用/主节点处理读取查询。
  • 延迟副本服务(延迟):从一定时间前的同一集群访问较旧的数据,由延迟副本处理。

默认服务

Pigsty 将为每个 PostgreSQL 集群启用四个默认服务:

服务端口描述
primary5433pgbouncer 读写,连接到主节点 5432 或 6432
replica5434pgbouncer 只读,连接到副本 5432/6432
default5436管理员或直接访问主节点
offline5438OLAP、ETL、个人用户、交互式查询

以默认的 pg-meta 集群为例,您可以通过以下方式访问这些服务:

psql postgres://dbuser_meta:DBUser.Meta@pg-meta:5433/meta   # pg-meta-primary : 通过主节点 pgbouncer(6432) 进行生产读写
psql postgres://dbuser_meta:DBUser.Meta@pg-meta:5434/meta   # pg-meta-replica : 通过副本 pgbouncer(6432) 进行生产只读
psql postgres://dbuser_dba:DBUser.DBA@pg-meta:5436/meta     # pg-meta-default : 通过主节点 postgres(5432) 直接连接主节点
psql postgres://dbuser_stats:DBUser.Stats@pg-meta:5438/meta # pg-meta-offline : 通过离线 postgres(5432) 直接连接离线

pigsty-ha.png

这里 pg-meta 域名指向集群的 L2 VIP,进而指向主实例上的 haproxy 负载均衡器。 它负责将流量路由到不同的实例,查看访问服务了解详细信息。


主服务

主服务可能是生产使用中最关键的服务。

它将根据 pg_default_service_dest 将流量路由到主实例:

  • pgbouncer:将流量路由到主节点 pgbouncer 端口(6432),这是默认行为
  • postgres:如果您不想使用 pgbouncer,直接将流量路由到主节点 postgres 端口(5432)
- { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }

这意味着所有集群成员都将包含在主服务中(selector: "[]"),但通过健康检查(check: /primary)的唯一实例将被用作主实例。Patroni 将保证任何时候只有一个实例是主节点,因此主服务将始终将流量路由到主实例。

listen pg-test-primary
    bind *:5433
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /primary
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:6432 check port 8008 weight 100
    server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

副本服务

副本服务用于生产只读流量。

在现实场景中,只读查询可能比读写查询多得多。您可能有许多副本。

副本服务将根据 pg_default_service_dest 将流量路由到 Pgbouncer 或 postgres,就像主服务一样。

- { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }

replica 服务流量将尽量使用 pg_role = replica 的普通 pg 实例,以尽可能减轻 primary 实例的负载。它将尽量不使用 pg_role = offline 的实例,以尽可能避免混合 OLAP 和 OLTP 查询。

所有集群成员将包含在副本服务中(selector: "[]"),当它通过只读健康检查(check: /read-only)时。primaryoffline 实例用作备份服务器,在所有 replica 实例都宕机的情况下接管。

listen pg-test-replica
    bind *:5434
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /read-only
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:6432 check port 8008 weight 100 backup
    server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

默认服务

默认服务将默认路由到主节点 postgres(5432)。

它非常类似于主服务,除了它将始终绕过 pgbouncer,无论 pg_default_service_dest 如何。这对于管理连接、ETL 写入、CDC 变更数据捕获等非常有用…

- { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
listen pg-test-default
    bind *:5436
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /primary
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:5432 check port 8008 weight 100
    server pg-test-3 10.10.10.13:5432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:5432 check port 8008 weight 100

离线服务

离线服务将直接将流量路由到专用的 postgres 实例。

这可能是 pg_role = offline 实例,或者是标记了 pg_offline_query 的实例。

如果找不到这样的实例,它将回退到任何副本实例。底线是:它永远不会将流量路由到主实例。

- { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}
listen pg-test-offline
    bind *:5438
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /replica
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-3 10.10.10.13:5432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:5432 check port 8008 weight 100 backup

访问服务

Pigsty 使用 haproxy 暴露服务。默认情况下,所有节点都启用了 haproxy。

默认情况下,haproxy 负载均衡器在同一个 pg 集群中是幂等的,您可以通过任何/所有方式使用它们。

典型的方法是通过集群域名访问,它解析为集群 L2 VIP,或以轮询方式解析为所有实例 IP 地址。

服务可以通过不同的方式实现。您甚至可以实现自己的访问方法,如 L4 LVS、F5 等,而不是 haproxy。

pigsty-access.jpg

您可以使用主机和端口的不同组合,它们以不同的方式提供 PostgreSQL 服务。

主机

类型示例描述
集群域名pg-test通过集群域名(由基础设施节点上的 dnsmasq 解析)
集群 VIP 地址10.10.10.3通过由 vip-manager 管理的 L2 VIP 地址,绑定到主节点
实例主机名pg-test-1通过任何实例主机名访问(由基础设施节点上的 dnsmasq 解析)
实例 IP 地址10.10.10.11访问任何实例 IP 地址

端口

Pigsty 使用不同的端口来区分 pg 服务:

端口服务类型描述
5432postgres数据库直接访问 postgres 服务器
6432pgbouncer中间件在访问 postgres 之前通过连接池中间件
5433primary服务访问主节点 pgbouncer(或 postgres)
5434replica服务访问副本 pgbouncer(或 postgres)
5436default服务访问主节点 postgres
5438offline服务访问离线 postgres

组合

# 通过集群域名访问
postgres://test@pg-test:5432/test # DNS -> L2 VIP -> 主节点直接连接
postgres://test@pg-test:6432/test # DNS -> L2 VIP -> 主节点连接池 -> 主节点
postgres://test@pg-test:5433/test # DNS -> L2 VIP -> HAProxy -> 主节点连接池 -> 主节点
postgres://test@pg-test:5434/test # DNS -> L2 VIP -> HAProxy -> 副本连接池 -> 副本
postgres://dbuser_dba@pg-test:5436/test # DNS -> L2 VIP -> HAProxy -> 主节点直接连接(管理员)
postgres://dbuser_stats@pg-test:5438/test # DNS -> L2 VIP -> HAProxy -> 离线直接连接(ETL/个人查询)

# 通过集群 VIP 直接访问
postgres://test@10.10.10.3:5432/test # L2 VIP -> 主节点直接访问
postgres://test@10.10.10.3:6432/test # L2 VIP -> 主节点连接池 -> 主节点
postgres://test@10.10.10.3:5433/test # L2 VIP -> HAProxy -> 主节点连接池 -> 主节点
postgres://test@10.10.10.3:5434/test # L2 VIP -> HAProxy -> 副本连接池 -> 副本
postgres://dbuser_dba@10.10.10.3:5436/test # L2 VIP -> HAProxy -> 主节点直接连接(管理员)
postgres://dbuser_stats@10.10.10.3::5438/test # L2 VIP -> HAProxy -> 离线直接连接(ETL/个人查询)

# 直接指定任何集群实例名称
postgres://test@pg-test-1:5432/test # DNS -> 数据库实例直接连接(单例访问)
postgres://test@pg-test-1:6432/test # DNS -> 连接池 -> 数据库
postgres://test@pg-test-1:5433/test # DNS -> HAProxy -> 连接池 -> 数据库读写
postgres://test@pg-test-1:5434/test # DNS -> HAProxy -> 连接池 -> 数据库只读
postgres://dbuser_dba@pg-test-1:5436/test # DNS -> HAProxy -> 数据库直接连接
postgres://dbuser_stats@pg-test-1:5438/test # DNS -> HAProxy -> 数据库离线读写

# 直接指定任何集群实例 IP 访问
postgres://test@10.10.10.11:5432/test # 数据库实例直接连接(直接指定实例,无自动流量分发)
postgres://test@10.10.10.11:6432/test # 连接池 -> 数据库
postgres://test@10.10.10.11:5433/test # HAProxy -> 连接池 -> 数据库读写
postgres://test@10.10.10.11:5434/test # HAProxy -> 连接池 -> 数据库只读
postgres://dbuser_dba@10.10.10.11:5436/test # HAProxy -> 数据库直接连接
postgres://dbuser_stats@10.10.10.11:5438/test # HAProxy -> 数据库离线读写

# 智能客户端自动读写分离(连接池)
postgres://test@10.10.10.11:6432,10.10.10.12:6432,10.10.10.13:6432/test?target_session_attrs=primary
postgres://test@10.10.10.11:6432,10.10.10.12:6432,10.10.10.13:6432/test?target_session_attrs=prefer-standby