1. pg_isready 是什么:先搞懂它到底在做什么
做 PostgreSQL 运维和开发的人,应该都体会过那种"数据库到底起来没有"的焦虑。尤其是在自动化部署、容器编排和 CI/CD 流水线里,你得在脚本里等数据库就绪,然后才能执行建表、导数据、跑迁移这些后续操作。以前我见过不少人用pg_isready之前都是用psql -c "SELECT 1"去探测,或者干脆 sleep 个十几秒硬等,这两种方式要么太重、要么傻等,都不够优雅。其实 PostgreSQL 自带了一个轻量级诊断工具,就是pg_isready,专干这件事:检查数据库服务器是否接受连接,然后通过退出码告诉你是正常、拒绝、还是根本没响应。
pg_isready的定位和psql完全不同。psql是完整的交互式客户端,要建立会话、做认证、执行查询,一旦数据库没起来,它会打印一堆错误信息,还要等 TCP 超时,脚本处理起来很繁琐。pg_isready就单纯做"连接探活"这件事,它不会真的登录进去,不执行任何 SQL,只是探测服务端口上有没有 PostgreSQL 在响应连接请求,然后干净利落地返回一个状态码。在 shell 脚本里,你只需要拿到这个退出码,就知道下一步该怎么走了。
这个工具适合谁用?我觉得至少三类人离不开它:第一类是写部署脚本的运维工程师,需要在自动化流程里判断数据库何时可以接受连接;第二类是用 Docker Compose 或者 Kubernetes 编排的开发者,容器启动顺序经常依赖数据库先就绪;第三类是搞监控告警的平台团队,用pg_isready做数据库存活探测,比写一堆自定义脚本可靠得多。其实即使是普通开发者在本地调试时,偶尔也会遇到"postgres 服务怎么连不上"的情况,用pg_isready快速探一下,能直接区分是服务没起、端口不对、还是连接数满了,这比反复连psql猜原因要高效得多。
这个工具最讨喜的地方在于它的"轻"。它不依赖任何配置文件,不需要加载.pgpass之类的认证信息,也不强制要求你输入密码。它的默认行为很简单:根据当前环境变量PGHOST、PGPORT、PGUSER去尝试连接,如果这些都没设置,就用 Unix 域套接字连本地默认端口 5432。你可以在命令行里显式指定-h、-p、-d、-U覆盖这些默认值。这种设计理念就是"探活归探活,认证归认证",你不需为了检查数据库是否起来而额外承担认证失败的各种复杂度。
一句话概括:pg_isready就是 PostgreSQL 自带的"问路"工具,它不问你是谁,只问"你在不在、能不能接客"。理解了这一点,你就能在正确的场景里用好它,而不是拿它当psql的替代品。
2. 参数详解与版本差异:别只会用默认命令
很多人第一次用pg_isready都是直接敲命令不加参数,看到localhost:5432 accepting connections就觉得完事了。实际用起来,尤其在复杂的部署环境里,你会发现参数才是这个工具的精华所在。
2.1 核心参数逐个拆解
pg_isready的参数不多,但每个都有明确的使用场景。我整理了一份参数表,覆盖了最常用的选项:
| 参数 | 作用 | 典型使用场景 |
|---|---|---|
-h 主机 | 指定要探测的主机地址 | 探测远程数据库、跨容器/跨主机检查 |
-p 端口 | 指定要探测的端口,默认 5432 | 数据库跑在非标准端口时 |
-d 数据库名 | 指定要连接尝试的数据库 | 需要区分不同数据库实例的探活 |
-U 用户名 | 指定连接时用的用户名 | 需要在探测阶段就暴露认证问题的场景 |
-q | 静默模式,不输出任何信息 | 脚本中只看退出码,不想产生日志噪音 |
-t 秒数 | 设置连接尝试的超时时间 | 避免网络卡顿导致脚本长时间挂起 |
-s | 每次探测失败后自动重试 | 循环等待数据库就绪,不用自己写 while |
-V | 打印版本号后退出 | 确认工具版本 |
你可能注意到了,-d和-U在"探活"这个语境下听着有点多余,毕竟我们又不真的登录。但这两个参数实际用起来有一个隐藏价值:如果数据库服务器已经启动,但postgresql.conf里做了访问控制限制(比如pg_hba.conf里指定了某些库只允许特定用户连),带上正确的-d和-U可以让探测结果更接近真实业务请求。也就是说,如果你关心的是"业务能不能连上",那么探测参数就应该模拟业务连接的方式,而不是只检查进程在不在。
-t这个参数我觉得是脚本场景下的救命稻草。默认情况下,如果目标主机网络不通,TCP 连接可能要等系统超时才能返回,这个时间在某些网络环境下能拖到两三分钟。在自动化脚本里,等两三分钟是不可接受的。把超时设成 5 秒或者 10 秒,失败就快速返回,配合重试逻辑才是正确姿势。这里有个细节:-t设置的是连接尝试的总体超时时间,而不是每次尝试的超时时间。
-s参数可以用来实现"自动重试直到成功或超时",它其实是把 shell 脚本里的for循环给内置了。这个参数在 Docker 的健康检查(Healthcheck)里特别有用,因为容器健康检查通常要求"在某个时间窗口内能成功探测",配合-s可以提升成功率,避免因为数据库恰好还在启动窗口内而误报不健康。
2.2 不同版本之间的行为差异
pg_isready是 PostgreSQL 9.3 版本开始正式提供的客户端工具。如果你用的是 PostgreSQL 9.2 或更老的版本,系统里压根没有这个命令,那你就得用psql -c "SELECT 1"凑合,或者考虑升级了。不过现在的环境基本都 12 以上了,这个历史问题不太会遇到。
版本之间还有一个值得注意的差异是-s参数的引入。-s(--retry)是 PostgreSQL 14 才加入的,老版本里你只能自己在脚本里写while循环。所以如果你管理的环境有 13 和 14 两套数据库,写脚本的时候要留意:在 13 的客户端上运行pg_isready -s会直接报"invalid option",这种问题排查起来还挺容易让人懵的。
另外,不同大版本对 IPv6 地址的处理也有一些细微差别。新版本对 IPv6 字面量的解析更稳,老版本在某些系统上需要你把 IPv6 地址用方括号包起来,比如pg_isready -h ::1在某些老版本上会解析异常。这些都是实际操作中冷不丁会踩到的小坑。
提示:写跨版本兼容脚本时,先跑一下
pg_isready --help确认当前客户端支持哪些参数,再决定是否用-s这种新特性。最稳妥的方案是兼容旧版本的手写重试逻辑。
2.3 默认行为:不指定参数时它在探测什么
pg_isready不指定任何参数时,它会尝试连接本地 Unix 域套接字,路径通常是/var/run/postgresql(Debian/Ubuntu)或/tmp(RHEL/CentOS 系),端口默认 5432。也就是说,如果你的 PostgreSQL 是通过源码编译并安装在自定义路径下的,Unix 套接字的目录可能不在默认位置,这时直接运行pg_isready会得到/tmp/.s.PGSQL.5432 不存在这类提示(实际报错信息可能是No response)。所以不指定参数不代表"智能发现",它只是按照编译时的默认配置去探测,这一点和psql是一致的。
在实际生产环境里,我强烈建议至少显式指定-h和-p。因为很多系统上环境变量PGHOST、PGPORT可能被设置成了意想不到的值,你不显式指定,探测的可能根本不是你以为的那个数据库。显式传参虽然啰嗦一点,但能保证脚本行为可预期,排查问题时少一个变量。
3. 退出码才是灵魂:0/1/2/3 背后都说了什么
pg_isready最有价值的地方不在它的输出文字,而在它以不同退出码表达服务器状态。这个设计对脚本编程极其友好:你不需要解析文本输出,不需要管中英文差异,只需要判断返回值是多少。这一点在自动化场景里是压倒性的优势,因为解析字符串是最容易出 bug 的事情之一,而整数退出码是稳定、可移植的接口。
3.1 退出码含义与触发场景
pg_isready的退出码一共有四种取值,官方文档里写得很清楚,但实际使用中每个退出码背后对应的情况比字面描述要丰富得多。我根据自己的排查经验整理了一个速查表:
| 退出码 | 含义 | 典型触发场景 | 你该怎么做 |
|---|---|---|---|
| 0 | 服务器接受连接 | 数据库正常运行,可接收新连接 | 继续执行后续操作 |
| 1 | 服务器拒绝连接 | 进程在跑,但处于启动中、恢复中、或达到 max_connections 限制 | 等待几秒后重试,不要急着报故障 |
| 2 | 服务器无响应 | 进程没起来、端口没监听、网络不通、防火墙拦截 | 检查进程状态、端口监听、网络连通性 |
| 3 | 未发起连接尝试 | 参数错误、主机解析失败、权限不足等客户端侧错误 | 检查命令参数、DNS 解析、运行用户权限 |
这里我想特别提醒一个容易误判的情况:退出码 1 不等于数据库挂了。PostgreSQL 在启动过程中,postmaster进程会在某个阶段开始监听端口,但此时它还处于startup状态,不接受新的连接,表现为pg_isready返回 1。另外在崩溃恢复时也是一样,服务器正在重放 WAL 日志,这时候连接请求会被拒绝,返回 1。所以在等待数据库就绪的脚本里,遇到退出码 1 的正确操作是"稍等再试",而不是立刻告警。只有连续多次返回 1 才值得关注,比如是不是一直卡在恢复状态无法完成。
退出码 2 的情况也值得掰扯一下。它的英文描述是"no response",字面意思是发送了连接请求,但服务器端没有任何响应。最常见的触发场景是端口根本没在监听,比如 postgres 进程没起来,或者监听在别的端口上。这类问题的排查路径是:先看进程还在不在(ps -ef | grep postgres),再看端口监听情况(ss -lnt | grep 5432),最后考虑防火墙和网络。当然也还有一种情况是 CPU 满载或者 IO 卡死导致 postmaster 无法响应新连接,表现形式同样是退出码 2,这种时候光看进程和端口还不够,得看系统负载和数据库日志。
退出码 3 是客户端侧错误,例如把-h写成了 IP 地址但该地址无法解析,或者操作系统层面没有权限创建 socket。这类问题通常伴随错误信息输出,比如could not translate host name。在脚本里遇到退出码 3,重试往往没有意义,应该直接终止流程并检查命令用法和网络配置。
3.2 实战验证:亲手看看不同退出码长什么样
光看文档不够,我自己实际测过各种状态下的pg_isready返回结果,这里把现场记录下来,方便你理解"文本输出和退出码是怎么对应的"。
正常运行时:
$ pg_isready -h 127.0.0.1 -p 5432 127.0.0.1:5432 accepting connections $ echo $? 0停掉 PostgreSQL 后:
$ pg_isready -h 127.0.0.1 -p 5432 127.0.0.1:5432 no response $ echo $? 2用-q静默模式看退出码:
$ pg_isready -h 127.0.0.1 -p 5432 -q $ echo $? 2看到区别了吧?-q模式下不会输出任何文本,但退出码依然准确。这就是它适合脚本的理由:不产生日志噪音,状态判断完全基于退出码。
注意:
pg_isready的-q静默模式和很多命令的-q不太一样,它不会改变退出码的语义,只是抑制标准输出。有的命令在-q模式下把所有结果都吞掉并返回 0,pg_isready不是这样,退出码该是几还是几,这一点对脚本非常关键。
3.3 为什么基于退出码而不是解析输出
我知道有些人是通过判断pg_isready输出的字符串里有没有 "accepting connections" 来写脚本的。这种做法在英文环境下能跑,但有几个隐患。首先,输出文本在不同的语言环境(LC_MESSAGES)下会变成别的语言,你按英文去 grep 会失败;其次,-q模式下根本没有输出可解析;第三,不同 PostgreSQL 大版本的输出格式偶尔有微调,字符串匹配很容易因为一个空格变化就出问题。反过来,退出码是程序接口契约,稳定得很,跨版本几乎不会变。
我在前面也提过,脚本逻辑应该写成这样:判断退出码,0直接往下走,1延时重试,2重试并计数,超过阈值就报错退出,3直接报参数错误。不解析文本、不做grep、不做cut,这就是我在自动化脚本里始终坚持的做法。
4. 实战场景一:单机部署与脚本等待数据库就绪
理论说完,接下来是大家最关心的部分:到底怎么在真实场景里用好pg_isready。我这儿先讲单机部署场景,比如你在一台新服务器上刚装好 PostgreSQL,要在脚本里等它起来再执行后续任务。
4.1 从零开始:安装后如何确认服务可用
假设你刚在 CentOS 7.9 上通过postgresql-server这个 RPM 包装好了 PostgreSQL 14,执行了initdb和systemctl start postgresql。怎么确认它真的能接连接?最简单的检查:
/usr/pgsql-14/bin/pg_isready -h 127.0.0.1 -p 5432这里有个容易踩的坑:pg_isready的完整路径因安装方式而异。RPM 包装出来的通常在/usr/pgsql-14/bin/下,源码编译安装的默认在/usr/local/pgsql/bin/下,而postgresql-client包(Debian/Ubuntu 系)则直接放进/usr/bin/。如果你运行命令提示command not found,第一件事就是确认 PostgreSQL 二进制目录有没有加进PATH。我见过不少人新装完数据库,明明服务起来了,却因为pg_isready不在PATH里而误判为命令不可用。
确认能连接后,再配合systemctl status postgresql看服务状态,基本上就能确定数据库层面没问题了。这一步在排查"数据库装了但连不上"的问题时特别高效,比psql连半天要直观得多。
4.2 等数据库就绪的稳妥脚本写法
在自动化初始化脚本里,等数据库就绪是个高频需求。最简单粗暴的写法是sleep 30然后直接操作,但问题是这个 30 秒要么太长拖慢流程,要么太短数据库还没起来,纯碰运气。用pg_isready写等待逻辑就优雅多了。这里给出一段经过多次生产验证的脚本:
#!/bin/bash # 等待 PostgreSQL 就绪,超时 60 秒 PGHOST=127.0.0.1 PGPORT=5432 RETRY_TIMES=12 RETRY_INTERVAL=5 for i in $(seq 1 $RETRY_TIMES); do /usr/pgsql-14/bin/pg_isready -h "$PGHOST" -p "$PGPORT" -q case $? in 0) echo "PostgreSQL is ready (attempt $i)" exit 0 ;; 1) echo "PostgreSQL is starting up, retrying... ($i/$RETRY_TIMES)" ;; 2) echo "PostgreSQL is not responding, retrying... ($i/$RETRY_TIMES)" ;; *) echo "Client error, cannot proceed" exit 1 ;; esac sleep $RETRY_INTERVAL done echo "PostgreSQL did not become ready in time" exit 1这段脚本的逻辑很清晰:最多尝试 12 次,每次间隔 5 秒,60 秒内数据库没起来就报错。退出码 1 和 2 都会继续重试,因为数据库可能在启动窗口内。区别在于它们打印的日志不同,方便你事后排查。如果在第 3 次尝试时数据库已接受连接,脚本立刻退出并返回 0,不会傻等完整轮次。
提示:如果用的是 PostgreSQL 14 及以上版本,
pg_isready自带了-s参数,可以把上面这段循环精简成一条命令:pg_isready -h 127.0.0.1 -p 5432 -q -t 60 -s。但要注意-s的重试间隔是固定的(1 秒),不能自定义间隔时长,这在某些场景下不够灵活。
4.3 把 pg_isready 写进系统服务脚本和 cron
如果你管理系统服务,pg_isready还可以作为服务脚本的状态检查函数。比如自定义的 init 脚本里,status子命令可以这么写:
status() { if /usr/pgsql-14/bin/pg_isready -h 127.0.0.1 -p 5432 -q; then echo "postgresql is running" return 0 else echo "postgresql is stopped" return 1 fi }这里的思路是以pg_isready作为服务存活性判据,比只查 pid 文件可靠得多。pid 文件存在不代表进程活着,也不代表端口在监听,更不代表数据库能接受连接。pg_isready这一步直接验证到 TCP 层甚至数据库协议层,信息量完全不一样。
另外,如果你写监控脚本定期检查数据库状态,可以用pg_isready加cron的组合,每分钟探测一次,把退出码记录到日志里。这样一个简单的数据库存活监控就建起来了,不需要引入额外的监控组件。
5. 实战场景二:Docker 环境与 Kubernetes 健康检查
容器化部署大概是pg_isready用得最多的场景,因为容器编排里"等待数据库就绪"几乎成了标配需求。你在网上搜 Docker Compose 部署 PostgreSQL 的文章,大概率能看到类似depends_on: - postgres: condition: service_healthy的配置,而数据库侧的健康检查命令,用pg_isready是默认方案。
5.1 Docker Compose 中的 healthcheck 配置
postgres官方镜像默认提供了健康检查支持,其内部执行的命令就是pg_isready。在 Docker Compose 里,你可以显式覆盖默认健康检查配置,自定义探活参数和检查频率。一个完整的示例:
services: postgres: image: postgres:16 environment: POSTGRES_USER: app_user POSTGRES_PASSWORD: app_password POSTGRES_DB: app_db ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U app_user -d app_db -h 127.0.0.1 -p 5432"] interval: 5s timeout: 3s retries: 5 start_period: 10s这个配置里有几个细节值得注意。一是test命令里我用了CMD-SHELL方式,因为pg_isready不是 shell 内置命令,直接写成["CMD", "pg_isready", "-U", "app_user", ...]也可以,但 Dockers 的 JSON 数组形式要求命令路径正确,而CMD-SHELL会走 shell 查找PATH。二是探活参数带上了-U和-d,这相当于模拟真实业务连接路径,比单纯探活默认库更贴近实际。三是start_period设为 10 秒,给容器启动和数据库初始化留出缓冲,避免健康检查在启动初期就误报失败。
5.2 为什么官方镜偏偏选 pg_isready 而不是 psql
官方postgres镜像默认的HEALTHCHECK指令在早期版本里就是pg_isready,后来演变成能够在启动时自动检测用户、密码等配置。为什么选它不选psql?核心原因有两点:第一,pg_isready不会真的建立完整会话,所以不需要处理psql连接时产生的认证提示、密码输入等交互逻辑,检查逻辑更纯粹;第二,pg_isready的执行开销极小,即使容器频繁做健康检查,对数据库的影响也几乎可以忽略,而频繁建立psql会话会带来额外的进程开销和认证日志噪音。
容器健康检查(healthcheck)本身也是基于退出码判断容器状态的:退出码 0 代表健康,退出码 1 代表异常(会触发重启或停止调度流量)。这和pg_isready的返回语义天然契合。试想一下,如果健康检查命令是psql -c "SELECT 1",数据库启动过程中psql会报一堆连接错误并以非零码退出,容器可能被误判为不健康;而用pg_isready,退出码 1(服务器拒绝连接)虽然也是非零,但配合start_period和重试机制,可以更平滑地表示"正在启动中"的状态。
注意:Docker 的健康检查只认退出码非零即失败,它不区分 1 和 2。所以如果你希望容器在数据库启动窗口内不被重启,必须把
start_period配得足够长,或者前置一个等待脚本,让pg_isready在数据库真正能连接前不退出。我见过有人反复被容器重启折磨,最后发现是start_period太短,数据库初始化需要 15 秒,而start_period只给了 5 秒,健康检查超时后容器就被终止了。
5.3 Kubernetes 里的 startupProbe 与 livenessProbe
Kubernetes 中的livenessProbe和readinessProbe同样可以用pg_isready实现。一个 PostgreSQL StatefulSet 的探针配置大致如下:
livenessProbe: exec: command: - pg_isready - -h - 127.0.0.1 - -p - "5432" initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 readinessProbe: exec: command: - pg_isready - -h - 127.0.0.1 - -p - "5432" - -U - postgres - -d - postgres initialDelaySeconds: 5 periodSeconds: 5这里我刻意把livenessProbe和readinessProbe的参数区分开了。livenessProbe(存活探针)只检查进程层和端口层的存活,不需要带用户和库名,因为如果数据库还活着只是认证有问题,不应该杀掉容器;但readinessProbe(就绪探针)可以更严格一些,带上-U和-d模拟真实请求路径,因为就绪状态关心的是"能不能正常服务业务流量"。
这种分工在 Kubernetes 实际运维里很有价值。你肯定不希望数据库因为临时拒绝了连接(比如正在做检查点或刚重启)就被 kubelet 反复杀掉,所以livenessProbe的判定标准应该放宽;但readinessProbe可以严格,因为 Pod 没就绪就不会进入 Service 的负载均衡池,这对线上应用更安全。
5.4 容器探索的一个小技巧:用 pg_isready 做 initContainer
Kubernetes 里还有一种玩法是用initContainer等待数据库就绪。比如你的应用 Pod 启动时依赖数据库已经可用,可以在 Pod 定义里加一个初始化容器:
initContainers: - name: wait-for-db image: postgres:16 command: - sh - -c - | until pg_isready -h postgres-svc -p 5432 -q; do echo "waiting for postgres..." sleep 2 done这段脚本会一直循环执行pg_isready,直到数据库可以接受连接才退出。initContainer的退出代表整个 Pod 可以启动主应用容器。用官方postgres镜像作为 initContainer 镜像,好处是镜像里自带客户端工具,不需要额外安装。如果你不想用这么重的镜像,也可以基于busybox自己装postgresql-client,但官方镜像显然更省事。
这个模式在开发环境里尤其常用,因为开发环境的数据库和应用经常一起编排起来,没有独立的运维流程保证数据库先启动完。用initContainer就把"依赖就绪"的逻辑做进了调度层面,应用容器永远不会比数据库更早启动。
6. 实战场景三:监控脚本与告警集成
除了部署阶段的等待逻辑,pg_isready在生产监控里也是好东西。它的轻量特性意味着你可以高频执行而不给数据库增加负担,每秒探测一次都没问题。虽然 production 环境一般不建议每秒钟都去探测,但 10 秒一次完全说得过去,因为它几乎不建会话、不做认证,产生的开销远低于psql查询。这对监控系统是一个巨大的优势,你可以用更低成本获取更细粒度的可用性采样。
6.1 监控脚本:记录退出码与延迟
一个典型的监控脚本会循环执行pg_isready,记录每次的退出码和消耗的时间,用于计算可用性。下面这段脚本收集数据并输出到日志文件:
#!/bin/bash # 每 10 秒探测一次,记录退出码和时间戳 while true; do timestamp=$(date '+%Y-%m-%d %H:%M:%S') start_time=$(date +%s%N) /usr/pgsql-14/bin/pg_isready -h 127.0.0.1 -p 5432 -q exit_code=$? end_time=$(date +%s%N) elapsed=$(( (end_time - start_time) / 1000000 )) echo "$timestamp code=$exit_code latency=${elapsed}ms" >> /var/log/pg_isready_monitor.log sleep 10 done这个脚本的产出是一个可用性记录文件,后续可以用 awk 或其他工具统计某段时间内退出码 0 的比例、平均延迟等指标。如果你觉得脚本里手动计时太粗糙,其实pg_isready没有内置计时输出,所以这种计时脚本还是有价值的。
如果配合更专业的监控组件(如 Prometheus 的postgres_exporter或 Zabbix),pg_isready可以承担"外部探活"这一层的数据来源。常见的做法是用自定义脚本周期执行pg_isready,把退出码映射成监控指标:0 对应up=1,非 0 对应up=0。这样一个简单的数据库存活指标就进了监控系统,配合 Grafana 能看到数据库可用性曲线。
6.2 告警阈值与误报规避
告警场景里最怕误报。pg_isready返回退出码 1 时,如果你直接告警"数据库不可用",那数据库每次重启或者崩溃恢复都会触发一波无谓的告警。正确做法是区分"短暂拒绝连接"和"持续不可用"。
我建议的告警策略是:连续 N 次探测失败(比如 3 次,每次间隔 10 秒)才触发告警。这样既能避开启动窗口,又能过滤偶发的网络抖动。对应的脚本逻辑可以这么写:
#!/bin/bash # 连续 3 次失败才告警 PGHOST=127.0.0.1 PGPORT=5432 FAIL_COUNT=0 THRESHOLD=3 while true; do if /usr/pgsql-14/bin/pg_isready -h "$PGHOST" -p "$PGPORT" -q; then FAIL_COUNT=0 else FAIL_COUNT=$((FAIL_COUNT + 1)) fi if [ "$FAIL_COUNT" -ge "$THRESHOLD" ]; then echo "ALERT: PostgreSQL has been down for at least $THRESHOLD checks" # 这里可以接入邮件、企业微信、钉钉等告警通道 FAIL_COUNT=0 fi sleep 10 done这个脚本里有个细节:只要有一次成功,FAIL_COUNT就清零。这种"连续失败计数"策略能有效减少抖动导致的误报。你可以根据业务容忍度调整THRESHOLD和sleep间隔:业务敏感就设 2 次、间隔 5 秒;业务容忍度高就设 5 次、间隔 30 秒。
提示:监控和告警脚本一定要加上超时参数
-t,建议 5 秒以内。如果不加超时,网络故障时 TCP 连接可能长时间挂起,导致监控脚本阻塞,进而错过后续检查。这在"数据库故障但监控也挂了"的经典事故里是个常见诱因。
6.3 结合外部工具做更全面的检查
pg_isready只探活数据库服务本身,它不检查复制状态、磁盘空间、慢查询等业务层面的问题。所以在监控体系里,pg_isready适合做第一层的"存活探针",更深层的健康检查还是要交给专业工具或 SQL 查询。比如你可以用pg_isready探活,然后用psql -c "SELECT pg_is_in_recovery()"检查是不是备库,再查pg_stat_replication判断复制延迟。分层检查的好处是:当某个指标异常时,你能快速定位这是"服务不可用"还是"功能降级",而不至于在一条链路上什么都查。
在我实际的工作流里,监控面板上的数据库模块会同时展示三块数据:pg_isready的存活状态(进程在不在、端口通不通)、pg_stat_activity的连接数和活跃查询、pg_stat_database的事务和锁情况。pg_isready放在最前面作为可能性的"总开关",它挂了后面那些数据多半也没意义了。
7. 常见问题与排查技巧实录
这部分是我打算送给你的压轴内容,因为我在不同环境里折腾pg_isready的过程中,攒了不少真实问题。这些问题单独看都不难,但组合在一起的排查路径很值得梳理成一份速查表。
7.1 问题速查表
| 现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| 返回 "no response" 且退出码 2 | PostgreSQL 未启动 | systemctl status postgresql或 `ps -ef | grep postgres` |
| 返回 "no response" 但进程在 | 端口被改或监听地址不对 | 查看postgresql.conf里的port和listen_addresses | 用正确端口重试;修改配置后重启 |
| 返回 "refusing connections" 且退出码 1 | 数据库仍在启动或恢复中 | 查看 PostgreSQL 日志 | 等待启动完成;长时间不结束需检查 WAL 恢复 |
| 返回 "refusing connections" | max_connections已满 | 查看pg_stat_activity连接数 | 增加max_connections或排查连接泄漏 |
| 报 "could not translate host name" | DNS 解析失败 | nslookup 主机名 | 检查 DNS、/etc/hosts |
| 报 "connection timed out" | 网络不通或防火墙拦截 | telnet 主机 端口测试 | 检查防火墙规则、安全组、网络路由 |
| 报 "option requires an argument" | 参数漏了值 | 检查-h、-p、-t等参数后是否跟了值 | 补全参数 |
| 报 "invalid option" | 客户端版本太老 | pg_isready --version | 使用新版客户端,或避免使用-s等新参数 |
这张表基本覆盖了我遇到过的绝大多数问题。遇到问题时对照表格逐项排查,效率会高很多。
7.2 排查关键:日志是最终裁判
当你发现pg_isready返回异常但搞不清楚原因时,数据库日志才是最终裁判。PostgreSQL 的日志位置因系统而异:Debian/Ubuntu 下通常在/var/log/postgresql/,RHEL/CentOS 系在/var/log/messages里也能看到 PostgreSQL 的输出,源码编译安装的默认在data_directory/pg_log/下。查看日志里有没有database system is ready to accept connections这句,基本就能确定数据库走到了哪一步。
举个例子,有一次某台服务器上的pg_isready一直返回退出码 1,进程在、端口在监听,但就是不接受连接。我翻日志发现一直重复打印consistent recovery state reached和invalid record length at ...,说明 WAL 出了问题,数据库在恢复过程中卡住了。这种问题靠pg_isready本身解不了,必须人工介入修复 WAL 或做恢复,但pg_isready至少给了你明确的信号:不是网络问题,不是端口问题,是数据库层面的恢复未完成。
7.3 几个容易被忽视的小坑
最后分享几个我在实践中发现的、写文档里不一定提到的细节。
第一件事,pg_isready的探测并不保证"数据库一定能跑业务查询"。它只是验证到"接受连接"这一层,之后认证能否通过、事务能否执行是另一回事。数据库可能处于 read-only 模式、连接池耗尽、或者表空间损坏,这些pg_isready都感知不到。所以有些团队会用pg_isready && psql -c "SELECT 1"的组合来补足深度检查,我建议在生产监控里至少做一次SELECT 1级别的验证。
第二件事,使用 Unix 域套接字探测时,运行用户必须对 socket 文件有访问权限。如果你用sudo -u postgres执行没问题,但换普通用户执行pg_isready(不指定-h)失败,很可能就是权限问题。这种情况指定-h 127.0.0.1绕开 Unix 套接字往往就通了,但这也意味着你探测的网络路径和本地不同,可能掩盖问题。
第三件事,pg_isready -t的超时数值单位是秒,而且只接受整数。我曾经在脚本里写过-t 0.5,结果直接被解析成非法参数。想要 500ms 超时只能通过外部工具(比如timeout 0.5 pg_isready ...)实现。这个细节容易让人困惑,因为 PostgreSQL 很多参数是支持小数的。
第四件事,在 Windows 环境(如果走 PostgreSQL Windows 安装包),pg_isready同样可用,并且退出码语义完全一致。但 Windows 的批处理脚本判断退出码需要%ERRORLEVEL%,和 Linux 的$?语法不同。跨平台脚本要留意这个差异。
7.4 我的建议:把 pg_isready 当作工具箱里的标配
经过这么多实际场景的打磨,我对pg_isready的定位就四个字:简单可靠。它不会替你解决所有数据库可用性问题,但在"判断 PostgreSQL 到底能不能连接"这个单一任务上,它是目前最干净利落的工具。在部署脚本里等就绪、在容器里做健康检查、在监控里做存活探针,这三个场景它都表现得很稳。
我个人踩过几次坑之后的体会是:用好pg_isready的关键不是记住参数,而是理解它的退出码语义,并把它嵌入到自动化逻辑里。不要只把它当命令用,要把它当协议接口用——在脚本里专门抽一个函数封装pg_isready的调用和退出码处理,这样整个项目的部署和监控代码都能复用,后续维护也轻松很多。如果你现在还在用psql -c "SELECT 1"做探活,真心建议尽快切换到pg_isready,你会在后续的自动化工作中感受到这份清爽的。