想象你去图书馆借书。管理员告诉你:“这本书有副本,你直接拿走就行,不用登记。”这就是强缓存。但有时管理员会说:“我帮你查一下原版有没有修订,如果没有,你手上的副本还能接着用。”这就是协商缓存。而每次都必须去总馆重新调阅、复印一份新书的,则是完全没有使用缓存的网络请求。 HTTP缓存机制,就是浏览器与服务器之间约定的一套“借书规则”。它决定了哪些资源可以放心地在本地复用(强缓存),哪些资源需要先问服务器“我的副本还新吗”(协商缓存),以及你手里的副本是否已经“借阅过期”。这套机制是Web性能优化的基石——理解它,你就能解释为什么刷新页面时有些资源瞬间加载、有些却要等网络往返。
对AI从业者而言,理解HTTP缓存是排查线上模型服务延迟、优化推理接口响应速度的必备技能——你部署的模型服务,同样要面对“缓存该不该用、怎么用才不出错”的问题。
可视化演示
\u5c55\u793a32\u4f4d\u5355\u7cbe\u5ea6\u6d6e\u70b9\u6570\u7684\u5b58\u50a8\u683c\u5f0f\uff1a1\u4f4d\u7b26\u53f7\u4f4d\u30018\u4f4d\u6307\u6570\u4f4d\u300123\u4f4d\u5c3e\u6570\u4f4d\uff0c\u8c03\u6574\u6570\u503c\u53ef\u5b9e\u65f6\u67e5\u770b\u4e8c\u8fdb\u5236\u5206\u89e3\u8fc7\u7a0b\u3002
本节配有交互动效(进入视口后自动加载,约 3KB)。
IEEE 754\u6d6e\u70b9\u6570 S\u7b26\u53f7\u4f4d E\u6307\u6570\u4f4d M\u5c3e\u6570\u4f4d (-1)^S\u00d72^(E-127)\u00d71.M \u8c03\u6574\u6ed1\u5757\u67e5\u770b\u5206\u89e3
缓存初识
继续我们的图书馆。你发现管理员有一套“常借图书登记册”:你第一次借《深度学习》时,管理员从总馆调来一本,并告诉你:“这本书我复印了一份放你桌上,下次直接看复印件。”此后再借,如果复印件还能用,你连管理员都不用找——这是强缓存找副本的直觉。
但在计算机网络里,“复印件”就是浏览器本地磁盘上的缓存文件。HTTP协议设计者们意识到:Web页面里90%的资源(图片、CSS、JS)在短时间内根本不会变化,每次访问都从服务器重新下载,是巨大的浪费。于是有了缓存机制——把服务器返回的资源连同“保鲜期”一起存下来。
HTTP缓存分两大类:强缓存(浏览器自认为副本新鲜,完全不发请求)和协商缓存(浏览器不确定副本是否新鲜,发一个轻量请求让服务器做裁决)。两者的共同点是都用上了本地副本;区别在于,强缓存省掉了整个网络往返,协商缓存则多了一次“确认”。
那么,是不是缓存越多越好?也不是。缓存就像复印本——如果原著已经修订,你还捧着旧复印件,就会看到过时信息。这就是缓存与“新鲜度”的永恒矛盾:缓存要快,就必须大胆复用;但要准确,就必须小心验证。强缓存与协商缓存,正是解决这一矛盾的两把尺子。
- 响应请求数所有未真正访问网络的请求,例如强缓存直接命中、协商缓存返回304(也计为命中但需一次往返)。
- 总请求数页面发起的全部HTTP请求,包含命中与未命中的。
- 比值与百分比两者相除后乘以100,得到0到100之间的百分比。
强缓存规则
现在管理员给了你一张“绿色通行证”:某些书,你在借阅期内可以直接从桌上拿走副本,连登记都省了。这张通行证上写着有效期——这就是强缓存。HTTP协议里,服务器通过响应头告诉浏览器:“这个资源,在过期之前你都别来问我,直接用本地副本。”
强缓存的核心指令是Cache-Control,它是HTTP/1.1时代制定的标准,也是最灵活的缓存控制头。它的取值组合非常丰富:max-age=3600表示副本在3600秒内新鲜;public表示任何节点(包括代理服务器)都能缓存;private表示仅供单个用户缓存。与之相对的是HTTP/1.0时代的Expires,它用一个绝对时间戳(如Wed, 21 Oct 2025 07:28:00 GMT)指定过期时刻。
如果响应头同时出现了Cache-Control和Expires,以Cache-Control为准——它是更现代的规则,能处理时区、时钟偏移等问题。比如Expires是绝对时间,一旦用户电脑时间不准,强缓存就立刻失效;而max-age是相对时间,从响应时刻开始计时,天然免疫时钟错误。
强缓存的命中不需要任何网络请求,浏览器直接从本地读取文件。所以你在DevTools里常见的"200 (from disk cache)",就是强缓存命中的标志。它的速度极快,近乎瞬时。但代价是:在保鲜期内,服务器即使更新了资源,你也看不见。
- 设置保鲜期服务器在响应头写入
Cache-Control: max-age=3600,等价于告诉浏览器“接下来1小时内,这个副本都是新鲜的”。 - 记录响应时间浏览器收到响应时,记下当前时间作为起点。
- 计算剩余寿命用保鲜期减去已经过去的时间,得到的结果就是副本还能直接用的时长。
协商缓存规则
借阅期的概念再推进一步:假设管理员说,“这些书你桌上的副本可以一直用,但我要求你每次来还书时,顺口问我一句'这书改过没'?如果我说没改,你就继续用旧副本。”这就是协商缓存——本地副本可能过期,但浏览器愿意先花一次轻量请求,换取“可能不用下载完整资源”的机会。
协商缓存有两个判据:Last-Modified(最后修改时间)和ETag(实体标签)。Last-Modified是服务器返回资源时附带的“最后修改时间”,浏览器下次请求时通过If-Modified-Since把这个时间带回给服务器,服务器对比当前资源的修改时间,若未变化则返回304状态码。
ETag则更精确——它是服务器为资源内容生成的哈希指纹(如"33a64df5"),只要内容变了,ETag就变。浏览器通过If-None-Match把ETag带回,服务器比对当前资源的ETag:
那Last-Modified和ETag谁更好?ETag更可靠——因为文件可能改了又改回原样,修改时间变了但内容一致;或者修改时间精度不够(如1秒内多次修改)。但ETag需要额外计算哈希。实践中通常同时使用:请求时两个验证头都带上,服务器先比对If-None-Match,若不一致再比对If-Modified-Since。只要任意一个验证通过,就返回304。
协商缓存的价值在于“用一次轻往返换掉一次重下载”。对于大体积的静态资源(几MB的JS文件),304省下的流量和带宽相当可观。它不像强缓存那样零请求,但比完全重新下载高效得多。
- 生成指纹服务器在首次响应时,用资源内容计算出一个哈希字符串。
- 回传验证浏览器再次请求时,把ETag放入
If-None-Match请求头。 - 服务器比对服务器计算当前资源的ETag,和请求头里的值比较。
- 结果分岔相等则304(未修改),不相等则200+新资源。
优先级判定
现在你手上有两本规则手册:一本写着“绿色通行证优先,过期了再去问管理员”,另一本写着“就算没过期,管理员也可能要求你验证”。当资源同时带有强缓存和协商缓存的条件时,浏览器遵循一个严格的顺序——先强缓存,后协商缓存。
这个优先级顺序是浏览器内置的:
特别注意:Cache-Control内部也有优先级。比如no-cache这个指令,它的意思是“可以用缓存,但每次都得先验证”,所以它实际上会强制跳过强缓存、直接进入协商缓存阶段。而max-age=0也有类似效果——剩余寿命恰好为0,强缓存失效,转入协商。
代理缓存的引入让优先级更加复杂:浏览器与源站之间可能还有CDN节点。浏览器先查自己的强缓存,没命中就请求CDN;CDN再查它的强缓存,没命中就回源站验证。每一层都是“先强后协”的递归结构。理解了这点,你就理解了整个Web缓存的骨架。
- 第一道闸门浏览器先检查
Cache-Control/Expires,若副本新鲜则直接命中,整个流程到此结束。 - 第二道闸门强缓存过期后,浏览器携带
If-None-Match/If-Modified-Since发送请求。 - 最后手段服务器返回200,浏览器下载新内容并更新缓存。
no-cache辨析
“no-cache“这个名字极具迷惑性——字面意思是”不要缓存“,但它真正的含义是”可以用缓存,但每次用之前必须验证“。这就像图书馆的”禁带出馆”书籍:你可以坐着看,但每次都要跟管理员登记一次。
所以Cache-Control: no-cache的效果是:强制跳过强缓存,每次请求都走协商缓存流程。它不禁止存储,只是禁止“未经验证的直接复用”。而Cache-Control: no-store才是真正的“禁止缓存”——浏览器和中间代理都不得保存任何副本。
这两个指令的误用代价很高。如果给静态资源加了no-store,每次刷新页面都要重新下载全部资源,配置CDN缓存时会被回源请求淹没;如果给包含敏感信息的API响应加了no-cache(而非no-store),虽然每次会验证,但缓存副本仍留在磁盘上,有安全风险。正确的姿势是:静态资源用max-age+指纹,动态HTML用no-cache,敏感数据用no-store。
- no-cache语义允许浏览器保存副本,但每次使用前必须发请求给服务器验证有效性。
- no-store语义禁止任何缓存组件保存副本,每次请求都必须从源站获取完整资源。
- 混用场景两者可以同时出现,但其实no-store已经包含了no-cache的语义。
指纹化策略
图书馆有个聪明的管理员:他给每本书的修订版起了个新名字《深度学习·第3版》。当第4版出来时,读者借的是《深度学习·第4版》,永远不会拿错版本。这就是指纹化——给静态资源的文件名打上内容哈希。
现代前端构建工具(Webpack、Vite)会自动把app.js变成app.a3f9d2.js,其中a3f9d2是文件内容的哈希值。只要文件内容变了,哈希就变,文件名也随之改变。这个新文件名意味着它是一个全新的资源,可以放心地设置超长max-age(比如一年)。
指纹化策略与CDN配合时,威力更大。假设你的静态资源部署在CDN上,CDN节点会缓存app.a3f9d2.js一年。当app.b4c0e1.js(新版本)上线时,用户的浏览器发现需要请求一个全新的URL,于是绕过旧缓存,直接从CDN拉取新文件——这一过程中,CDN的旧缓存依然有效,无需手动刷新。
但这里有个陷阱:index.html本身不能设长缓存。因为HTML是“入口文件”,它引用的JS/CSS文件名会随版本变化,如果HTML被强缓存一年,用户就永远看到旧页面。标准做法是:HTML用no-cache(每次验证),静态资源用指纹+长缓存。这样HTML始终最新,而静态资源只下载一次。
immutable表示“文件永不变”,浏览器连协商验证都省了,直到文件名变化。- 计算内容哈希构建工具读取文件内容,计算哈希值(如MD5、SHA-1的短片段)。
- 拼接新文件名将哈希嵌入文件名,内容一变,文件名就变。
- 设置超长缓存因为文件名与内容一一对应,文件名不变的资源内容必然不变,可以放心使用极长缓存时间。
AI 应用场景
模型服务API的缓存编排
阶段:模型推理服务部署与优化
解决的问题:AI模型的推理结果通常需要几百毫秒到几秒,如果同一个请求(如文本分类、图像识别)反复打满GPU,成本极高且延迟不可控。HTTP缓存可以在不降低服务质量的前提下,把重复请求的响应时间从秒级降到毫秒级——强缓存命中时甚至不经过网络,协商缓存则用ETag验证请求参数是否变化。
具体案例:某内容审核API,对同一张图片的重复审核请求,服务端返回Cache-Control: max-age=300 + ETag。CDN层存储审核结果,99%的重复请求在CDN节点直接命中,源站GPU负载下降70%。当审核策略调整(如新增违禁词库)时,通过更新ETag强制协商缓存失效,实现“秒级生效、全网同步”。
大模型Web应用的静态资源加速
阶段:AI应用前端交付
解决的问题:LLM Web应用(如ChatGPT类产品)的JS包动辄数MB,每次刷新都完整下载会严重拖慢首屏。利用指纹化策略让JS/CSS长缓存,用户二次访问时几乎秒开;用no-cache保护index.html,保证新功能上线后用户能第一时间拉到最新入口。
具体案例:某AI绘画Web应用,原来每次发布新版本都要引导用户强制刷新(Cmd+Shift+R),否则会白屏报错。引入指纹化后,构建产物自动携带哈希,HTML走协商缓存验证版本,JS/CSS走一年强缓存——发布新版本后,用户刷新页面即自动加载新版,无需任何手动操作。
特征数据接口的缓存分层
阶段:特征工程与在线推理
解决的问题:推荐系统的特征服务每天被调用数十亿次,特征数据(如用户画像)在几分钟内通常不会有变化。如果能缓存这些特征,就能大幅降低特征存储层的QPS压力。用Cache-Control: max-age=60, private对用户级特征做强缓存,用ETag对群体特征做协商验证,在延迟和新鲜度之间找到平衡。
具体案例:某电商推荐系统,用户兴趣标签接口加缓存后,P99延迟从85ms降至12ms,特征存储集群的QPS压力减少60%。同时用ETag的版本号机制,在推荐策略迭代时对特征缓存做批量失效,保证模型能及时感知特征分布变化,避免“旧特征喂新模型”的线上线下不一致问题。
Python 演示
# -*- coding: utf-8 -*-"""HTTP缓存机制模拟器:演示强缓存与协商缓存的完整流程"""import hashlibimport time class CacheEntry: def __init__(self, data, max_age, etag=None, last_modified=None): self.data = data self.max_age = max_age self.etag = etag or hashlib.md5(data.encode()).hexdigest()[:8] self.last_modified = last_modified or time.time() self.stored_at = time.time() def is_fresh(self): return (time.time() - self.stored_at) < self.max_age class Server: """模拟源站:资源会随时间变化""" def __init__(self): self.content = "v1: hello world" self.updated_at = time.time() def update(self, new_content): self.content = new_content self.updated_at = time.time() def get_response(self, if_none_match=None, if_modified_since=None): etag = hashlib.md5(self.content.encode()).hexdigest()[:8] last_modified = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(self.updated_at) ) if if_none_match == etag: return 304, None, etag, last_modified if if_modified_since and self.updated_at <= if_modified_since: return 304, None, etag, last_modified return 200, self.content, etag, last_modified class Browser: """模拟浏览器缓存行为""" def __init__(self, server): self.server = server self.cache = {} self.request_count = 0 def fetch(self, url): self.request_count += 1 entry = self.cache.get(url) if entry and entry.is_fresh(): return "strong-cache-hit", entry.data if entry: status, data, etag, lm = self.server.get_response( if_none_match=entry.etag, if_modified_since=entry.last_modified, ) if status == 304: self.cache[url] = CacheEntry( entry.data, entry.max_age, etag, entry.last_modified ) return "negotiated-304", entry.data status, data, etag, lm = self.server.get_response() self.cache[url] = CacheEntry(data, max_age=10, etag=etag) return "fresh-200", data def main(): srv = Server() browser = Browser(srv) for i in range(5): res, body = browser.fetch("/api/news") print(f"请求{i+1}: {res} | 内容: {body}") if i == 2: print("[服务器更新内容]") srv.update("v2: hello world, refreshed") time.sleep(0.5) if __name__ == "__main__": main()
class CacheEntry缓存条目,记录内容、保鲜期(max_age)、ETag指纹和存储时间,对应浏览器缓存的元数据。def is_fresh(self)判断剩余寿命是否大于0,这是强缓存命中的核心条件。class Server模拟源站,维护资源内容和最后修改时间,update方法模拟服务器更新资源。def get_response服务器核心逻辑:先比ETag,再比Last-Modified,条件满足就返回304。if if_none_match == etag:服务器端ETag精准比对,内容只要变过,ETag必然不同。class Browser模拟浏览器,维护本地缓存字典和请求计数器。if entry and entry.is_fresh():第一道闸门:强缓存命中,直接返回本地副本,零网络请求。if status == 304:第二道闸门:协商缓存,服务器确认副本仍有效,用旧的但不重新下载。self.cache[url] = CacheEntry(...)200响应后更新本地缓存,写入新的ETag和保鲜期。
常见误区
误区我一开始以为`no-cache`就是完全不缓存,每次都要重新下载。给所有静态资源都加了`no-cache`,结果页面刷新特别慢,因为每个CSS、JS都要重新拉一遍。
正解no-cache的真实含义是“允许缓存,但每次使用前必须向服务器验证”。它禁止的是“不经验证的直接复用”,而不是“存储”。就像图书馆的“馆内阅览”——书可以放在你桌上,但每次拿起来看都要登记一次。真正禁止存储的是no-store。把静态资源加no-cache,等于放弃了强缓存的全部收益,但也没获得no-store的安全性,是最亏的配置。
为何常见:因为指令的英文字面意思太有欺骗性,“cache“这个词让人直接联想到”缓存“,所以”no-cache“自然被理解成”无缓存”。HTTP规范文档里对它的定义又比较绕,非专业读者很容易望文生义。
误区我担心给静态资源设置一年`max-age`后,用户就永远看到旧页面。等我自己改了代码重新部署,发现很多用户还在用老版本,就再也不敢用强缓存了。
正解这个问题的解法不是放弃缓存,而是改变缓存的“键”。指纹化策略让文件名随内容变化——新版本发布后,HTML引用的JS/CSS文件名不同,浏览器会当作全新资源请求,完全绕开旧缓存。就像图书馆给修订版起新书名,读者借《深度学习·第4版》自然拿不到第3版的内容。关键在于:可以对静态资源设长缓存,但绝不能对HTML设长缓存,因为HTML是入口,必须保持最新。
为何常见:大多数开发者的第一次缓存优化都是“设个max-age”这种单一维度操作,没意识到缓存策略需要“入口文件动态化、子资源静态化”的组合拳。加上浏览器刷新(F5)和强制刷新(Ctrl+F5)行为不同,更加深了“缓存导致更新失败”的错觉。
误区我图省事,只用了`Last-Modified`。结果有一次资源在1秒内被修改了两次,第二次修改后内容其实和第一次一样,但浏览器的请求还是拿到了200而不是304,白白下载了一遍。
正解Last-Modified的精度是秒级,而且只能反映“修改时间”,无法感知“内容是否真的变了”。ETag是内容哈希,只要字节不同它就不同。正确的做法是同时使用:服务器先比对If-None-Match,不一致再比对If-Modified-Since。对于CDN回源、负载均衡器转发的场景,代理服务器可能改写资源,Last-Modified会失真,ETag依然是最终裁决者。
为何常见:Last-Modified是HTTP/1.0时代就有的老字段,资料多、用起来简单,而ETag是HTTP/1.1才引入的。很多人接触到的是老教程,自然只学会了前者,不知道两者其实可以取长补短。
误区我负责的是API后端,觉得缓存是前端和CDN的事,后端接口不需要考虑HTTP缓存。结果同一份IDEA数据每天被不同用户反复请求,数据库压力很大。
正解API接口同样能从缓存获益——Cache-Control: private允许浏览器缓存但禁止共享缓存,适合用户维度的个性化数据;public配ETag适合内容不怎么变化的公共数据。AI推理服务是更典型的例子:相同的输入图片在审核、OCR场景中经常重复出现,用ETag验证加CDN缓存,可以把重复请求的GPU负载几乎降为零。缓存不是静态资源专属,而是要区分数据的“隐私性”和“易变性”来选择策略。
为何常见:教程和博客大多用CSS、JS举例讲缓存,给人造成“缓存=静态资源”的思维定式。实际上HTTP缓存是一种通用协议机制,与资源类型无关,关键是响应头怎么配。
误区我在服务器上给接口加了`Cache-Control: max-age=60`,但测试时发现每次请求都还是打到服务器,根本没走缓存。我以为是浏览器不行,换了好几个浏览器都一样。
正解max-age生效的前提是响应状态码可缓存(如200),且没有no-cache、no-store等更强制令干扰。更常见的原因是:代理或CDN层可能把Cache-Control头剥离或改写,浏览器收到的响应头里根本没有max-age。另外,POST请求默认不可缓存,只有GET才能利用缓存。排查时先看DevTools里"Response Headers"是否真的有缓存指令,再确认请求方法是GET、状态码是200。这就像管理员给你发了张通行证,但快递寄丢了——你手里根本没有证。
为何常见:很多人只关注“应用层怎么设”,忽略了中间链路(Nginx、CDN、云厂商WAF)可能改写响应头。加上浏览器对不同状态码、不同请求方法的缓存策略有隐藏规则,实际生效情况与直觉差异很大。
面试题
Q1. 强缓存命中时,浏览器会发生什么?
- A.发送请求到服务器,服务器返回304
- B.不发送请求,直接从本地缓存读取资源← 正确答案
- C.发送请求到服务器,服务器返回200
- D.发送请求到CDN节点重新下载
答案:B。强缓存的核心特征就是“零网络请求”——浏览器根据Cache-Control或Expires判断副本还在保鲜期内,直接走"200 (from disk cache)"。其他选项描述的都是协商缓存(304)或缓存过期(200)的场景。就像你桌上的复印本还在有效期内,管理员放行,你直接看即可,不用再去柜台登记。
Q2. Cache-Control: no-cache的正确含义是?
- A.禁止浏览器保存任何缓存副本
- B.允许缓存,但每次使用前必须向服务器验证← 正确答案
- C.只允许代理服务器缓存,源站不缓存
- D.缓存时间设为0秒,之后立即失效
答案:B。no-cache是“可以用,但每次都要验证”,它会强制跳过强缓存阶段直接进入协商缓存。真正禁止存储的是no-store。选项A是no-store的含义,C是public/private的混淆,D误将相对时间指令当成“立即过期”。
Q3. 关于ETag和Last-Modified,下列说法正确的是?
- A.ETag是绝对时间戳,Last-Modified是内容哈希
- B.ETag是内容哈希,Last-Modified是最后修改时间← 正确答案
- C.两者都是时间戳
- D.两者都是内容哈希
答案:B。ETag是服务器为资源内容生成的唯一标识(通常是哈希值),内容一变ETag就变;Last-Modified是资源的最后修改时间。ETag比时间戳更精确,能感知“修改时间没变但内容变了”的情况,所以两者同时使用时,服务器优先比对ETag。
Q4. 浏览器请求一个资源时,正确的缓存判断顺序是?
- A.先协商缓存验证,再检查强缓存是否新鲜,最后重新下载
- B.先检查强缓存是否新鲜,新鲜则直接使用;过期再协商缓存验证;最后重新下载← 正确答案
- C.先重新下载,再判断是否缓存
- D.先发请求给CDN,再检查本地缓存
答案:B。浏览器的缓存流程是“两道闸门”:第一道强缓存判断副本是否新鲜,新鲜就终止流程;不新鲜才进入第二道协商缓存,携带ETag/Last-Modified验证;验证失败才重新下载。就像你先看桌上复印本的日期,过期了才去问管理员,而不是一上来就去找管理员。
Q5. 指纹化策略(文件名加哈希)的核心原理是?
- A.让服务器可以加密资源内容
- B.让文件名随内容变化,从而安全地使用长缓存← 正确答案
- C.让浏览器每次都能找到相同文件名的资源
- D.让CDN可以压缩资源体积
答案:B。指纹化把内容哈希拼进文件名,app.a3f9.js与内容一一对应——内容不变文件名就不变,内容变了文件名就变。这样旧文件可以放心设置一年max-age,新文件因为是全新URL自然绕开缓存。这就是“内容寻址”的威力,与加密、压缩无关。
Q6. 以下哪个响应头组合最适合“每个用户的个性化数据,且不希望被CDN共享缓存”?
- A.
Cache-Control: public, max-age=3600 - B.
Cache-Control: private, max-age=3600← 正确答案 - C.
Cache-Control: no-store, max-age=0 - D.
Cache-Control: proxy-revalidate, max-age=60
答案:B。private明确禁止共享缓存(CDN、代理),只允许浏览器私有缓存,完美匹配“个性化数据”的场景;max-age=3600允许浏览器在一小时内复用。public会让CDN也缓存,可能把用户A的数据发给用户B。no-store连浏览器也不给缓存,牺牲了缓存收益。
Q7. 关于304 Not Modified响应,以下说法正确的是?
- A.304响应包含完整的响应体
- B.304响应体为空,只包含少量头信息← 正确答案
- C.304表示服务器确认本地副本仍然有效← 正确答案
- D.304表示资源已永久删除
答案:BC。304 Not Modified的响应体为空,它的全部意义就是“用头信息告诉浏览器:你这副本还能用”——这就是协商缓存的精髓:用几十个字节的头替代几MB的响应体,节省大量带宽。选项D的“永久删除”是410状态码,A与B的事实相反。
Q8. 哪些情况下会绕过强缓存,直接向服务器发送请求?
- A.用户按Cmd/Ctrl+F5强制刷新← 正确答案
- B.缓存副本已超过max-age← 正确答案
- C.响应头包含
Cache-Control: no-cache← 正确答案 - D.资源URL以
.css结尾
答案:ABC。强制刷新会在请求头加Cache-Control: no-cache绕过强缓存;max-age过期自然进入协商或重新请求;响应头带no-cache则每次都要验证。而资源后缀名与缓存无直接关系——只要响应头允许,.css文件也能命中强缓存。D选项是常见误解。
Q9. 服务器同时收到If-None-Match: "abc"和If-Modified-Since: Tue, 15 Aug 2023 10:00:00 GMT,当前资源ETag为"def",最后修改时间为2023年8月16日。服务器应返回?
- A.304,因为Last-Modified之后内容未变
- B.200 + 新资源,因为ETag不一致← 正确答案
- C.200 + 新资源,因为Last-Modified变了
- D.404,因为资源已删除
答案:B。服务器对协商缓存请求的判定顺序是:先比对ETag,ETag不一致(abc vs def)就直接返回200+新资源,不再看Last-Modified。这不是说Last-Modified没用了——在ETag一致或没有ETag时才轮到它。这里ETag已经判定内容不同,说明资源确实更新了,必须返回新内容。B选项里的“Last-Modified变了”与最终返回200的判断无关,因为ETag已经做出了裁决。
Q10. 一个大型Web应用希望“静态资源长缓存、入口HTML永远最新、敏感接口不落盘”。合理的配置是?
- A.
app.a3f9.js设置Cache-Control: max-age=31536000, immutable← 正确答案 - B.
index.html设置Cache-Control: no-cache← 正确答案 - C.
/api/user/profile设置Cache-Control: no-store← 正确答案 - D.所有资源统一设置
Cache-Control: public, max-age=86400
答案:ABC。指纹化后的静态资源内容不可变,用一年长缓存+immutable是最优解;HTML作为入口必须每次验证(no-cache),否则发布新版本后用户拿不到新入口;敏感API(含用户身份数据)用no-store防止落盘泄露。D选项把所有资源一刀切设置为public,会让CDN缓存用户敏感数据,是严重的安全隐患。这套组合正是现代前端性能优化的标准范式。