十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

一次HTTP请求的全链路拆解:从DNS解析到Node.js排障指南

一次HTTP请求的全链路拆解:从DNS解析到Node.js排障指南 先问一个问题你在浏览器里输入一个域名回车页面出来这一瞬间到底发生了什么很多同学能说出“DNS 解析、发送 HTTP 请求、服务器返回 HTML”这个大概流程但一旦遇到线上故障——“接口超时了”“页面打不开”“Nginx 502 了”——就开始发懵不知道从哪查起。我这些年排查过不少 Node.js 服务的线上问题最深的体会是大多数故障都不是代码逻辑错了而是请求在某一层被卡住、被丢弃、被错误转发。所以这篇文章我想把这条链路彻底拆开从你敲下域名开始一直到 Node.js 进程内部处理完请求返回响应每一层都讲清楚它们做了什么、容易出什么幺蛾子、以及怎么快速定位问题。适合刚买了云服务器准备部署 Node 项目的同学也适合前后端联调时经常被“网络问题”折磨的开发者。1. 先框定范围从你敲下域名到 Node.js 收到请求这条链路到底有多长先说清楚我这里讲的“层”不是 OSI 七层模型那种教科书分层而是从一次实际请求的视角来看它依次经过了哪些逻辑阶段。我习惯把它分成六段阶段主要参与者核心动作域名解析浏览器、系统 hosts、本地 DNS、权威 DNS把域名换成 IP网络传输运营商网络、骨干网、云厂商入口数据包路由到服务器云平台边界安全组、NAT、负载均衡决定数据包能不能进、进到哪台机器服务器系统层内核协议栈、iptables、监听 Socket完成 TCP 连接把数据交给进程反向代理层Nginx / Caddy / Apache终结 HTTP转发给后端Node.js 进程HTTP 解析器、框架中间件、业务代码处理请求、返回响应理解这六段的价值在于它给你提供了一棵排障决策树请求失败时你可以从上往下逐层确认问题出在哪一段而不是瞎猜。就像快递从北京发到上海可能因为地址写错、揽收延迟、干线运输故障、派送员联系不上等各种原因失败你得先确认包裹到底卡在哪个环节才能对症下药。另一个需要框定的点是“一次请求”的粒度。你在地址栏回车浏览器实际发出的往往不是一次请求而是先请求 HTML然后解析出 CSS、JS、图片等资源再逐个发起请求。页面加载慢和某个接口慢分析思路完全不同。本文以“一次 HTTP 请求”为主线条来分析但最后我会把页面整体性能的观察方法也带上。还有一个容易被忽略但极其重要的观念层与层之间有清晰的边界。DNS 出问题域名解析不到 IP连接层出问题TCP 握手失败HTTP 层出问题会返回 4xx/5xx 但链路是通的业务层出问题可能返回 200 但数据不对。很多人排查问题时把这几件事混在一起效率极低。接下来我们就一层一层过。2. 第一层域名解析——你输入的网址怎么变成云服务器的 IP2.1 域名系统不是“查一次”而是“逐级问”域名设计的初衷很简单IP 地址太难记了203.0.113.10这种数字没人能记住但example.com人人都能拼。于是有了 DNSDomain Name System它本质上是一本分布式的“电话簿”。但请注意这台“电话簿”不是一本就够的——全球域名数量太多不可能靠一台服务器扛住所以 DNS 是一个树状层级结构。当你输入www.example.com完整解析流程是这样的浏览器先查自己的 DNS 缓存命中就直接用没命中走下一步。查操作系统缓存和 hosts 文件。hosts 文件的优先级比 DNS 还高这点后面会说到它的坑。把请求发给本地配置的 DNS 服务器运营商默认分配的或者你手动配的114.114.114.114、223.5.5.5这类公共 DNS。本地 DNS 发现没有缓存就去问根域名服务器“你知道www.example.com的 IP 吗”根服务器说“我不知道但com顶级域的服务器地址是这些你去问它。”本地 DNS 接着问com顶级域服务器得到example.com的权威 DNS 服务器地址。本地 DNS 再问权威 DNS——也就是你在云厂商控制台配置解析记录的地方——终于拿到www.example.com对应的 A 记录IPv4 地址或 AAAA 记录IPv6 地址。本地 DNS 把结果缓存在本地然后返回给浏览器。浏览器也做一层缓存。这个过程叫递归查询和迭代查询的组合。每一级都可能缓存结果缓存时间由 TTLTime To Live决定单位是秒。这也是为什么你刚改完 DNS 解析记录全球生效要等一会儿——不是没生效而是沿途的缓存还没过期。2.2 生产环境里 DNS 层的几个经典坑先说 hosts 文件。它是个好东西本地调试时把127.0.0.1 example.com写进去就能不花钱“实现”域名访问。但如果机器上残留了旧的 hosts 记录线上问题排查时你会怀疑人生。我遇到过不止一次同事说“我这边访问线上域名怎么跳到本地开发环境了”查了半天就是 hosts 里有一条早就该删的记录。所以第一板斧出问题先cat /etc/hostsWindows 是C:\Windows\System32\drivers\etc\hosts看一眼。再说 CDN。很多人不知道用了 CDN 之后DNS 解析出来的 IP 根本不是你的源站服务器 IP而是 CDN 边缘节点的 IP。CDN 的工作原理就是通过 DNS 调度把不同地域的用户解析到离他们最近的节点。所以拿ping 你的域名得到的 IP 去连 SSH大概率连不上——压根不是一台机器。这个认知很重要否则你会觉得“服务器是不是被劫持了”。还有国内云服务器的部署场景域名解析成功IP 也正确但 80 和 443 端口访问就是不通。最常见的解释是域名备案没完成服务提供商会拦截未备案域名的访问。这种问题从技术链路看你的请求其实已经到了云平台边界但被平台策略拦下了。这类“链路通但被策略拦”的情况靠ping和telnet是查不出来的只能通过看返回页面里的提示信息来判断。2.3 Node.js 服务端的 DNS 也值得留意你的 Node.js 应用向外发起 HTTP 请求比如调用第三方 API时同样要经历 DNS 解析。Node 的net模块默认走系统的getaddrinfo也就是会查系统缓存但http模块在建立连接时如果频繁创建新连接可能会反复触发 DNS 查询。我做高并发服务时踩过一个坑某个第三方 API 的域名 TTL 设得很短服务端 QPS 一高DNS 查询就成了瓶颈导致大量请求超时。后面改成启动时解析一次 IP通过 IP 直连 自定义Host请求头来请求问题立刻缓解。排查 DNS 最实用的命令nslookup example.com看解析结果和用的哪台 DNS 服务器。dig trace example.com完整跟踪解析链路能看到每一级查询的耗时。Windows 下用ipconfig /flushdns清空本地 DNS 缓存macOS 是sudo dscacheutil -flushcache。3. 第二层建立连接——TCP 握手、TLS 加密与端口寻址3.1 为什么访问服务器要“IP 端口”DNS 把域名解析成了 IP但这还不够。一台云服务器上可能同时跑着多个服务Nginx 监听 80/443、Node 应用监听 3000、Redis 监听 6379、MySQL 监听 3306。如果没有端口操作系统拿到一个数据包根本不知道要交给哪个进程。IP 是“哪台机器”端口才是“哪个应用”。浏览器默认会在 HTTP 请求里用 80 端口、HTTPS 用 443 端口所以你在地址栏不用手动输入端口号。3.2 三次握手到底在握什么TCP 是面向连接的协议正式传输数据前要先建立连接。三次握手的过程客户端发送 SYN 包携带一个初始序列号比如 x。服务器收到后回复 SYNACK 包携带自己的初始序列号比如 y并确认客户端的 x。客户端再发送 ACK 包确认服务器的 y。双方都知道“你能收到我的消息我也能收到你的消息”连接建立。为什么一定要三次而不是两次关键在于确认双方的收发能力都正常。如果只有两次服务器无法确认客户端是否收到了自己的 SYNACK——万一客户端没收到它会把服务器发的数据当成无效数据丢弃连接就“假成功”了。三次握手之后两边都有了对方已确认的序列号后续数据包的顺序和重传才有依据。这个环节最容易出的问题是 SYN 洪水攻击者伪造大量 IP 发送 SYN 包但不完成后续握手把服务器的半连接队列塞满导致正常用户无法建连。所以云平台一般都有 DDoS 防护Linux 内核也默认开启了tcp_syncookies机制。如果你遇到“服务器 CPU 不高但新连接就是建不上”可以考虑是不是半连接队列被打满用netstat -s看 SYN 相关计数。3.3 HTTPSTCP 之上再加一层保险如果你用的是 HTTPSTCP 握手完成之后还有一次 TLS 握手。这个过程要协商加密算法、验证服务器证书、交换会话密钥比 TCP 握手复杂得多。TLS 握手失败时外表看起来就是“网页打不开”但 TCP 本身是通的。我用curl -v https://example.com调试时经常看到连接卡在SSL connection阶段——要么是证书链不完整要么是服务器的 TLS 版本和客户端不兼容要么是域名和证书不匹配。这里还有个细节一台服务器可能同时托管多个域名每个域名有各自的证书。客户端发起 TLS 握手时会在 ClientHello 里带上一个 SNIServer Name Indication字段告诉服务器“我要访问的是哪个域名”服务器再选择对应的证书。所以证书问题的排查必须基于“域名 IP”一起看单看 IP 往往看不出问题。3.4 云服务器的“两道门”安全组和防火墙这块很多新手完全没概念。云服务器的网络边界至少有两层第一层是云平台的安全组它在虚拟机外部做过滤。你在云控制台创建安全组规则时要明确放行哪些端口、哪些来源 IP。第二层是服务器系统内部的防火墙常见的如iptables、firewalld。两层都要放行流量才能进入 Node.js 进程。我帮人排查过这样一个案例Node 服务在 3000 端口跑得好好的本地用curl localhost:3000也通但公网访问死活不通。一查安全组只放行了 80 和 4433000 端口根本没开。修改安全组规则后秒通。反过来也有安全组放行了但 Node 服务监听的是127.0.0.1:3000而不是0.0.0.0:3000外部流量到了系统但进程只接受本机回环地址的连接照样不通。Node 的app.listen(3000)默认监听所有网卡但如果显式写了app.listen(3000, 127.0.0.1)就只有本机能访问了。判断端口是否通最快的命令是telnet 你的公网IP 80或者nc -vz 你的公网IP 80。如果ping通但telnet不通说明 ICMP 和 TCP 走了不同的策略——其实ping根本测不出端口它测的是网络层通不通别拿ping的结果来判断 Web 服务是否正常。4. 第三层HTTP 请求到达服务器后的第一站——反向代理与网关4.1 为什么 Node.js 前面还要加一层 Nginx很多新手直接让 Node 监听 80 端口也能跑但这在生产环境不是好方案。我推荐你在 Node 前面加一层反向代理最常用的是 Nginx。原因有几个静态资源处理图片、CSS、JS 这类文件由 Nginx 直接读文件返回不经过 Node 进程省下大量 JavaScript 执行时间和内存。Node 适合处理动态逻辑让它处理静态文件属于暴殄天物。HTTPS 证书终止证书配置在 Nginx 这一层Node 只处理 HTTP 明文请求内部通信简单可靠。如果多个应用共用一台服务器证书统一管理也更方便。负载均衡Node 进程可以起多个实例多核 CPU 下的cluster模式、或者多机部署Nginx 用upstream把请求分发到不同实例。请求限制和超时控制client_max_body_size限制上传体积、proxy_read_timeout控制后端响应超时这些在 Nginx 做比在代码里做简单得多。WebSocket 支持WebSocket 的Upgrade请求需要特殊的头转发配置Nginx 可以无缝支持。4.2 一个最基础的反向代理配置server { listen 80; server_name example.com; 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; } }每行都值得说道说道。proxy_pass http://127.0.0.1:3000是核心所有匹配到的请求都被转发到本机 3000 端口的 Node 进程。proxy_set_header Host $host太关键了——如果不把原始的Host头传过去Node 框架Express、Koa、Nest 等拿到请求时req.hostname会变成127.0.0.1:3000导致基于域名判断路由的中间件、生成绝对 URL 的逻辑全部出错。X-Real-IP和X-Forwarded-For则是为了把真实客户端 IP 传过去否则 Node 里拿到的req.socket.remoteAddress永远是127.0.0.1做 IP 限流和访问日志分析时全是废数据。4.3 顺带说下 Apache 那个热门报错写这篇时我注意到很多人搜ah00558: httpd.exe: 无法可靠地确定服务器的完全限定域名。这个报错在 Apache 启动时特别常见原因是httpd.conf里没有配置ServerName。它不一定是致命错误但确实会导致一些功能异常。为什么提这个因为它是一个典型的“链路中间层配置问题”案例——很多人想用 Apache 做反向代理结果启动时被这个警告吓住以为自己的代码有问题。链路里任何一层服务器的配置错误外表看起来都是“请求失败”排障时一定要有这个意识不只要会写代码还要看得懂你前面那层服务器的日志。4.4 网关层还有哪些角色除了自己装的 Nginx云平台通常还提供负载均衡产品SLB/CLB它可以做四层转发直接转发 TCP 流量或七层转发解析 HTTP 后转发。如果流量先经过负载均衡再到你的服务器链路就又多了一段。还有 API 网关它在反向代理的基础上增加了鉴权、限流、参数校验、计量等功能。有时你会发现请求返回了“未获得授权许可”“请求信息无效”这类错误——这说明请求其实已经过了网络层和连接层在网关或应用入口被策略拦下了并不是链路断了。理解这个边界排查时就不会去纠结 DNS 或安全组而是直接看网关配置和应用层校验逻辑。另外提一句 Tunnel 类工具比如 Cloudflare Tunnel它可以把内网服务通过隧道映射到一个公网域名省去“公网 IP 安全组放行”的环节。这个方案在个人项目和临时演示场景很好用但在企业生产环境还是会优先走 Load Balancer Nginx 的标准链路因为可观测性和权限控制更成熟。5. 第四层Node.js 进程内部——事件循环、请求对象与响应返回5.1 请求到达 Node 之后发生了什么一个 HTTP 请求的 TCP 数据到达服务器后内核把数据放进 Socket 接收缓冲区Node.js 进程通过事件机制感知到可读数据随后llhttpNode 内置的 HTTP 解析器把裸的 HTTP 报文解析成一个请求对象。在 Express 这类框架里你写的app.get(/user, handler)能拿到req和res就是这一系列解析封装的结果。请求对象里有你要的一切req.url是路径和查询参数req.headers是请求头req.body是解析后的请求体需要中间件如express.json()去解析 JSON。res则是你用来写响应状态码、响应头和响应体的对象。业务代码处理完后调用res.json()或res.end()数据从内核 Socket 发送缓冲区返回给客户端。5.2 单线程为什么能扛高并发Node.js 最常被质疑的就是“单线程能行吗”。实际上单线程指的是 JavaScript 主执行线程只有一个但 I/O 操作并不在主线程上做。文件读取、网络请求、数据库查询这类操作Node 会交给 libuv 线程池或操作系统异步接口处理完成后通过事件循环回调通知主线程。所以 Node 可以同时发起成千上万个 I/O 操作谁先完成先处理谁这就是它能支撑高并发 I/O 型应用的原理。我用一个生活类比餐厅里只有一个服务员主线程。客人点菜后服务员把菜单送到厨房异步 I/O然后立刻去服务下一桌客人不需要站在厨房等菜。菜做好了厨房喊一声服务员再端过去。只要服务员不在某个环节死等一个人也能接待很多桌。这也解释了一个常见故障如果业务代码里有同步的 CPU 密集型操作比如大文件 JSON 解析、加解密、复杂的正则匹配事件循环就被堵住了后续所有请求都得排队等它执行完。表现是服务器 CPU 可能都没跑满单核确实吃不满但所有接口的响应时间集体变慢。排查方法是在代码里加事件循环延迟监控或者用clinic.js、node --prof这类工具做性能分析。5.3 从一个最小服务看请求的完整旅程const http require(node:http); const server http.createServer((req, res) { // 1. 解析阶段已完成req 和 res 已就绪 // 2. 业务逻辑查数据库、调外部 API异步 const data queryDatabase(req.query.userId); data.then((result) { // 3. 拿到结果后组装响应 res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify(result)); }).catch((err) { res.writeHead(500); res.end(Internal Server Error); }); }); server.listen(3000, () { console.log(server listening on 3000); });真实项目里中间件链会在这中间做很多事情日志、鉴权、参数校验、错误处理。一个请求经过的完整路径大致是Nginx → 框架内置中间件 → 路由匹配 → 应用层中间件 → 业务处理器 → 异步 I/ODB/Redis/外部 API → 组装响应 → 返回 Nginx → 返回浏览器。每一步都可能耗时所以线上排查时需要知道请求到底停在哪一步。5.4 部署 Node 服务时的“最后一公里”部署环节有几个高频坑单独拎出来说。端口选择不建议让 Node 直接以 root 身份监听 80 端口一般让 Nginx 监听 80Node 监听 3000 或 8080。1024 以下端口需要 root 权限纯 Node 方案要么用 root 要么用cap_net_bind_service安全性不如 Nginx 代理方案。进程守护Node 进程一旦崩溃Nginx 会返回 502 Bad Gateway。必须用pm2、systemd或forever来做进程守护实现崩溃自动重启。我习惯用 pm2因为它还自带日志管理、负载均衡模式pm2 start app.js -i max可以按 CPU 核数启动多个实例对中小项目非常省事。Node 版本选择不要一看到新版本发布就去追。Node 的版本号分 Current 和 LTS生产环境老老实实用 LTS长期维护版本。网上有“node v24.20.0 is not yet released”这种报错的搜索词大概率是版本管理工具或镜像源同步滞后安装了一个尚未正式发布的版本号造成的。用nvm管理版本装之前nvm ls-remote确认版本存在且稳定。环境变量数据库密码、第三方 API 密钥绝对不要硬编码在代码里用环境变量管理。NODE_ENVproduction这个环境变量还会影响很多框架的日志级别和错误堆栈详情线上环境必须显式设置。5.5 你的 Node 应用访问数据库走的也是同样的链路Node 应用很少是孤独的它通常还要访问 MySQL、Redis 或第三方的 HTTP API。这些依赖访问同样要经历 DNS 解析、TCP 连接、可能有 TLS、应用层鉴权。一个项目慢未必是用户请求链路慢也可能是 Node 到数据库这一段慢。所以排障时不要只盯着入口还要看出口依赖。数据库连接池耗尽、连接超时参数不合理都会表现为接口变慢。6. 把整条链路串起来一次真实请求的逐层观察与问题定位6.1 浏览器 DevTools 是第一现场排查前端发起的请求打开浏览器开发者工具的 Network 面板点一个请求看 Timing 标签。这里会把这些时间分段展示Timing 项对应链路层常见瓶颈DNS Lookup域名解析DNS 服务器延迟、TTL 太短Initial ConnectionTCP 握手 TLS跨网络链路差、安全组丢包TTFB服务器处理反代 Node DBNode 业务慢、数据库慢Content Download响应体传输带宽不足、响应体太大这里有个经验如果 TTFB 特别高说明请求其实已经到了服务器问题出在应用层如果 Initial Connection 特别高那要先怀疑网络链路和安全组如果 DNS Lookup 高则优先检查解析配置。我曾经帮人看一个“接口偶尔要等 3 秒”的问题Timing 面板里 Initial Connection 频繁飙高排查到最后是云服务器 CPU 核数太少TCP 握手都没法及时响应。你看不打开这个面板很难想到问题出在这一层。6.2 curl 命令行可以更精确地分解耗时浏览器面板适合 UI 场景但脚本化监控和服务器端调试curl更顺手curl -w DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n -o /dev/null -s https://example.com/api输出结果里DNS对应解析耗时TCP对应连接建立耗时TLS对应 TLS 握手完成耗时TTFB是从开始到收到第一个字节的耗时——它包含了服务器端全部处理时间。对比TLS和TTFB的差值能大致判断服务器处理用了多久。如果TTFB远大于服务器日志里记录的处理时间说明中间还有一层延迟比如负载均衡排队如果基本一致问题就在应用本身。6.3 服务端视角Nginx 日志和 Node 日志时间戳对比我曾经排查过一个“部分请求 5 秒后才响应”的诡异问题。Nginx access log 显示的请求时间比 Node 应用日志里的到达时间早了 4 秒——也就是说请求到了 Nginx但 Nginx 花了 4 秒才转发给 Node。最后发现是 Nginx 的proxy_connect_timeout配置过大而 Node 服务当时是单实例过载大量连接在 Nginx 的 upstream 队列里排队。这种问题纯粹看代码是发现不了的必须两边日志对时间。排障原则逐层确认不要跳层。先确认域名解析正常吗nslookup再确认端口通吗telnet再确认 HTTP 响应正常吗curl -I再确认反代日志有没有记录最后确认 Node 日志有没有记录。这个过程就像检查一条水管从水龙头开始逐段摸过去总能找到漏水点。6.4 几个高频“假故障”的识别方法ping 得通但网页打不开ping走的是 ICMP 协议网页走的是 TCP 80/443。有可能是安全组只放行了 ICMP 没放行 TCP 端口或者 TLS 证书不匹配。不要用ping的结果判断 Web 服务。首页能开但接口全部 404大概率是 Nginx 的location规则有问题比如location /里配置了try_files指向静态目录导致/api也被当成静态文件路径去找找不到自然 404。前端收到“请求信息无效”“请求参数无效”请求已经穿透了网络层、连接层、反代层到达了应用入口被参数校验或网关鉴权拦下。这种错误说明链路是通的问题在应用层的数据格式——比如请求头里Content-Type没设成application/json或者请求体编码不是 UTF-8Node 解析后得到乱码。浏览器调试模式里你可以直接复制请求负载参数对照接口文档检查字段名和格式。WebSocket 连不上检查反代有没有配置Upgrade和Connection头。Nginx 默认不会转发 WebSocket 的升级请求需要在location里加proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;PUT/DELETE 等非 GET 请求被拦截某些云厂商的安全策略或 Web 应用防火墙会拦非常规方法或者 Nginx 配置里只允许了 GET/POST。检查反代配置和防火墙规则。6.5 一条可以直接抄的逐层排查清单症状排查命令/工具可能原因下一步动作域名解析不到 IPnslookup domainDNS 记录没配、TTL 没过期云控制台检查解析记录IP 对但端口不通telnet IP 端口安全组/防火墙/进程未监听放行端口检查监听地址端口通但 HTTP 无响应curl -v http://IPNginx 挂了或配置错了看 Nginx 日志Nginx 收到但 Node 没日志对比两边日志反代转发配置错误检查proxy_pass、网络Node 处理慢Node 应用日志、事件循环监控业务代码阻塞、DB 慢性能分析、慢查询日志返回 200 但数据不对接口响应体业务逻辑或参数解析错误代码层面排查我自己排查线上问题一般是先看整体时间分布DevTools 或 curl锁定是哪一个阶段慢再钻到那一层的日志里找细节。这条方法论比任何单一工具都重要。面对任何“请求失败”第一反应应该是问这是哪一层的失败然后带着这个问题去逐层验证。最后分享一个小习惯我会在项目里给每层的关键日志打上阶段标签[DNS]、[TCP]、[HTTP]、[APP]、[DB]同时在前端请求里带上一个X-Request-ID一路透传到 Nginx 和 Node 日志。这样一次请求无论卡在哪一层我都能通过同一个 Request ID 把所有相关日志串起来几分钟内定位问题。这套方法我用了很多年从单机 Node 服务到复杂微服务架构都适用。你从今天开始也可以试着用这种“分层”的视角重新审视你手上的项目。
返回列表