2. 连接建立与认证过程剖析
PostgreSQL 的每一次客户端连接,都不是简单的“打开一个 socket”。从 libpq 发起连接请求,到后端进程完成身份认证并进入可执行 SQL 的状态,中间经历了一条明确的协议链路。理解这条链路,是排查“连接慢”“认证失败”“连接数打满”等问题的前提。
libpq 连接流程:从 connect() 到 ReadyForQuery
libpq 是 PostgreSQL 官方 C 客户端库,psql、pgAdmin、大多数语言驱动最终都通过它或它的协议实现来建立连接。一次典型连接包含以下阶段:
- 解析连接参数:libpq 从连接字符串或环境变量中读取 host、port、dbname、user、password、sslmode 等。若 host 为空,则走 Unix domain socket,通常比 TCP 更快。
- 建立传输层连接:TCP 模式下完成三次握手;若配置了多个 host,libpq 会按顺序尝试,直到成功或全部失败。
- 发送 StartupMessage:客户端发送协议版本、用户名、数据库名及运行时参数(如 application_name、client_encoding)。
- 后端认证协商:postmaster 派生出 backend 进程,backend 根据 pg_hba.conf 匹配结果,向客户端发送 AuthenticationRequest,指定认证方法。
- 认证交换:客户端按方法回应密码或令牌,backend 校验。
- 发送 ReadyForQuery:认证通过后,backend 返回 ParameterStatus、BackendKeyData,最后发送 ReadyForQuery,连接进入空闲可查询状态。
这条链路中,第 3 到第 6 步是“连接延迟”的主要来源,尤其是认证方法的选择和 pg_hba.conf 的匹配顺序。
认证方法:md5、scram-sha-256、gssapi 的差异
PostgreSQL 支持多种认证方法,选择哪一种,直接影响安全性和连接开销。
| 方法 | 密码传输方式 | 服务端存储 | 安全性 | 典型开销 |
|---|---|---|---|---|
| trust | 不认证 | 无 | 无 | 最低 |
| password | 明文 | 明文或 SCRAM | 低,禁止在公网使用 | 低 |
| md5 | MD5 加盐挑战 | md5 哈希 | 中,已不推荐 | 低 |
| scram-sha-256 | SASL 挑战响应 | SCRAM verifier | 高,抗离线破解 | 中,多一次往返 |
| gssapi | Kerberos 票据 | 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 响应慢,连接延迟会明显上升。
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 到认证往返
连接慢通常不是单一原因,而是多个环节叠加。可以按以下顺序排查和优化:
- 避免反向 DNS:pg_hba.conf 中若使用主机名而非 IP/CIDR,PostgreSQL 会对客户端 IP 做反向解析。可在 postgresql.conf 中设置
log_hostname = off,并尽量用 CIDR。 - 优先 Unix socket:同机应用把 host 设为
/var/run/postgresql或留空,省去 TCP 握手和本地回环开销。 - 减少认证往返:scram-sha-256 比 md5 多一次往返。若客户端与服务端跨地域,可评估是否用 TLS 客户端证书或 gssapi 减少密码交换次数,但需权衡运维复杂度。
- 控制连接数:每个连接对应一个 backend 进程,内存和上下文切换成本随连接数上升。使用 PgBouncer 等连接池,把应用侧连接数收敛到数据库可承受范围。
- 调整 TCP 参数:
tcp_keepalives_idle、tcp_keepalives_interval可避免空闲连接被中间设备静默断开,减少重连。 - 观察日志:设置
log_connections = on、log_disconnections = on,可看到每个连接的建立和断开时间,定位是认证慢还是网络慢。
一个可参考的排查路径是:先在客户端用 psql "host=... connect_timeout=5" 计时,再在服务端日志中对比连接建立时间。若客户端耗时远大于服务端记录,问题多在网络或 DNS;若服务端认证阶段耗时长,则检查 pg_hba.conf 是否触发了 DNS 解析、认证方法是否往返过多、KDC 或 LDAP 是否响应慢。