第一次遇到这种情况,蘑菇影视在线观看的缓存管理问题我终于定位到原因了

最近在维护蘑菇影视在线观看平台的时候,遇到一个很棘手的缓存问题:用户播放视频时会出现卡顿、片段加载失败、或播放到一半突然回退到很早的画面。问题偶发,机房和CDN日志都没有明显错误,直到把视角拉到浏览器端和服务端缓存策略上,才把原因一步步定位清楚。把排查过程、根因和解决办法整理如下,供遇到类似问题的同学参考。
一、症状回顾(用户反馈与现场表现)
- 播放器报错:“加载失败”“解码异常”或者直接停在缓冲;
- 部分用户只能播放前几秒或前几分钟,随后重试才能继续;
- 有时候展示的是旧内容(旧章节、旧海报)而非最新上线的资源;
- 问题在不同浏览器和不同网络下表现不一致,移动端更容易触发。
二、我如何开始排查
- 在复现环境用 Chrome DevTools 的 Network 面板观察:关注请求是否返回 206 Partial Content,还是 200 OK;检查 Range 请求与响应头;
- 检查 Service Worker:看是否有 fetch 事件对所有请求进行了缓存拦截;
- 查看浏览器 Application → Cache Storage、IndexedDB、localStorage 内容,确认是否有视频片段或 manifest 被错误缓存;
- 在服务器端查看 nginx/access.log,关注请求的请求行和响应码,尤其是带 Range 的请求与响应;
- 检查 CDN 配置与响应头(Cache-Control、ETag、Vary、Accept-Ranges)以及是否对某些路径做了不当缓存策略。
三、关键发现(定位到真正的原因) 最终定位到两个互相叠加的问题: 1) 前端 Service Worker 把所有 fetch 请求统一缓存(包括 .m3u8 和 ts/fragment、mp4 分片),而 fetch handler 没有正确处理带 Range 的请求或没有区分媒体资源与静态资源。结果浏览器在需要 206 Partial Content 时,Service Worker 返回了缓存的 200 响应或完整文件,导致播放器无法正确请求字节范围,从而出现卡顿或播放失败。 2) 后端代理(nginx -> 源站 或 CDN)在代理 Range 请求时没有正确配置 cache key,导致不同 Range 的请求被当成同一资源缓存并返回固定内容(比如完整文件或第一个 Range 的片段),从而对播放器的按需加载行为造成破坏。
简单说,就是“谁都在缓存,但都没理解视频分片和 Range 请求的语义”,前端的全拦截 + 后端的错误 proxy_cache 配置合起来造成了错乱。
四、详细修复步骤(代码示例与配置建议) 下面分前端与后端给出可直接落地的修改方案。
前端(Service Worker)
- 不要盲目缓存所有请求。只缓存真正静态、可长期缓存的资源(CSS/JS/图片等),对视频 manifest(.m3u8)和分片(.ts/.mp4/.m4s)要绕过缓存或使用不同策略。
- 在 fetch handler 中检测 request.destination、Content-Type 或 URL 后缀,选择性处理。
示例逻辑(伪代码): if (event.request.destination === 'video' || url.endsWith('.m3u8') || url.match(/\/fragment\//)) { // 直接网络优先或无缓存策略 return fetch(event.request); } else { // 对静态资源做缓存策略 … }
- 对于需要缓存的动态请求,避免缓存带有 Range 头的请求:如果 request.headers.has('range'),直接走网络。
后端(nginx / proxy)
- 确保对 Range 请求的支持:响应中包含 Accept-Ranges: bytes,响应状态为 206 对于分段请求。
- proxycachekey 中包含 $httprange,以免不同 Range 请求冲突: proxycachekey "$scheme$requestmethod$host$requesturi$httprange";
- 允许缓存 206 响应: proxycachevalid 200 206 1h;
- 对直播/实时更新的 manifest(m3u8)和小文件,设置合理的 Cache-Control:例如实时流 manifest 用 Cache-Control: no-cache 或 short max-age;对版本化的 VOD 资源可以设置长缓存。
- 如果不希望 CDN/代理缓存某类请求,可通过设置 Cache-Control: private/no-store 或在 nginx 中用 add_header Cache-Control "no-store";
- 对于需要绕过缓存的请求(如携带 Authorization 或带 Range 时),用 proxycachebypass: proxycachebypass $http_range;
- 开启 proxycachelock 来避免并发回源痛点: proxycachelock on;
nginx 片段示例: location /media/ { proxypass http://upstream; proxysetheader Range $httprange; proxysetheader If-Range $httpifrange; proxycache mycache; proxycachekey "$scheme$requestmethod$host$requesturi$httprange"; proxycachevalid 200 206 1h; proxycachebypass $httprange; add_header Accept-Ranges bytes; }
验证命令(测试 Range 返回):
- curl -I -r 0-1 https://example.com/path/to/file.mp4 (查看返回是否为 206)
- curl -v -H "Range: bytes=0-1023" https://… (检查响应头和状态)
五、回归测试与监控点
- 在修复后,用不同网络条件与不同设备反复测试播放长短视频,确认播放器收到 206、Content-Range 正常,并且没有被 Service Worker 拦截并返回错误响应。
- 增加监控指标:
- 统计带 Range 请求返回 206 的比例(若显著下降警报);
- 播放器错误率(加载失败、缓冲中断);
- CDN 缓存命中率与 200/206 比例;
- 在发布前逐步灰度部署 Service Worker 的改动,避免一次性把所有客户端都升级到会引发问题的策略。
六、经验与建议(落地可复用的规则)
- 视频相关的请求(manifest、分片、带 Range 的请求)默认不要被 Service Worker 缓存,除非你非常明确如何处理 Range 和分片语义;
- 后端代理缓存要把 Range 纳入 cache_key,或直接 bypass 缓存 Range 请求;
- 对直播和点播采取不同的缓存策略:直播 manifest 保持短缓存或不缓存,点播资源使用版本化文件名结合长缓存;
- 在变更缓存策略前,先在实验环境验证 Range 行为、206 响应和播放器兼容性;
- 把浏览器开发者工具、curl、nginx 日志和 CDN 日志结合起来排查,前端与后端的日志往往互补。
七、结语 这次问题是前端与后端“都对缓存做了优化,但语义不同步”导致的典型案例。把 Service Worker 的缓存边界理清楚、让后端代理正确处理 Range,再配合合理的 Cache-Control 策略,问题就能彻底解决。把这些修复部署并观察一段时间后,用户的播放体验恢复稳定,相关错误率大幅下降。
如果你也在做在线视频服务,愿这篇排查路线和实践经验能少走一些弯路。需要我把上面那些 nginx 配置、Service Worker 代码片段按你现在的项目结构具体化,贴出来供直接替换配置吗?