Appearance
一次访问网站
浏览器输入一个网址回车,这一下背后到底发生了多少事?很多人觉得不就是个 HTTP 请求嘛——其实这一下能拆出至少五六层来,每一层都可能成为访问失败的地方。先不碰具体部署和代码,把对象认清楚就够了:URL、IP、端口、HTTP 报文、入口层、后端、数据库。
排查网站打不开的时候,套路其实就是按这个顺序一层层往外剥。比如浏览器直接提示 DNS_PROBE_FINISHED_NXDOMAIN,那是 DNS 层挂了;能连上但页面 502,那是入口层往后端转发这一段挂了;页面打开但接口转圈,问题大概率在后端或者数据库。所以这每一层都得认得,不然后面排查只能瞎猜。
https://www.example.com/docs/index.html 这串地址其实信息量很大:用的是 HTTPS,要访问的是 www.example.com,具体路径是 /docs/index.html,端口默认走 443。把这些拆开看,就是后面所有讨论的基础。
一、URL 结构
浏览器地址栏里那串完整地址,学名叫 URL。它有固定格式,浏览器照着这个格式才知道要怎么访问、访问哪里。一个完整的 URL 长这样:
https://www.example.com:443/docs/index.html?from=notes#title
拆开看,大概有六块——协议、域名、端口、路径、查询参数、片段。协议告诉浏览器走 HTTP 还是 HTTPS,后面会不会加密就看这个;域名是要访问的主机名,但机器之间通信认的是 IP,所以这一段后面要查一次 DNS;端口一般省略,https:// 默认 443、http:// 默认 80,写了就按写的来;路径告诉服务器要哪个页面或接口;查询参数(?key=value)是塞给后端的额外信息,常见于搜索、过滤、分页;片段(#xxx)是浏览器自己用的,跳到页面里某个锚点,根本不会发给服务器。
这里面有个容易绕的点:DNS 只管把域名翻译成 IP,URL 后面那一长串路径它不碰。所以 www.example.com/docs 和 www.example.com/api 解析出来是同一个 IP,差别只在请求发出去之后服务器怎么处理路径。一开始可能以为不同路径要走不同服务器,其实不是——同一台机器、同一个 IP,Web 服务自己根据路径分流。
二、DNS 解析
机器之间通信认的是 IP 地址,域名这东西纯粹是给人看的——www.example.com 比 203.0.113.10 好记多了。所以浏览器拿到域名之后,第一件事就是查一下这名字背后到底对应哪个 IP,这个过程就是 DNS 解析。打个比方,DNS 就像个 114 查号台:你给它一个公司名(域名),它返回一个电话号码(IP)。
查到的结果不一定就是 IP,可能是另一种记录。常见的几种:
| 记录类型 | 返回什么 | 什么时候碰到 |
|---|---|---|
A | IPv4 地址 | 最常见,绝大多数业务域名都是 A 记录 |
AAAA | IPv6 地址 | 走 IPv6 的场景,现在越来越多 |
CNAME | 另一个域名 | 接 CDN 的业务基本都走 CNAME,业务域名先指到 CDN,CDN 再返回边缘节点 IP |
接 CDN 的网站为什么会用 CNAME?因为 CDN 厂商的边缘节点 IP 是动态变的(根据用户位置就近分配),业务方不可能写死 IP。所以业务域名先 CNAME 到一个 CDN 域名,具体哪个 IP 由 CDN 自己根据请求来源决定。
域名查不到的时候,浏览器会直接抛 DNS_PROBE_FINISHED_NXDOMAIN 这种错。这种情况请求根本没发出去,问题卡在最开始——可能是域名拼错了、解析记录没配、本地 DNS 缓存了旧的失败结果。先确认这几个,再考虑别的。
三、端口与请求
拿到 IP 之后,浏览器要跟 IP:端口 建立连接。HTTPS 一般是 203.0.113.10:443,HTTP 是 203.0.113.10:80。端口这东西存在的意义是一台机器可以同时跑很多服务,得有个编号区分——22 是 SSH、80 是 HTTP、443 是 HTTPS、3306 是 MySQL、6379 是 Redis,这些是约定俗成的默认端口,记一下排查的时候用得上。
连接建立之前要先做 TCP 握手,HTTPS 还要再做 TLS 握手——客户端验证服务器证书、协商加密参数。证书过期、证书域名跟访问的域名对不上、客户端不信任发证机构,这些都会在 TLS 握手这一步暴露出来,浏览器会跳一个红色警告页。
连上之后,浏览器开始发 HTTP 请求。HTTP 本质就是个文本协议,规定了客户端和服务器之间怎么表达"我要什么""返回了什么"。一个请求里大概有这些内容:方法(GET 读、POST 写)、路径、Host 头(说明访问的是哪个域名)、其他 Header(Cookie、认证信息、内容类型)、Body(POST 这类提交数据的请求才有)。
这里有个细节容易忽略:同一个 IP 后面可能挂好几个站点,服务器靠 Host 头区分。www.example.com 走官网、api.example.com 走接口服务,这俩域名可能解析到同一个 IP,但因为 Host 不一样,Nginx 就能把请求转发到不同的后端去。所以 Host 这个头不是装饰,缺了它入口层就分不清请求是给谁的。
请求发出去之后,能拿到 404、500、502 这种状态码,说明请求至少进了 Web 入口或者后端链路——浏览器到服务器这条路是通的。但如果连状态码都没有,只是 ERR_CONNECTION_REFUSED 或者 ERR_CONNECTION_TIMED_OUT,那是连接层面就挂了:REFUSED 多半是请求到了目标 IP,但端口上没服务监听;TIMED_OUT 更像中间没人回应,安全组、防火墙、路由、公网 IP 都要查一遍。
四、入口层
公网请求进来之后,通常不会直接打到业务程序上。中间至少隔着好几层:CDN、WAF、负载均衡、Nginx、Ingress、API 网关——具体几层看架构复杂度,小站点可能就一台云服务器加 Nginx,大企业系统每一层都是一组机器。
排查问题的时候,先分清请求到底停在哪一层非常关键。状态码就是个很好的分层线索:
404——请求到了入口或后端,但路径没匹配到页面或接口,问题是路由配置401——没登录或者认证失败,问题是身份状态403——有身份但没权限,问题是授权500——后端收到请求之后处理异常,问题是应用代码、数据库或者下游服务502——入口层连后端失败了,问题是后端实例没起来、端口不对、连接被拒绝503——后端实例不可用或服务过载,可能是限流、健康检查失败、容量打满
这里面 500 和 502 最容易混——一开始都可能以为是"后端挂了",但根因完全不一样。500 是后端收到了请求、处理过程中报错,看后端应用日志基本能定位;502 是入口层根本没连上后端,可能是后端 Pod 没起来、端口写错了、Service selector 没选中 Pod、后端进程崩了。看入口层(Nginx/Ingress)的 error log 通常能直接看到原因,比如 connect() refused 或者 no live upstreams。
五、页面资源加载
服务器返回 HTML 之后,浏览器才开始真正"打开"这个页面。HTML 里还会引用 CSS、JavaScript、图片、字体——浏览器解析到这些标签之后,会再发新的请求去拉这些资源。所以一个网页打开,Network 面板里几十个请求是常事,根本不是"一次请求一次响应"那么简单。
前后端分离的页面更是这样。第一次请求往往只拿到一个 index.html 骨架和几份 JS/CSS,真正的业务数据要等 JS 跑起来之后,前端再去请求 /api/tasks、/api/users/me 这类接口才能拿到。所以页面打开慢,不一定是最初那个 HTML 慢,也可能是后面某个 JS bundle 太大、或者某个 API 接口超时。
把整条请求链路画出来大概是这样:
把这张图记住,后面排查任何"打不开""慢""报错"的问题,基本都是顺着这条链路一层层查:DNS 通不通、TCP 连不连得上、TLS 握手过不过、HTTP 请求发出去没、状态码是多少、是入口层还是后端、是后端还是数据库——每一步都有对应的检查命令和现象。