2. 连接建立与认证过程剖析

PostgreSQL 的每一次客户端连接,都不是简单的“打开一个 socket”。从 libpq 发起连接请求,到后端进程完成身份认证并进入可执行 SQL 的状态,中间经历了一条明确的协议链路。理解这条链路,是排查“连接慢”“认证失败”“连接数打满”等问题的前提。

libpq 连接流程:从 connect() 到 ReadyForQuery

libpq 是 PostgreSQL 官方 C 客户端库,psql、pgAdmin、大多数语言驱动最终都通过它或它的协议实现来建立连接。一次典型连接包含以下阶段:

  1. 解析连接参数:libpq 从连接字符串或环境变量中读取 host、port、dbname、user、password、sslmode 等。若 host 为空,则走 Unix domain socket,通常比 TCP 更快。
  2. 建立传输层连接:TCP 模式下完成三次握手;若配置了多个 host,libpq 会按顺序尝试,直到成功或全部失败。
  3. 发送 StartupMessage:客户端发送协议版本、用户名、数据库名及运行时参数(如 application_name、client_encoding)。
  4. 后端认证协商:postmaster 派生出 backend 进程,backend 根据 pg_hba.conf 匹配结果,向客户端发送 AuthenticationRequest,指定认证方法。
  5. 认证交换:客户端按方法回应密码或令牌,backend 校验。
  6. 发送 ReadyForQuery:认证通过后,backend 返回 ParameterStatus、BackendKeyData,最后发送 ReadyForQuery,连接进入空闲可查询状态。

这条链路中,第 3 到第 6 步是“连接延迟”的主要来源,尤其是认证方法的选择和 pg_hba.conf 的匹配顺序。

libpq 连接建立与认证时序 libpq 客户端 postmaster backend 进程 TCP 三次握手 / Unix socket StartupMessage(user, db, params) fork backend 进程 读取 pg_hba.conf 匹配 type/db/user/address → method AuthenticationRequest(method) 按方法生成响应 md5 / SCRAM / GSS PasswordMessage / SASLResponse AuthenticationOk + ParameterStatus + BackendKeyData ReadyForQuery(连接可用)

认证方法:md5、scram-sha-256、gssapi 的差异

PostgreSQL 支持多种认证方法,选择哪一种,直接影响安全性和连接开销。

方法密码传输方式服务端存储安全性典型开销
trust不认证无无最低
password明文明文或 SCRAM低,禁止在公网使用低
md5MD5 加盐挑战md5 哈希中,已不推荐低
scram-sha-256SASL 挑战响应SCRAM verifier高,抗离线破解中,多一次往返
gssapiKerberos 票据KDC 侧高,适合企业域中高,依赖 KDC
cert客户端证书CA 信任链高中,TLS 握手

md5 的工作方式是:服务端发送一个随机 salt,客户端把“密码 + 用户名”的 MD5 哈希再与 salt 拼接后做一次 MD5,发送结果。服务端用存储的 md5 哈希做同样计算并比对。它的问题在于,一旦 pg_authid 泄露,攻击者可以离线暴力破解,且 MD5 本身已不再安全。

scram-sha-256 是 RFC 5802 定义的 SASL 机制。它不再把密码等价物直接存库,而是存储包含 salt、迭代次数、StoredKey、ServerKey 的 verifier。认证时双方通过 client-first、server-first、client-final、server-final 四步交换,服务端不直接验证密码,而是验证客户端能否用密码派生出正确的 proof。即使数据库被拖库,攻击者也需要对每个用户单独做高迭代次数的 PBKDF2 暴力尝试,成本显著提高。代价是比 md5 多一到两次网络往返,在高延迟链路上会放大连接时间。

gssapi 用于 Kerberos 环境。客户端先从 KDC 获取服务票据,再向 PostgreSQL 出示。它适合统一身份管理,但引入了 KDC 可用性和时钟偏移等额外依赖。若 KDC 响应慢,连接延迟会明显上升。

从 PostgreSQL 14 起,password_encryption 默认值已改为 scram-sha-256。新建用户或修改密码时,会生成 SCRAM verifier。老用户若仍是 md5 存储,需要执行 ALTER USER ... PASSWORD '...' 重新设置密码才能迁移。

pg_hba.conf:匹配顺序决定认证路径

pg_hba.conf 是客户端认证的“路由表”。backend 在收到 StartupMessage 后,会从上到下逐行匹配,第一条匹配的记录生效,后续规则不再检查。因此顺序错误会导致预期之外的认证方法。

每行格式为:

# TYPE      DATABASE   USER      ADDRESS         METHOD
local       all        all                       scram-sha-256
host        all        all       127.0.0.1/32    scram-sha-256
host        all        all       10.0.0.0/8      scram-sha-256
host        appdb      appuser   192.168.1.0/24  md5
host        all        all       ::1/128         scram-sha-256
host        all        all       0.0.0.0/0       reject

关键点:

  • TYPE:local 走 Unix socket,host 走 TCP(含 SSL),hostssl 要求 SSL,hostnossl 排除 SSL。
  • ADDRESS:支持 CIDR,也可写主机名(会触发反向 DNS,增加延迟)。
  • METHOD:trust、reject、md5、scram-sha-256、password、gss、sspi、cert、peer、ident、ldap、radius 等。
  • 修改后需执行 SELECT pg_reload_conf(); 或 pg_ctl reload,无需重启。
把 host all all 0.0.0.0/0 trust 放在文件顶部,会让所有后续规则失效,等于对公网开放无密码访问。生产环境务必把 reject 或具体网段规则放在最后作为兜底。

连接延迟优化:从 DNS 到认证往返

连接慢通常不是单一原因,而是多个环节叠加。可以按以下顺序排查和优化:

  1. 避免反向 DNS:pg_hba.conf 中若使用主机名而非 IP/CIDR,PostgreSQL 会对客户端 IP 做反向解析。可在 postgresql.conf 中设置 log_hostname = off,并尽量用 CIDR。
  2. 优先 Unix socket:同机应用把 host 设为 /var/run/postgresql 或留空,省去 TCP 握手和本地回环开销。
  3. 减少认证往返:scram-sha-256 比 md5 多一次往返。若客户端与服务端跨地域,可评估是否用 TLS 客户端证书或 gssapi 减少密码交换次数,但需权衡运维复杂度。
  4. 控制连接数:每个连接对应一个 backend 进程,内存和上下文切换成本随连接数上升。使用 PgBouncer 等连接池,把应用侧连接数收敛到数据库可承受范围。
  5. 调整 TCP 参数:tcp_keepalives_idle、tcp_keepalives_interval 可避免空闲连接被中间设备静默断开,减少重连。
  6. 观察日志:设置 log_connections = on、log_disconnections = on,可看到每个连接的建立和断开时间,定位是认证慢还是网络慢。

一个可参考的排查路径是:先在客户端用 psql "host=... connect_timeout=5" 计时,再在服务端日志中对比连接建立时间。若客户端耗时远大于服务端记录,问题多在网络或 DNS;若服务端认证阶段耗时长,则检查 pg_hba.conf 是否触发了 DNS 解析、认证方法是否往返过多、KDC 或 LDAP 是否响应慢。

连接建立与认证是 PostgreSQL 高并发工程的第一道关口。把 libpq 流程、认证方法和 pg_hba.conf 匹配顺序三者串起来看,才能既保证安全,又把连接延迟控制在可接受范围内。