写网络协议系列写到这里终于轮到应用层了。这一层跟 TCP/IP 那种需要盯着状态机、序列号、重传窗口的底层机制完全不同它直接面对业务逻辑是绝大多数开发者、运维、测试天天打交道的地方。你写的接口、你调的网页、你配的域名、你用的 Wi-Fi 自动获取 IP背后全是应用层协议在工作。这讲的内容很适合三类人一是想系统梳理网络知识的后端开发二是被跨域、代理、CDN 各种概念绕晕的前端同学三是排查网络问题时经常不知道从哪下手的运维新人。只要带着“协议到底是怎么被设计出来的”这个问题往下读一定会有收获。1. 应用层全景网络协议栈里的“最后一公里”1.1 为什么先讲应用层很多讲网络协议的文章喜欢从物理层开始一层层往上讲但大多数人真正接触网络开发时第一个感知到的反倒是应用层。你往浏览器地址栏输入一个域名域名要解析成 IP这是 DNS你的电脑连上网自动拿到一个可用的 IP 地址这是 DHCP浏览器跟服务器之间交换数据这是 HTTP。可以说应用层就是普通人维度里“网络”两个字的样子。但同时应用层又非常依赖底层提供的能力。TCP 保证了数据不丢不乱UDP 保证了低延迟传输IP 解决了跨网络寻址的问题。理解这个顺序很重要应用层协议解决的是“业务语义”问题而下面的传输层和网络层解决的是“怎么把字节流可靠地送过去”的问题。没有底层应用层协议只是一堆说给空气听的话没有应用层底层协议也失去了存在的意义。我个人的学习习惯是先理解“这一层要解决什么问题”再去看协议细节。比如 DNS 要解决的是“人类记不住 IP 地址”的问题DHCP 要解决的是“每台设备手动配 IP 太痛苦”的问题HTTP 要解决的是“客户端和服务器之间怎么用统一的格式交流”的问题。带着问题去学习你记住的就不是死板的字段而是一套设计逻辑。1.2 本讲涉及的内容地图这一讲的内容跨度比较大我先画一张“地图”免得你后面看着看着迷路。域名与 DNS从域名结构开始理解域名的层级、解析流程、缓存机制以及如何排查解析慢或解析错误。DHCP看设备联网时是怎么自动获取 IP、子网掩码、网关和 DNS 的以及租约到期、地址冲突这类实际问题。HTTP这是最重的一个部分要理解 ABNF 语法、报文格式、请求方法、头部字段、状态码以及浏览器环境里绕不开的跨域问题。代理与 CDN把前面学的知识串联起来看看流量在经过正向代理、反向代理、CDN 边缘节点时链路和头部发生了什么变化。这几块内容表面上各自独立实际上有很强的联动关系。比如你访问一个网站浏览器先 DNS 解析拿到 CDN 边缘节点的 IP然后通过 HTTP 发起请求这个过程中可能还经过了代理服务器。所以建议你把这篇文章当作一条完整的链路去读而不是看成五个孤立的知识点。2. 域名与 DNS互联网的“通讯录”2.1 域名结构和 FQDN 到底怎么回事域名这个东西说简单也简单说复杂也能挖出不少细节。你在浏览器里输入www.example.com回车这个字符串在技术上叫做 FQDNFully Qualified Domain Name完全限定域名。它的结构不是随意排的而是严格遵守从右往左的层级关系。最右侧的com是顶级域Top-Level DomainTLD再往左是二级域example再往左的www叫做主机记录或者子域。这个层级其实就是一棵树根域在最高层通常表示为一个点.下面挂着各种顶级域再往下就是各级子域。你可以把整个 DNS 系统理解成一棵巨大的树每个域名都是这棵树上的一个节点。这里有个容易混淆的点www.example.com和example.com是两个不同的 FQDN它们各自可以解析到不同的 IP。很多人在配置服务时只加了example.com的 A 记录结果用www.example.com访问时发现解析不出来就是这个原因。另外还有一个容易被忽略的细节FQDN 结尾的理论上的点比如example.com.这种写法在 DNS 报文里才会看到日常使用中一般省略。域名解析常用的记录类型有这么几种记录类型作用典型场景A域名指向 IPv4 地址把example.com解析到1.2.3.4AAAA域名指向 IPv6 地址双栈部署时使用CNAME域名别名指向另一个域名把www.example.com指向cdn.example.comMX邮件交换记录指定收件服务器NS指定域名服务器告诉解析器该去哪查询TXT任意文本信息域名验证、SPF、DKIM 等CNAME 在 CDN 场景里特别重要。很多站点对外提供的域名只是一个“别名”真实解析结果指向 CDN 服务商分配的域名再由这个域名最终解析到边缘节点 IP。这样源站 IP 就不用暴露出来也方便 CDN 做全局调度。2.2 一次完整的 DNS 解析要经过哪几站先记住一个大前提DNS 客户端自己不会去遍历整个域名树它只负责把问题抛给一个“本地 DNS 服务器”后面的递归查询由服务器完成。这个本地 DNS 服务器通常由网络服务商提供也可能由你手动配置成公共 DNS 服务。整个查询链路大致是这样的浏览器先查自己的缓存。操作系统也有缓存浏览器缓存没命中会走系统解析。系统解析也没命中的话会去查hosts文件。这个文件在本地有最高优先级具体视系统配置而定适合做本地测试用的域名映射。如果hosts也没有请求就会发给配置好的本地 DNS 服务器默认走 UDP 53 端口。本地 DNS 服务器如果也没有缓存会替你去问根域名服务器“com顶级域的服务器是谁”根服务器返回com的 NS 记录。再去问com顶级域服务器“example.com的权威服务器是谁”顶级域服务器返回example.com的 NS 记录。最后去问example.com的权威服务器“www.example.com的 A 记录是什么”这次能拿到真正的 IP。本地 DNS 服务器把结果返回给客户端同时按照 TTL 缓存一段时间。这种“先问根、再问顶级域、最后问权威”的方式叫做迭代查询而客户端和本地 DNS 服务器之间通常是一次递归查询。整个链路里的每一层都有缓存所以一般不会真的每次都走完整流程。这里要提一下 TTLTime To Live。每条 DNS 记录都有一个 TTL单位是秒表示这条记录可以被缓存多久。TTL 设太短会导致解析请求频繁打到权威服务器增加压力TTL 设太长出现问题时要很久才能生效。我见过不少团队在迁移服务器时把 A 记录改了结果解析结果迟迟不更新最后排查下来发现是 TTL 还设在一个小时以上这就是典型的问题。2.3 工程排障我平时怎么查 DNS 问题在线上一篇讲原理的文章里排障实操往往最受欢迎因为这是文档里不一定会写的部分。当怀疑 DNS 有问题时第一步先要看系统当前用的是哪个 DNS 服务器。在多数系统上可以直接用nslookup查看默认服务器地址这里展示一个更通用的思路用dig命令能看到解析全过程这在多数类 Unix 系统里默认可用Windows 下也建议安装一个带dig的工具集。dig example.com这个命令会返回example.com.的 A 记录同时显示查询耗时和使用的 DNS 服务器。如果觉得结果不对可以指定 DNS 服务器再查dig 8.8.8.8 example.com注意8.8.8.8是一个常见的公共 DNS 地址这里仅为示意。实际生产环境建议使用你所在网络可用的公共 DNS 服务。想知道一次解析到底经过了哪些步骤可以用 trace 模式dig trace example.com这个命令会从根域开始把每一级返回的 NS 记录和最终结果都打印出来。通过它你能看出是哪个环节响应慢或者是哪一级返回了错误记录。如果某条权威记录返回的 IP 和你预期不符问题基本就在权威服务器这一侧如果本地 DNS 一直返回旧 IP那就先考虑清缓存。排查阶段有个顺序我觉得很重要先查本机hosts和缓存再查本地 DNS最后才怀疑权威服务器。因为在真实环境里绝大多数“DNS 解析不对”的问题都出在缓存层。另外内网环境里经常存在多个 DNS 域。同一个域名在办公网和公网可能解析到完全不同的 IP这叫“水平分割 DNS”Split DNS。你在内网排查问题时不要一上来就套用公网解析结果不然可能会被带偏。3. DHCP自动入网背后的“物业服务”3.1 为什么不能靠手动配置 IP在没有 DHCP 的年代给电脑配网络是一件枯燥且容易出错的事你得手动填 IP 地址、子网掩码、默认网关、DNS 服务器。填错了上不了网填重复了两个机器冲突整个办公室可能有一半人网络不通。DHCPDynamic Host Configuration Protocol动态主机配置协议的出现就是为了解决这个问题。它本质上是一个“分配管理员”设备接入网络后自动向服务器申请一个可用的 IP并拿到配套的网络配置。使用体验就是插上网线或者连上 Wi-Fi什么也不用配置就能上网。DHCP 基于 UDP 协议运行客户端使用 68 端口服务器使用 67 端口。这里有个有意思的点客户端在还没拿到 IP 时怎么跟服务器通信答案是用广播。它把自己的源 IP 设为0.0.0.0目标 IP 设为255.255.255.255受限广播地址在同一广播域内发出请求。3.2 DORA 四步从 Discover 到 AckDHCP 最经典的交互过程是四步缩写叫 DORADiscover发现、Offer提供、Request请求、Ack确认。Discover客户端发送广播说“你好我需要一个 IP 地址”。Offer服务器接收到后在该子网的地址池里挑一个可用 IP通过广播在早期或者单播回复给客户端说“你看这个 IP 行不行”。Request客户端收到 Offer 后如果认可就广播一条请求说“我决定使用这个 IP”这个广播的目的之一是告诉其他 DHCP 服务器“我已经选了别家你们的 Offer 不用了”。Ack服务器收到 Request 后再次确认分配并带上租约时长、子网掩码、网关、DNS 等配置客户端收到后就可以正式使用这个 IP 了。整个过程中xid事务 ID字段用来把 Discover 和后续的消息关联起来避免一个客户端收到无关服务器的 Offer 后造成混乱。还有一个常用的字段是 Option 53用来标识消息类型1 到 7 分别对应 Discover、Offer、Request、Decline、Ack、Nak、Release。需要注意的是不同系统在实现上会有些差异。有的场景下Offer 并不一定都由收到 Discover 的服务器主动发出特别是存在多个 DHCP 服务器的网络里客户端还可以选择使用哪个 Offer。另外当客户端续租时并不会重新走一遍 DORA而是直接进入 Request/Ack 阶段这样效率更高。3.3 租约、续租与常见故障DHCP 分配的 IP 不是永久的它有一个租约时长常见配置是几分钟到几天不等。租约到期前客户端需要在时间过半通常叫 T1时向服务器请求续租如果服务器没回复会在租约到期的 87.5%T2时尝试广播续租。如果整个租约已经到期IP 就会被释放回地址池供别的设备使用。我在实际工作中遇到过的 DHCP 故障大致可以归成几类第一类是“获取不到 IP”。最常见原因是地址池耗尽。整个子网的 IP 数量是有限的如果设备数量超过了地址池新设备就会一直处于获取状态。此时先检查地址池使用率再决定是扩大子网、缩短租约时间还是增加分段。第二类是“获取到 IP 但网关不通”。这种情况往往是设备跨 VLAN 后 DHCP 服务器没有正确下发网关地址或者下发的网关和子网不匹配。我见过一次事故排查了一圈发现是管理员在配置租约时把默认网关打错了一位数字。第三类是“IP 冲突”。服务器分配了同一个 IP 给两台设备导致其中一台掉线。现在很多交换机支持 DHCP Snooping可以监听 DHCP 报文并建立 IP-MAC 映射表防止用户私设静态 IP 造成冲突。对于排查来说先看服务器分配的记录再检查是不是有设备手动设置了和地址池重叠的静态 IP。当网络中存在多个网段而 DHCP 服务器只部署在核心机房时还需要用到 DHCP 中继。中继的作用是把客户端的广播请求转换成单播转发给远端的 DHCP 服务器。如果跨网段无法获取 IP第一反应应该检查中继配置而不是怀疑协议本身出现了问题。4. HTTP 协议最常用的应用层协议从报文到 ABNF4.1 一次 HTTP 请求在报文层长什么样HTTP 是一个基于文本的协议至少在 HTTP/1.x 时代是这样。它的报文结构清晰直白分成三个部分起始行、头部字段、消息主体。起始行和每个头部字段都以 CRLF回车换行即\r\n结尾头部和主体之间用一行空行\r\n隔开。一个典型的请求报文长这样POST /api/login HTTP/1.1 Host: example.com Content-Type: application/json Content-Length: 27 {username:admin,password:123}第一行POST /api/login HTTP/1.1是请求行由方法、请求目标、HTTP 版本三个部分组成。接下来的Host、Content-Type、Content-Length都是头部字段最后一个空行之后就是请求体。对应的响应报文长这样HTTP/1.1 200 OK Content-Type: application/json Content-Length: 52 {code:0,message:success,data:{id:1}}第一行HTTP/1.1 200 OK是状态行由版本、状态码、原因短语组成。和请求一样头部之后空一行再是响应体。我建议每个人都自己抓一次包或者用curl -v看一次完整报文因为只有亲眼见过报文你才会对“HTTP 是基于文本的”这句话有体感。看报文的时候注意一个细节头部字段名称不区分大小写但惯例是用首字母大写加连字符的格式比如Content-Type而不是content-type。4.2 ABNF 语法别怕这些字母缩写很多同学看到 ABNFAugmented Backus-Naur Form扩展巴科斯范式会本能地害怕觉得这是编译原理里才有的东西。其实它就是一套用来精确定义协议格式的“语法规则”和正则表达式有点像只是更偏向形式化定义。RFC 5234 是 ABNF 的基础定义。它使用一些简单的符号来表示重复、可选、选择等逻辑比如用a*belement表示重复多少个元素用方括号[element]表示可选用斜杠/表示“或”用双引号表示字面量字符串用%x表示十六进制字符。HTTP/1.1 的报文格式在 RFC 7230 里就是用 ABNF 定义的。比如HTTP-message start-line *( header-field CRLF ) CRLF [ message-body ] start-line request-line / status-line这段定义说的是一个 HTTP 报文由起始行开始后面跟零个或多个头部字段每个头部字段后面都有 CRLF再跟一个空行 CRLF最后是一个可选的消息主体。*表示“零个或多个”/表示“两者取一”。你不需要能背下这些规则只要会读能理解“头部字段后面跟 CRLF头部和主体之间有一个空行”这个结构是哪里来的以后看协议文档就不会发怵。ABNF 的意义在于消除歧义。比如头部字段中Content-Length: 27的冒号后面为什么有一个空格ABNF 里会定义成field-value *( field-content / obs-fold )obs-fold是新版本显式废弃的折叠写法。当你看协议文档时ABNF 就是最权威的“实现说明书”。4.3 请求方法语义、幂等与安全HTTP 请求方法定义了客户端希望对资源执行的操作。常用的有 GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS还有一些比较少见的 TRACE、CONNECT。先看个表格把方法语义和安全、幂等的区别理清楚方法语义是否安全是否幂等GET获取资源是是HEAD获取响应头不返回响应体是是OPTIONS查询服务器支持的能力是是POST在服务器创建资源或触发处理否否PUT完整替换资源否是DELETE删除资源否是PATCH对资源做部分修改否否“安全”在这里不是说不会泄露隐私而是指这个操作不应该改变服务器资源的状态。你把 GET 请求发一万次服务器上的数据不应该有任何变化。“幂等”则是指把同一个请求发多少次结果都和发一次一样。PUT 是幂等的因为你重复提交的都是同一个完整实体POST 不幂等因为每次提交都可能创建一个新的资源。实际开发里最常见的误区之一就是“用 POST 走天下”任何操作都 POST结果搞得接口设计完全没有语义排查日志时根本分不清每个请求的意图。我建议遵循一个原则查询用 GET新建用 POST整体替换用 PUT局部更新用 PATCH删除用 DELETE。状态变更类操作如果是从请求一开始就确定目标尽量设计成幂等这样网络重试时后果可控。4.4 头部字段常用头与容易踩坑的细节HTTP 头部字段数量非常多但经常打交道的其实就二三十个。我按类别整理一下最常用的通用头同时适用于请求和响应Cache-Control控制缓存行为比如no-cache、max-age3600。Connection控制连接是否保持比如keep-alive、close。Date报文发送的时间。请求头Host目标主机名和端口HTTP/1.1 里是必填项。User-Agent客户端标识。Accept客户端期望接收的内容类型。Accept-Encoding客户端支持的压缩算法常见的有 gzip、br。Content-Type请求体的媒体类型。Content-Length请求体的字节长度。Authorization携带认证凭据。Cookie携带会话状态。Origin发起请求的源跨域判断时用。响应头Server服务器软件标识。Set-Cookie服务器下发 Cookie。Location重定向目标地址。ETag实体标签用于协商缓存。Last-Modified资源最后修改时间。Access-Control-Allow-Origin跨域相关告诉浏览器允许哪个源访问。这里有几个容易踩坑的地方。第一个是Content-Length和Transfer-Encoding不能同时使用。如果报文里出现Transfer-Encoding: chunked就表示主体以分块方式传输长度由每一块的首部标记出来此时Content-Length应该被忽略。用curl调试分块响应时如果不看原始报文只通过 JSON 解析工具看结果可能看不出这个细节。第二个是Host头。HTTP/1.1 之后的虚拟主机依赖Host区分不同站点也就是说多个域名共用同一个 IP 是很正常的。你直接用 IP 访问一个站点很可能拿到默认站点而不是目标站点就是因为Host头不对。第三个是 Cookie 和Authorization的携带范围。跨域环境下默认情况下通过 JavaScript 发起的请求不会携带跨域 Cookie这在后面的跨域部分会详细讲。4.5 状态码1xx 到 5xx 的分类速查HTTP 状态码是服务器对请求结果的数字化表达。虽然数量很多但核心分类逻辑很清晰1xx 是信息2xx 是成功3xx 是重定向4xx 是客户端错误5xx 是服务器错误。状态码分类表分类含义代表状态码1xx信息性响应100 Continue101 Switching Protocols2xx成功200 OK201 Created204 No Content3xx重定向301 Moved Permanently302 Found304 Not Modified4xx客户端错误400 Bad Request401 Unauthorized403 Forbidden404 Not Found429 Too Many Requests5xx服务器错误500 Internal Server Error502 Bad Gateway503 Service Unavailable504 Gateway Timeout有几个状态码特别容易搞混我展开说一下。301和302都是重定向但一个是永久、一个是临时。对于 SEO 来说影响很大搜索引擎会把 301 的权重转移到新地址302 则不转移。所以如果要永久迁移域名一定用 301只是临时跳转到另一个页面用 302。另外还要注意 307 和 308它们是 302 和 301 的严格版本要求客户端重定向时不能改变请求方法和请求体。比如说一个 POST 请求用 302 重定向某些浏览器会把它变成 GET 请求而用 307 就能保持 POST 方法和 body 不被修改。304 Not Modified是缓存协商的典型结果。浏览器带上If-Modified-Since或If-None-Match头服务器判断资源没变就返回 304 和空 body表示“继续用本地缓存”。这个状态码不算错误但抓包时容易误以为请求失败。401和403的差别也很关键401 表示“未认证”服务器不知道你是谁403 表示“已认证但没有权限”。如果你的接口明明传了 Token 却返回 403先检查权限配置而不是重新登录。502和504是网关层的常见错误。502 一般指网关从上游收到了无效响应比如上游服务崩溃、迟迟没有响应504 则是网关在限定时间内没等到上游响应。排查顺序通常是先看上游服务日志再看网关超时配置最后看负载均衡器是否把请求转发到了已经不健康的后端节点。5. 跨域浏览器安全策略导致的“老熟人”5.1 同源策略到底防的是什么跨域Cross-Origin这个词做过 Web 开发的人不可能没听过。跨域的根源是浏览器的同源策略Same-Origin Policy。所谓“同源”指的是协议、域名、端口三者完全相同。http://example.com和https://example.com不同源http://example.com和http://api.example.com不同源http://example.com:8080和http://example.com:9090也不同源。同源策略的核心目的是防止恶意网页读取另一个网站的敏感数据。如果没有这个策略你在浏览某个恶意页面时它就可以通过脚本向你的网上银行发起请求然后读取响应内容你的账户信息就泄露了。要注意的是浏览器对“发请求”和“读响应”是区别对待的。在很多情况下跨域请求本身是能发出去的真正被拦截的是“读取响应结果”。这就是为什么你在控制台里经常看到“CORS error”但后端日志里明明收到了请求。5.2 CORS 如何放行简单请求和预检请求跨域解决方案里最标准、最正规的就是 CORSCross-Origin Resource Sharing跨源资源共享。它的核心机制很简单服务器在响应头里加上Access-Control-Allow-Origin告诉浏览器“这个源允许被跨域读取”浏览器据此决定要不要把响应交给你。举个例子假设前端运行在http://front.example.com后端接口在http://api.example.com后端返回如下响应头Access-Control-Allow-Origin: http://front.example.com浏览器看到这个头且值与当前页面源一致就会放行前端脚本能正常读取响应。如果服务器不返回这个头浏览器就把响应“藏起来”前端拿不到数据。CORS 还有一个关键概念叫预检请求preflight。当跨域请求不满足“简单请求”条件时浏览器会先发送一个OPTIONS请求问服务器“我要用这个方法带这些自定义头你允许吗”服务器通过Access-Control-Allow-Methods和Access-Control-Allow-Headers来回答。满足下面条件的请求是简单请求不需要预检方法是 GET、HEAD、POST 之一使用 CORS 安全列表里的请求头主要是Accept、Accept-Language、Content-Language、Content-Type且Content-Type仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain。一旦你设置了application/json的Content-Type或者使用了自定义头比如Authorization请求就会触发预检。我在调试接口时经常看到OPTIONS请求半路杀出来如果后端没有正确处理OPTIONS预检失败后续真正的请求根本不会执行。5.3 跨域开发中的常见坑与处理思路跨域问题很常见常见到我已经形成了条件反射前端报跨域错误先看响应头请求没到后端先想预检。第一个坑是Access-Control-Allow-Origin配成了*同时还需要携带 Cookie。浏览器规定带凭证credentials的跨域请求Access-Control-Allow-Origin不能是*必须指定精确源。也就是你用了withCredentials: true或fetch的credentials: include后端必须返回具体的源比如http://front.example.com并且还要设置Access-Control-Allow-Credentials: true。如果你不想支持 Cookie那*没问题但要明确它带来的安全含义。第二个坑是预检请求找不到对应路由。很多后端框架默认只注册了实际接口的 POST 路由收到OPTIONS请求后直接返回 404预检就失败了。解决方法是全局处理OPTIONS请求在网关或中间件层面统一返回允许的跨域头。第三个坑是 Nginx 之类的反向代理转发时跨域头丢失或者冲突。如果是前后端部署在同一域名下通过路径区分比如/api转发到后端这种“同源化处理”其实是绕过了跨域问题比开放 CORS 更简单也更适合生产环境。如果你只是做本地调试也可以用本地代理把前端的请求转发到目标接口让浏览器认为所有请求都发给了同一个源。这种做法与反向代理的原理是一致的后面讲代理时你会理解得更深。6. 代理与 CDN给流量“安排路线”的中间层6.1 三种代理正向、反向、透明别搞混代理这个概念在真实网络里无处不在但很多人一提到代理就只会想到“正向代理”。其实从流量路径上看代理的角色分得很清楚。正向代理是站在客户端一侧的。客户端的请求先发给代理服务器代理再转发给目标服务器。对于目标服务器来说它看到的是代理服务器的 IP而不是客户端的真实 IP。正向代理的典型用途包括访问控制、缓存、日志审计。你在公司上网流量很可能就经过了一台正向代理它能记录每个员工访问了哪些域名决定哪些站点可以访问。反向代理站在服务器一侧。客户端的请求先到达反向代理由代理决定转发到哪台后端服务器。对于客户端来说它只认识反向代理的域名并不知道背后有多少台服务器。反向代理的典型用途是负载均衡、SSL 卸载、统一入口。一个常见的配置是客户端访问https://example.com反向代理接收到请求后把请求转发到内网某一台应用服务器响应再原路返回。透明代理介于两者之间可以理解成“客户端无感知的正向代理”。客户端发出去的请求被网络设备通常是网关拦截并代为转发客户端甚至不知道自己经过了代理。有些内网审计系统就是这么做的。判断一个流量是否经过透明代理可以从响应头里是否有代理追加的字段来判断比如Via头。还有一个被反复提及的头是X-Forwarded-ForXFF它用来记录请求经过的每一级代理的客户端 IP。反向代理把原始客户端 IP 追加到这个头里传给后端后端就能拿到用户的真实 IP。这里有一个安全细节如果请求直接连到你的后端服务客户端可以伪造X-Forwarded-For所以后端在做 IP 白名单或日志分析时一定要在可信代理层重写这个头而不是直接信最外层传进来的值。6.2 CDN 是怎么把内容送到“你家门口”的CDNContent Delivery Network内容分发网络的核心思想是“内容离用户更近”。一个网站如果只有一个源站服务器用户遍布各地那么偏远地区的用户访问延迟就会很高。CDN 的做法是在全国甚至全球部署大量边缘节点把静态内容缓存到这些节点上让用户就近访问。这里要理解 CDN 的接入方式。网站一般会把自己的域名通过 CNAME 记录指向 CDN 服务商提供的域名再由 CDN 的调度系统通常基于 DNS 解析或者 Anycast 技术决定将用户请求引导到哪个边缘节点。整个过程对用户是无感的用户还是输入同样的域名但解析出来的 IP 可能今天是这个边缘节点明天相邻城市网络负载更低又换成另一个。当用户请求到达边缘节点后节点先看自己有没有缓存这份内容。如果缓存了就直接返回这叫“命中”如果没缓存节点会向源站发起请求拿到内容后再返回给用户同时自己也留一份缓存这叫“回源”。CDN 适合加速的对象首先是图片、视频、CSS、JS 这类静态文件因为它们不常变化缓存收益极高其次是下载场景文件分发给边缘节点后能大幅降低源站带宽压力。对于动态 API 接口虽然也能通过 CDN 做动态加速但一般需要额外配置不能当作静态缓存来看。另外还要区分 CDN 和负载均衡。负载均衡是把流量分发到多个后端服务上后端服务本身要承担实际计算而 CDN 侧重于内容的就近分发和缓存边缘节点一般不执行业务逻辑只做静态内容响应或转发。6.3 CDN 缓存与命中率调优的实操笔记如果你负责一个使用 CDN 的站点最关心的指标通常就是缓存命中率。命中率低意味着大量请求都回源了CDN 的加速效果就大打折扣。影响命中率的第一配置是缓存控制头。源站返回的Cache-Control和Expires头会直接决定边缘节点能否缓存以及缓存多久。比如Cache-Control: max-age3600这表示内容可以缓存 3600 秒。如果源站返回了Cache-Control: no-storeCDN 一般不会缓存这份内容。很多团队在动态接口上误加了缓存头导致用户看到旧数据又在静态资源上没配置长缓存导致命中率极低。我建议静态资源文件名带上内容哈希比如app-8f3d2a.js这样文件名一变就被视为新资源可以放心设置很长的 max-age。第二个要点是理解“刷新”和“预热”。当源站内容更新后运维人员需要手动刷新 CDN 节点的缓存否则用户可能一直看到旧版本。刷新分为目录刷新和 URL 刷新目录刷新粒度粗但也简单粗暴URL 刷新更精准。预热则是主动把某些资源预先推送到边缘节点适合内容即将上线或刚更新完的场景。第三个要点是 HTTPS 证书管理。启用 CDN 后用户访问的是 CDN 节点的域名证书也得在 CDN 侧配好。源站本身可以使用内部证书反正回源链路一般在内网但要做好回源鉴权防止有人绕过 CDN 直接访问源站。还有一个容易被忽略的点CDN 只是把流量入口挡在了前面源站服务器一定要限制只允许来自 CDN 的回源请求否则一旦源站 IP 暴露攻击流量可以直接打在源站上CDN 的防护作用就形同虚设。我在实际项目里排查“用户访问慢”时会先看 DNS 解析耗时如果是 CDN 节点很慢再检查命中率如果解析出来的 IP 不对或者节点距离很远再看调度策略。这个分层排查的习惯帮我省了很多时间建议你也按这个思路建立自己的排障顺序。