大家每天都在浏览器里输入网址、点链接,但“从你按下回车到页面真正显示出来”这中间到底发生了什么,很多人其实说不清楚,甚至连不少写了好几年代码的同行,对完整链路也是一知半解。我见过太多人,前端调接口报错只知道截图问后端,后端排障只盯着应用日志,最后发现是DNS缓存、Nginx配置或者安全组没放行的问题。这篇文章就想把URL、服务器、客户端这三者之间的完整链条拆开揉碎,讲清楚一次访问从“手指按下回车”到“屏幕上出现内容”的全过程,并且把我踩过的坑、常用的排查套路一并分享出来。
这篇文章适合谁看?刚入门的前端和后端开发、做运维的同行、以及所有想搞明白“网址背后到底发生了什么”的爱好者。看完之后,你再遇到404、502这类报错,不会只停留在“页面打不开”的层面,而是能顺着链路一层层往下查,自己动手定位问题。
1. 一次URL访问的完整生命周期:从按下回车到首字节返回
1.1 第一步不是在请求服务器,而是在“查电话本”——DNS解析
你在浏览器地址栏输入 https://example.com 然后回车,浏览器做的第一件事并不是直接发请求,而是先解析这个域名对应的IP地址。域名是给人看的,服务器在网络上只认IP,所以必须有一个“翻译”过程,这就是DNS解析。
整个解析过程是分层的,我按实际查询顺序给你捋一遍:
- 浏览器缓存:Chrome、Firefox这些浏览器会把近期解析过的域名和IP对应关系缓存一段时间,TTL(Time To Live,生存时间)内直接命中,不发任何网络请求。
- 操作系统缓存:浏览器没命中,就会查操作系统层面的DNS缓存。Windows下你可以执行
ipconfig /displaydns查看,Linux/macOS下可以用nscd或系统自带的解析器缓存。 - hosts文件:这是一个“本地强制覆盖”的静态文件,Windows在
C:\Windows\System32\drivers\etc\hosts,Linux/macOS在/etc/hosts。很多开发环境会往这里写“测试域名指向本机”,我之前调试微服务就经常改它。 - 本地DNS服务器:如果上面都没命中,系统会把请求发给配置的DNS服务器(比如路由器的DNS、运营商的DNS,或者你手动设置的
8.8.8.8、114.114.114.114)。本地DNS服务器会先看自己的缓存,没有再继续向上级查询。 - 迭代查询:本地DNS服务器会去问根DNS服务器“com域的权威服务器是谁”,再去问com域的权威服务器“example.com的权威服务器是谁”,最后拿到记录返回给客户端。这个过程中,每一级都会做缓存,所以不是每一次访问都会走完整条链。
这里有一个很重要的概念叫TTL,它决定了DNS记录在缓存里存活多久。我之前遇到过一个问题:改完域名解析指向新服务器后,用户那边还是访问到旧服务器,就是因为本地DNS缓存还没过期,运营商递归服务器可能缓存了更长时间。所以我现在的习惯是,做线上域名迁移之前,先把TTL调低到300秒甚至60秒,等迁移完成后再调回默认值。
1.2 建立连接:TCP三次握手与TLS握手
拿到IP地址之后,浏览器开始与目标服务器建立TCP连接。TCP是可靠传输协议,在正式发送HTTP数据之前,客户端和服务器需要先“握手”达成一致。三次握手的过程非常经典:
- 客户端发送一个SYN包,告诉服务器“我想建立连接”,并带上一个初始序列号。
- 服务器收到后,回复SYN+ACK包,意思是“我知道了,我也准备好了”,同时带上自己的初始序列号。
- 客户端再回复一个ACK包,表示“收到你的确认”,连接建立完成。
有人会问:为什么非要三次?两次行不行?我用一个很生活化的例子解释:甲拨电话给乙,甲说“你能听到我吗”,乙说“我能听到你,你能听到我吗”,甲再说“我能听到你”。第三次是必须的,因为乙需要确认“甲确实能收到我的回复”,如果只有两次握手,乙无法确定自己发出去的消息是否真的被甲收到,也就无法确认链路的双向可用性。
如果访问的是HTTPS链接,在TCP握手之后还多了一次TLS握手,用来协商加密密钥、验证服务器证书。这个过程涉及证书链校验、密钥交换算法选择,浏览器地址栏的小锁标志就是TLS握手成功后的结果。我之前排查过一些“页面加载不出但网是通的”案例,最后发现是服务器上的SSL证书过期了,TLS握手直接失败,连接被强制中断。
1.3 请求发出:HTTP请求行、请求头、请求体
连接建立后,浏览器开始构造HTTP请求。一个标准的HTTP/1.1 GET请求大概长这样:
http复制GET /index.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: text/html,application/json;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session_id=abc123
第一行是请求行,包含方法(GET)、路径(/index.html)和协议版本;后面的每一行是请求头,其中 Host 是必填项,因为一台服务器上可能同时部署多个网站,服务器要靠Host字段区分你访问的是哪个域名。Cookie 携带了客户端本地保存的状态信息,这就是“保持登录”的基础。GET请求一般没有请求体,POST/PUT请求会有,常见的 Content-Type: application/json 表示请求体是JSON格式。
前两年我犯过一个很低级的错误:用Node.js写接口时,前端一直传JSON对象,我后端用 req.query 去取,结果死活取不到。后来才发现,axios默认把对象序列化后放在请求体里,应该用 req.body 去取,而且服务端必须前置 express.json() 中间件才能解析。这种问题本质上就是“请求报文格式与服务端解析预期不匹配”,一旦你能看懂请求报文,这类问题基本一眼就能判断出来。
1.4 服务器收到请求后做了什么
请求通过网络到达服务器网卡后,数据包经过内核协议栈处理,被送到监听在对应端口上的进程。以最常见的Nginx + 后端应用架构为例:
- Nginx接收HTTP请求,解析请求行和请求头,根据
server_name和location规则决定将请求交给哪个后端服务,还是直接返回静态文件。 - 如果是动态请求,Nginx通过反向代理的方式(如
proxy_pass)把请求转发给后端的应用进程,常见的是监听在127.0.0.1:8080之类的端口上。 - 后端应用(Spring Boot、Node.js、Python Flask等)根据URL路由找到对应的处理函数,执行业务逻辑,可能还要查询数据库、调用其他内部服务。
- 业务处理完成后,组装HTTP响应,内容包括状态码、响应头、响应体,原路返回给Nginx,再由Nginx返回给客户端。
这个流程中,最容易出问题的环节是“后端服务没监听在预期端口上”,或者“Nginx配置的后端地址写错”。我后面会在排障章节详细展开。
1.5 响应返回与客户端渲染
服务器返回的HTTP响应大致长这样:
http复制HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=3600
Content-Encoding: gzip
<!DOCTYPE html>
<html>
...
</html>
状态码 200 OK 是最常见的成功标志。浏览器拿到HTML后,开始解析、构建DOM树,遇到 <script src="...">、<link href="..."> 这类标签时,会再发起新的HTTP请求去加载子资源。这就是为什么你经常看到DevTools的Network面板里“一个页面几百个请求”——每个JS、CSS、图片、字体文件都是单独的HTTP请求。
到这里,一次完整的“输入网址到页面显示”就完成了。但如果是SPA单页应用,页面首次加载返回的是一个空的HTML骨架,真正的内容靠JavaScript在浏览器里动态渲染,这也就是为什么很多人说“首屏性能要靠前端优化,而后端只负责给一个空壳子”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 服务器侧的核心处理环节:应用、中间件与数据库的配合
2.1 反向代理与网关:为什么你的请求要过好几道门
很多人不理解“为什么我明明直接访问后端应用也能通,还要在前面加一层Nginx”。原因有三个:安全、负载均衡、协议终结。
安全层面:Nginx隐藏了后端服务的真实地址和端口,外部请求只能打到Nginx的80/443端口,后端服务可以只监听内网地址,不暴露公网。负载均衡层面:一台Nginx可以配置多个上游服务器,通过加权轮询、IP哈希等策略把流量分散到不同机器上,这样可以提高整体吞吐量。协议终结层面:HTTPS证书可以统一配置在Nginx上,不需要每台后端机器都处理加解密,而且Nginx处理静态文件、做Gzip压缩的效率远高于应用层。
一个最小的Nginx反向代理配置大概是这样:
nginx复制upstream backend {
server 127.0.0.1:8080 weight=1;
server 127.0.0.1:8081 weight=2;
}
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /var/www/static/;
expires 7d;
}
}
注意 proxy_set_header Host $host 这一行非常关键,如果不把原始的Host头传递给后端,后端在生成绝对链接、判断域名归属、做多租户隔离时就可能出现问题。
2.2 应用服务怎么处理业务逻辑
后端应用收到请求后,会经历一个典型的处理链路:
- 请求进入框架的路由层,框架根据请求方法和URL路径匹配到对应的Controller/Handler。
- 通过中间件管道,依次执行身份认证、参数校验、日志记录、跨域处理等通用逻辑。
- 进入业务处理函数,执行具体的业务规则,期间可能会调用数据库、缓存、消息队列等基础设施。
- 组装统一的响应结构返回给调用方。
这里我要特别说一个“参数校验放前”的经验。很多后端新手喜欢拿到参数就直接操作数据库,结果前端传了个 null 或者超长字符串,直接把接口打崩了。我在项目里一般要求所有接口入口先做参数校验,非法输入直接返回 400 Bad Request,而不是等到数据库报错才被动处理。这样做的好处是:一方面数据库压力小,另一方面错误信息对前端更友好,不会出现“500 + 一串堆栈”这种让人摸不着头脑的响应。
2.3 会话保持与状态管理:cookie、session、token
HTTP协议本身是无状态的,也就是说,服务器默认不记得“上一次请求是你发的”。但现实业务里必须有登录态,所以就有了Cookie、Session、Token这套机制。
最传统的方案是Session:用户登录后,服务器在内存或Redis里保存一份Session数据,并生成一个Session ID,通过 Set-Cookie 响应头下发给浏览器。浏览器后续请求都会自动带上这个Cookie,服务器通过Session ID找到对应的Session数据,就“记得”你是谁了。这套方案的缺点是:当服务器水平扩展到多台机器时,用户的请求如果被负载均衡分到了另一台机器,那台机器上没有对应的Session数据,用户就被迫重新登录。所以更合理的做法是把Session集中存到Redis里,所有机器共享同一份Session数据,这就是为什么热词里会有“redis客户端”“redis可视化客户端”——排障和调试时,你很可能需要打开Redis客户端看某个Session是否存在。
现在更流行的方案是JWT Token:服务器不保存会话状态,而是把用户ID、过期时间等信息签名后生成一个Token下发给客户端,客户端在后续请求的 Authorization: Bearer <token> 头里带上它,服务器验签通过后就能解出用户信息。JWT的典型优势是天然适合分布式、无状态服务,但劣势是一旦签发,在过期之前很难主动作废。实际项目中要根据安全需求和运维便利性选择,没有银弹。
2.4 数据库与缓存的角色
一个接口响应慢,十有八九问题出在数据库查询上。我见过一个“列表接口”在数据量到百万级时,单次查询耗时从200ms涨到3秒,原因很简单:没有索引,每次都是全表扫描。所以任何生产环境上线前,都要review一遍慢查询日志,确认核心查询命中了合适索引。
为了减轻数据库压力,业界通用的做法是引入缓存层,最典型的就是Redis。对于热点数据、低频变化的数据,可以先查缓存,命中就直接返回,未命中再查数据库,并回填缓存。这个流程听起来很简单,但实际工程中要小心“缓存穿透”“缓存击穿”“缓存雪崩”这三个经典问题:
- 缓存穿透:请求的数据在缓存和数据库都不存在,查询会直接打到数据库,恶意攻击者可以利用这个漏洞疯狂刷不存在的数据。
- 缓存击穿:某个热点Key在过期瞬间,大量请求同时打到数据库。
- 缓存雪崩:大量Key在同一时间段集体过期,或者Redis服务宕机,导致流量全部打到数据库。
我的应对习惯是:对不存在的key也做短暂空值缓存(比如60秒),热点Key的过期时间加随机抖动,必要时用互斥锁或逻辑过期方案保证只有一个请求去重建缓存。这些手段都实践过,简单有效。
3. 实操:自己动手搭一个最小可用的Web服务端
3.1 环境准备:一台Linux云服务器与客户端工具
理论讲再多,都不如自己跑一遍学得快。接下来我带大家从零搭一个最小可用的Web服务端。你需要准备这些:
- 一台云服务器,操作系统选Ubuntu 22.04 LTS或CentOS 7.9均可,配置不用高,1核2G足够跑演示项目。
- 一个SSH客户端。Windows上我推荐直接使用终端里的
ssh命令,也可以装MobaXterm或Xshell,不过命令行工具才是跨平台通用的。 - 一个文本编辑器,服务器上装
vim或者用nano都行。 - 如果你有域名,可以做域名解析指向服务器IP;没有域名也没关系,直接用IP访问。
购买并启动服务器后,第一步是登录。执行:
bash复制ssh root@你的服务器公网IP
如果你是第一次连接,会提示确认主机指纹,输入 yes 回车,然后输入密码,进入系统。进入后我建议先做两件事:更新软件源、创建普通用户并配置sudo权限。生产环境不建议直接用root跑应用,这是个安全底线问题。
3.2 部署一个轻量HTTP服务(以Node.js为例)
这里我用Node.js + Express演示,因为代码量最少、理解门槛最低。先在服务器上安装Node.js:
bash复制curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
验证安装成功:
bash复制node -v
npm -v
然后创建一个项目目录并初始化:
bash复制mkdir ~/my-demo && cd ~/my-demo
npm init -y
npm install express
新建一个 server.js:
javascript复制const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({
code: 0,
message: 'Hello from server',
timestamp: Date.now()
});
});
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
注意 app.listen(PORT) 这行。默认情况下,Node.js会监听在所有网络接口上,也就是 0.0.0.0:3000,外部可以直接访问。如果你写成了 app.listen(PORT, '127.0.0.1'),那就只有本机能访问,外部通过公网IP是连不上的。这一行写错,是最常见的“服务起来了但外部连不上”的原因之一。
启动服务:
bash复制node server.js
这时你在终端里应该能看到 Server listening on port 3000。
3.3 安装Nginx并配置反向代理
直接通过3000端口访问也能通,但实际项目中我们会在前面加一层Nginx。安装并启动:
bash复制sudo apt-get install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
编辑站点配置:
bash复制sudo vim /etc/nginx/sites-available/my-demo
写入以下内容:
nginx复制server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
创建软链接启用配置,并测试语法:
bash复制sudo ln -s /etc/nginx/sites-available/my-demo /etc/nginx/sites-enabled/my-demo
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
此时在浏览器里访问 http://你的服务器IP/,应该就能看到JSON响应了。
3.4 验证整条链路:从curl到浏览器到安全组
在本地电脑上执行:
bash复制curl -v http://你的服务器IP/
-v 参数会打印完整请求响应过程,包括TCP连接、DNS解析、HTTP请求头、响应头,这是排查问题最常用的命令。如果curl返回了 200 OK,链路就是通的。如果超时,90%的可能是云服务商的安全组没有放行80端口。
云服务器的安全组策略就像一道前置防火墙,你必须在云控制台的安全组规则里放行 TCP:80 和 TCP:443,否则即使Nginx正常监听,外部请求也进不来。我第一次搭建服务器时就卡在这里半小时,业务日志一切正常,端口也在监听,但公网死活访问不了,最后发现是安全组规则没加。
4. 客户端视角的排障实录:那些常见的“不速之客”
4.1 404 Not Found:URL写错了还是路由没配?
404大概是大家最常见的错误了。它表示“服务器没找到对应的资源”,但具体原因可能有好多层,我按排查顺序给你梳理:
- URL路径是否写错:检查一下访问的路径和后端定义的路由是否一致。比如后端定义的是
/api/users,你却请求了/api/user,那就是404。 - Nginx层:如果请求被Nginx处理,但Nginx的
location没有匹配到任何规则,并且没有合理的 fallback,也会返回404。 - 前端路由:对于SPA应用,如果用了HTML5 History模式(路由看起来像正常路径,不是
#/hash),部署到Nginx时如果不加try_files $uri $uri/ /index.html;,刷新子页面就会404。这个坑我遇到过很多次,解决办法就是让Nginx把所有找不到的路径都交给前端入口文件处理。 - 后端框架路由没匹配上:即使路径看起来一样,如果参数类型不匹配、请求方法不对(比如应该是POST你用了GET),框架也可能返回404而不是405。
热词里有一个 unexpected status 404 not found: unknown error,这种格式的报错经常出现在调用某个第三方API或内部服务时。看到这种报错,我的第一反应是:先用curl把请求原样复现一遍,看返回的响应体是什么,因为很多SDK会把HTTP状态码之外的业务错误码吞掉,只给你抛一个笼统的“404 unknown error”。只有看到原始响应内容,才能判断是“路径不对”“参数不对”还是“网关路由有问题”。
4.2 502 Bad Gateway:服务器“接不上话”的典型场景
502 Bad Gateway表示:网关或代理服务器从上游服务器接收到了无效响应。说白了就是Nginx把请求转发给后端,但后端没有返回有效的数据。
常见原因有:
- 后端服务没有启动:检查一下后端进程是否还在。我经常在服务器重启后发现Nginx先起来了,但Node.js/Java等应用进程没设置开机自启,于是所有请求都502。
- 后端监听的地址不对:Nginx转发到了
127.0.0.1:3000,但你的后端实际监听在0.0.0.0:3000,按说没问题;但如果你让后端只监听在了IPv6地址上(::),Nginx用IPv4去连就连不上。 - 防火墙拦截了本地回环地址的请求:有些系统防火墙会拦截非本机来源的请求,而Nginx转发给本机后端时,
remote_addr可能被规则误判。 - 后端进程启动后崩了或者一直卡着没响应:Nginx等待超时后也会返回502/504。
热词里有一个 unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:15721/v1/responses。这种“本机地址 + 端口 + /v1/xxx路径”的报错形态,在本地调试时非常典型,通常是本地代理或转发服务的端口配置不一致导致的。我的排查方法是:
bash复制# 检查端口是否被监听
netstat -tlnp | grep 15721
# 或
lsof -i :15721
# 检查本机是否能通
curl -v http://127.0.0.1:15721/v1/responses
如果curl都返回502,那就是本机那个进程的问题,跟网络无关;如果curl正常,但你的应用调用时报502,那就要检查是不是走了某个代理,代理把请求转发到了错误的地址。
4.3 网络不可达与DNS解析失败的排查套路
Network Error、This site can't be reached、DNS_PROBE_FINISHED_NXDOMAIN 这些报错都属于连接层问题,而不是HTTP层问题。排查顺序一般是:
- 检查本机网络:能不能访问其他网站?如果不能,优先排查本地网络、路由器。
- 检查域名解析:执行
nslookup example.com或dig example.com,看返回的IP是否符合预期。 - 检查连通性:
ping能通不代表80端口能通,最好用telnet IP 80或nc -vz IP 80测试端口。 - 检查防火墙/安全组:这个前面已经说过了。
另外要提醒一个工具:ping 用的是ICMP协议,而HTTP用的是TCP协议,所以ping通不代表Web服务没问题,ping不通也不代表Web服务不能访问。有的云服务器安全组默认禁Ping,但HTTP照样能通。我之前排查过一个“用户说服务器挂了”的问题,远程一看进程全绿,最后发现是客户用它自己的网络ping不通,但通过其他网络访问完全正常,属于本地网络对ICMP的限制。
4.4 前端JS验证URL有效性的常见误区
“js验证url有效性”这个需求很常见,但很多人写出来的正则其实漏洞百出。我见过有人用一段特别长的正则想匹配所有可能的URL,结果连 https://example.com/path?query=1 这种标准URL都匹配不上。更实用的做法是使用浏览器内置的 URL 构造函数:
javascript复制function isValidUrl(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}
这个方案能正确识别协议、域名、路径格式,而且不需要维护复杂的正则。但它只能验证“URL格式合法”,不能验证“URL能访问”。如果你需要的是“这个地址能不能打开”,那只能发真实请求去试探,但前端直接请求跨域URL基本都会被CORS拦截,所以单纯在前端“验证URL可访问性”是不现实的,这个逻辑应该放到后端去做。
还有一个容易踩坑的点是URL编码。用户输入了包含中文、空格、特殊字符的地址,如果不做 encodeURIComponent,请求可能被服务器解析成非法路径导致404或400。正确做法是:参数值在拼接URL时统一编码,传递完整URL场景下先解析再用 URLSearchParams 或 encodeURI 处理。
5. 一些值得记住的优化与安全经验
5.1 让响应更快的几个实用技巧
说完排障,再聊几个实际项目中最常见的性能优化手段:
- 静态资源用CDN:图片、JS、CSS这些不怎么变化的资源,全部扔到CDN上,让用户从最近的节点获取,能省下后端大量带宽和请求量。
- 开启HTTP/2:HTTP/2支持多路复用,一个TCP连接可以同时传输多个请求,对延迟的改善非常明显。配置方式很简单,Nginx 在
listen 443 ssl http2;后即可。 - Gzip/Brotli压缩:对于纯文本类资源,压缩率通常能达到70%以上。Nginx里一行
gzip on;就能开启。 - 合理利用缓存响应头:比如静态资源设置
Cache-Control: max-age=31536000(一年),动态接口设置no-cache,能有效减少重复请求。 - 后端接口做数据裁剪:前端只显示10个字段,你却把整个大对象的100个字段全返回了,白白浪费流量和序列化时间。
这些优化每一项都“不起眼”,但加到一起来,页面加载耗时从3秒降到1秒以内是常有的事。我给业务做过一次纯Nginx层面的优化——开启Gzip、静态资源缓存、HTTP/2——没有动一行代码,整体响应时间下降了35%。
5.2 日常巡检与日志分析
服务部署好了,不是万事大吉,日常巡检要形成习惯。我最常看的几个地方:
- Nginx访问日志:默认路径是
/var/log/nginx/access.log,每一行是一次完整请求,包含时间、来源IP、请求行、状态码、响应字节数、上游响应时间。比如$upstream_response_time这个字段能告诉你后端到底花了多久才返回。 - 应用日志:如果应用出现了非200状态码,去应用日志里查找对应的堆栈。建议统一日志格式,至少包含请求ID,这样能方便地串联同一次请求在Nginx、应用、数据库三个层面的完整轨迹。
- 系统资源:用
top、free -h、df -h关注CPU、内存、磁盘。运维事故里“磁盘写满导致服务挂掉”出现的频率远超想象,尤其要注意日志文件有没有做轮转。
我强烈建议你在项目初期就把“结构化日志”这件事做好。我刚带团队时,经常看到大家在日志文件里写一堆“debug”级别的临时输出,用 grep 硬抠,效率极低。后来统一换成了JSON格式日志,并强制每一条日志带上 requestId 和 userId,排障速度提升了不止一个量级。
5.3 从一次“灵异”故障看整条链路的重要性
最后分享一个真实案例。有个客户的接口偶发超时,前端报错,后端日志却查不到任何异常。前后端互相甩锅,折腾了大半天。我介入后没有一上来就看代码,而是先沿链路一层层排查:客户端的HTTP请求确实发出去了,Nginx把请求转给了后端,后端返回了200,但响应耗时接近30秒。问题的根源是什么?后端应用响应慢,但慢在哪?应用日志里没有报错,因为请求最终成功返回了,只是数据库查询执行了28秒——原来是一条SQL在数据量增长后没有命中索引,触发了全表扫描。从客户端到服务器、再到数据库,整条链路里最不起眼的一环,成了真正的瓶颈。
这件事之后,我养成了一个习惯:遇到任何故障,先画链路图,从客户端到DNS、到Nginx、到应用、到数据库、到缓存,每一层都确认“正常”之后,才去怀疑更深层的代码逻辑。很多人学了一堆框架和工具,但遇到问题还是凭感觉乱试,本质上是头脑里没有“完整链路”这个概念。
我希望这篇内容能把这条链路清晰地印在你脑子里。以后再有人问你“输入网址回车之后发生了什么”,你能不只是背出“DNS解析、TCP握手、HTTP请求”这三个名词,而是真正知道每一环是怎么工作、出问题该看哪里。我个人在实际操作中的体会是:把这条链路理清楚之后,从前端到后端到ops,几乎没有你插不上话的排障场景。这也正是我写这篇文章的初衷。
