发布于 更新于
「站点挂了」几乎从来不是一件事实。一次请求要经过域名解析、TCP 连接、TLS 握手和 HTTP 交互,其中任何一环出问题,在浏览器里看起来都一样,在终端里却完全不同。一次有用的可用性检查,应该告诉你是哪一环失败、每一环各花了多久 —— 正是这一点把排查范围从整条技术栈收敛到某一个组件。
检查完整的请求路径
先把完整 URL 写下来:协议、主机、非默认端口、路径,一个都别省。然后按顺序逐段检查,不要一上来就盯着 HTTP 响应。域名解析不出来,和域名能解析、主机却拒绝连接,是两起完全不同的故障,但浏览器给出的错误页长得一模一样。
「哪一段失败」之所以比「状态码是多少」更重要,是因为每一段牵涉的团队和系统都不同。DNS 失败指向区域文件、委派关系,以及某人刚改过、还在 TTL 内的那条记录;连接失败指向防火墙、安全组,以及进程到底有没有在监听;TLS 失败指向证书链和证书上的名字。而只要拿到了 HTTP 状态码,就说明下面每一层都已经跑通了 —— 哪怕那个状态码是 500,这也是极有价值的信息。
Site Check 是从本站服务器发起请求的,不是从你的浏览器,所以它回答的问题和「刷新一下页面」不一样:它告诉你这个站点从一张不属于你的网络上是否可达。同事能打开、你打不开的时候,要的正是这个区分。
找到你遇到的症状那一行。最后一列是下一步该看的地方,不是对原因的猜测。
| 阶段 | 症状 | 已经能证明什么 | 接下来看哪里 |
|---|---|---|---|
| 域名解析 | NXDOMAIN、SERVFAIL 或无应答 | 除了名字存在与否,什么都证明不了 | 委派关系、区域文件内容、TTL 内被改过的记录、注册状态 |
| TCP 连接 | connection refused | 主机可达并且回应了 | 进程是否在监听、监听哪个端口、绑定了哪个地址族 |
| TCP 连接 | 连接超时,没有任何回包 | 什么都证明不了,请求消失了 | 防火墙与 ACL、安全组、路由、区域性封锁 |
| TLS 握手 | 证书过期、名称不匹配、协议或加密套件错误 | 服务器在运行并接受连接 | 证书链是否完整、SAN 列表、SNI、最低 TLS 版本 |
| HTTP 响应 | 2xx | 整条路径端到端都通 | 接下来看内容正确性,而不是可用性 |
| HTTP 响应 | 3xx | 有东西回应了,并且要你去别处 | 跳转目标、是否成环、协议与主机是否错配 |
| HTTP 响应 | 403、404、429 | 服务器回应了,并且明确拒绝了这次请求 | WAF 或地域规则(403)、路由与重写(404)、限流(429) |
| HTTP 响应 | 500、502、503、504 | 边缘或源站回应了,它后面的某个环节失败了 | 应用异常(500)、上游响应无效(502)、维护或过载(503)、上游超时(504) |
超时和 5xx 不是同一种故障
这是可用性排查里最有用的一个区分。连接超时意味着路径上没有任何组件愿意或能够回应:包被静默丢弃了 —— 这正是配置成 DROP 而非 REJECT 的防火墙、路由黑洞,或者一台已经不存在的主机会有的表现。你的应用日志里不会有任何记录,因为应用从头到尾没看见过这次请求。
5xx 恰恰相反。有东西回应了 —— 负载均衡器、反向代理、CDN 边缘节点,或者应用本身 —— 并且用一个 HTTP 响应告诉你它没能完成这项工作。这个响应带响应头,而响应头通常能指认是哪个组件写的。502 表示前面那层从后面拿到了无效或空的回复;504 表示后面那层没在规定时间内回复;503 通常表示前面那层知道后端池是空的,或者在主动卸载流量。
实际影响的是你先去看哪里。遇到超时,去对照网络和基础设施的变更,并确认故障是否只发生在特定区域 —— 全球性超时和某一张网上的超时是两个问题。遇到 5xx,直接去看发出这个响应的那个组件的日志,再用分段耗时判断失败是快的(立即拒绝)还是慢的(上游耗尽了时间)。
- curl 的时间字段是累计值不是分段值,不做减法就会把开销算到错的阶段上。
- 第一次请求 time_namelookup 很慢、之后很快,那是解析器缓存,不是 DNS 故障。
- time_appconnect 与 time_starttransfer 之间的大段空白是服务端处理时间,这个数字才该拿去找应用团队。
$ curl -sS -o /dev/null \
-w 'dns %{time_namelookup}\ntcp %{time_connect}\ntls %{time_appconnect}\nttfb %{time_starttransfer}\ntotal %{time_total}\nstatus %{http_code}\n' \
https://www.example.com/
dns 0.031
tcp 0.048
tls 0.104
ttfb 0.712
total 0.718
status 200
# 每个数字都是从请求开始累计的,所以实际分段是:
# DNS 0.031
# TCP 0.017 (0.048 - 0.031)
# TLS 0.056 (0.104 - 0.048)
# 服务端 0.608 (0.712 - 0.104) <- 真正的开销
# 下载 0.006 (0.718 - 0.712)跟完整条跳转链
站点完全健康,也可能因为跳转方式而看起来是坏的。每一跳都要付一次完整往返,而跨主机名的跳转还会丢掉作用域限定在前一个主机上的 Cookie —— 「同一个登录在这个 URL 能用、换个 URL 就不行」多半是这么来的。要记录整条链,而不只是最终状态码。
值得排查的几种情况:成环(两个主机互相跳,浏览器跳到固定次数后放弃);链条中间某一跳降级回明文 HTTP;因为规范化规则是几年里一条条加上去的,导致链条比需要的长;以及跳到了证书覆盖不到的主机名 —— 最后这种表现为一条起初正常的链在中途冒出 TLS 错误。
另外要确认跳转码是不是你想要的那个。301 和 308 都是永久跳转,但 301 在历史上允许客户端把 POST 改写成 GET,而 308 要求保持原方法。如果某个 API 客户端在跳转时悄悄丢了请求体,原因通常就在这里。
$ curl -sSIL http://example.com/docs | grep -Ei '^(HTTP/|location:)'
HTTP/1.1 301 Moved Permanently
location: https://example.com/docs
HTTP/2 308 Permanent Redirect
location: https://www.example.com/docs
HTTP/2 301 Moved Permanently
location: https://www.example.com/docs/
HTTP/2 200
# 一个页面发了四次请求。第 1 跳升级协议,
# 第 2 跳补上 www 主机,第 3 跳补上末尾斜杠。
# 把这条链合并成一次直达最终 URL 的 301,
# 就能为每个冷请求省下三次往返。下「故障」结论之前先对比区域
从一个观测点做的检查只测量了一条路径。当某个国家的用户报告问题、而办公室里谁都复现不了时,真正要问的是:故障跟着目标走,还是跟着源网络走。把同一个 URL 从多张网络上跑一遍并排对比;如果一个区域超时、其他区域返回 200,问题就在路由、互联、CDN 接入点或地域规则上,而不在应用里。
延迟同样需要谨慎。单次测量就是噪声。拥塞、冷启动的 TLS 会话、没命中缓存的 DNS 应答、请求级别的调度,都会让数字浮动,所以要采多次样本、看分布,而不是看最快的一次或最新的一次。区域之间稳定存在的延迟差,通常意味着请求跑到了很远的源站,而不是由就近的边缘节点服务 —— 看看响应里有没有缓存命中相关的响应头就能确认。
Site Check 对每个访问 IP 每个 UTC 自然日限 10 次,这个限制是有意的:每次检查都是一次真实发往第三方的出站 HTTP 请求,不是一次数据库查询。所以要有预算意识。开始之前先想清楚哪些 URL、哪种对比才真正回答你的问题,而不是反复点同一个检查、看着数字来回抖。
把一次检查变成故障信号
一次手工检查只有被记录成别人能据此行动的形式,才算证据。请保留完整 URL、带时区的时间戳、观测点、最终状态码、完整跳转链、分段耗时,以及任何能指认响应来自哪个组件的响应头。这大约十行内容,就能把「站点好像有点慢」变成一句工程师可以验证或推翻的陈述。
然后和你自己掌握的数据做关联。外部探测告诉你用户可见的症状,源站日志告诉你请求到底有没有到达。事故排查中最有价值的发现,往往是某个请求出现在外部检查里、却在任何源站日志里都找不到 —— 这一下就把故障范围锁定在中间的一切:DNS、边缘、负载均衡器或网络。
不要凭单次探测就升级事件。探测本身也会因为自己的原因失败:探测机的网络、它用的解析器、一次临时限流,或者目标把陌生源地址当成可疑请求。宣布故障前先换一个观测点确认;反过来,当用户还在报故障时,对单次成功的检查也要同样保持怀疑。
- 只检查你有权检查的目标;对别人的源站反复发起自动请求,是你没获得许可就制造的负载。
- 遵守 robots 指令和公开的速率限制;如果你自建探测,请如实标识自己。
- 如果检查是在一次发布或一次 DNS 变更之后开始失败的,在证明无关之前就当它相关 —— 多数可用性事故背后都有一次变更。
要点回顾
- 按 DNS、TCP、TLS、HTTP 的顺序逐段检查,因为失败的那一段指向要排查的系统,单看状态码做不到这一点。
- 连接超时意味着没有任何东西回应、也没有任何日志;5xx 意味着有东西回应了,直接去读那个组件的日志。
- 记录完整跳转链:每一跳都要付一次往返,跨主机的一跳还会悄悄丢掉限定在前一个主机上的 Cookie。
- 延迟要在多个观测点采多次样本再下结论,并记得 curl 的时间字段是累计值。
- Site Check 每个访问 IP 每个 UTC 日 10 次,因为每次都是真实的出站请求,动手前先想清楚要做哪组对比。