web前端缓存命中监控方案

web前端缓存命中监控方案

web前端缓存大致分为两种:

HTTP 网络缓存

浏览器默认的强缓存(Cache-Control/Expires)、协商缓存(304 Not Modified),针对所有静态资源(JS/CSS/ 图片 / 字体等)、接口请求

前端存储缓存

前端主动存储的缓存,比如 localStorage /sessionStorage/indexedDB / 内存缓存 (Map / 全局变量)

一、监控HTTP网络缓存命中

  • 浏览器加载所有网络资源(js/css/img/ 接口)时,都会触发 HTTP 缓存规则
获取PerformanceResourceTiming 数据的方式

1、performance.getEntriesByType('resource')(主动获取)

作用:主动获取当前页面已经加载完成的「所有资源」的 PerformanceResourceTiming 对象数组。

2、performance.addEventListener('resourceentry') (实时监听)

作用:实时监听页面中「每一个资源加载完成」的事件,每加载完一个资源,就会触发一次该事件,回调中能拿到当前资源的 PerformanceResourceTiming 对象。

javascript 复制代码
// 通用、精准、无歧义的缓存命中判断公式
function isHttpCacheHit(entry) {
  // 命中缓存:实际下载字节为0,但是资源原始大小>0
  return entry.transferSize === 0 && entry.decodedBodySize > 0;
}

transferSize : 从网络实际下载的字节大小
decodedBodySize: 资源解析后的原始实际大小

javascript 复制代码
// 监听页面加载完成,确保所有资源都已加载完毕
window.addEventListener('load', () => {
  // 浏览器兼容处理(极低版本浏览器兜底)
  if (!window.performance || !window.performance.getEntriesByType) return;

  // 1. 获取所有网络请求的性能数据(js/css/img/font/接口/ajax/fetch 全包含)
  const allResources = performance.getEntriesByType('resource');
  
  // 2. 初始化统计指标
  const cacheStats = {
    total: allResources.length, // 总资源数
    hit: 0, // 缓存命中数
    miss: 0, // 缓存未命中数
    hitUrls: [], // 命中缓存的资源列表
    missUrls: [], // 未命中缓存的资源列表
    hitRate: 0 // 缓存命中率
  };

  // 3. 遍历所有资源,判断缓存命中状态
  allResources.forEach(entry => {
    const isHit = entry.transferSize === 0 && entry.decodedBodySize > 0;
    if (isHit) {
      cacheStats.hit++;
      cacheStats.hitUrls.push(entry.name); // entry.name 是资源完整URL
    } else {
      cacheStats.miss++;
      cacheStats.missUrls.push(entry.name);
    }
  });

  // 4. 计算缓存命中率(保留2位小数)
  cacheStats.hitRate = cacheStats.total > 0 
    ? (cacheStats.hit / cacheStats.total * 100).toFixed(2) + '%' 
    : '0%';

  // 5. 核心:打印统计结果(可替换为:上报到埋点系统)
  console.log('【HTTP缓存命中统计】', cacheStats);

  // 6. 可选:上报单条资源的缓存命中详情(精细化监控)
  // reportToMonitor(cacheStats); 
});
解释:

命中强缓存(200 from disk cache / 200 from memory cache):浏览器直接从本地磁盘 / 内存读取资源,不会发起任何网络请求,所以 transferSize = 0
命中协商缓存(304 Not Modified):浏览器会发起请求到服务端校验资源有效性,服务端返回 304 无响应体,实际下载的字节也为 0,所以 transferSize = 0
未命中缓存(200 OK):浏览器需要从服务端完整下载资源,transferSize > 0 且 约等于 decodedBodySize

跨域资源的数据获取限制
  • 跨域资源的默认值都是 0

当页面加载的资源是跨域资源时(比如:CDN 上的图片 /js/css、第三方接口、跨域字体等),浏览器出于安全策略限制,会让 PerformanceResourceTiming 对象中的:

所有「体积属性」(decodedBodySize/encodedBodySize/transferSize) = 0

所有「时序属性」(dnsTime/tcpTime/ttfb 等) = 0

  • 解决方案(两个条件缺一不可)

1、资源服务器端配置 CORS 响应头:返回 Timing-Allow-Origin: * 或 Timing-Allow-Origin: 你的页面域名(比如 https://www.xxx.com),表示允许该域名获取资源的时序性能数据
2、前端页面发起合规的跨域请求:如果是图片 / 脚本 / 样式等标签加载资源,需要给标签添加 crossorigin="anonymous" 属性(比如 <img src="跨域图片地址" crossorigin="anonymous">)

二、监控存储缓存命中(localStorage / 内存缓存等)

  • 这类缓存是前端主动开发的缓存逻辑(浏览器不会自动统计),比如:用 localStorage 缓存接口数据、用 Map 做内存缓存、用 indexedDB 缓存大体积数据,这类缓存的命中监控逻辑:自己在缓存读写逻辑中加统计即可,因为缓存的读写完全由前端代码控制。
javascript 复制代码
// 封装带缓存命中统计的localStorage工具类
const cacheStorage = {
  stats: { hit: 0, miss: 0, hitRate: 0 }, // 缓存统计
  // 读取缓存
  get(key) {
    const value = localStorage.getItem(key);
    if (value) {
      this.stats.hit++; // 命中+1
      console.log(`localStorage缓存命中:${key}`);
    } else {
      this.stats.miss++; // 未命中+1
      console.log(`localStorage缓存未命中:${key}`);
    }
    // 实时计算命中率
    this.stats.hitRate = (this.stats.hit / (this.stats.hit + this.stats.miss) * 100).toFixed(2) + '%';
    return value ? JSON.parse(value) : null;
  },
  // 写入缓存
  set(key, value, expire = 0) {
    localStorage.setItem(key, JSON.stringify({ value, expire: expire ? Date.now() + expire : 0 }));
  },
  // 获取统计数据
  getStats() {
    return this.stats;
  }
};

// 使用示例
cacheStorage.set('user_info', { name: '张三' }, 3600 * 1000); // 缓存1小时
cacheStorage.get('user_info'); // 命中
cacheStorage.get('goods_list'); // 未命中
console.log('【localStorage缓存统计】', cacheStorage.getStats());
相关推荐
Juicedata2 小时前
仅两台缓存节点,如何支撑 1.45TB/s 大吞吐业务
人工智能·分布式·缓存
曹天骄2 小时前
我是如何用 Cloudflare Worker 实现 HTML 灰度发布与两级缓存的
java·缓存·html
恋爱绝缘体12 小时前
CSS3 多媒体查询实例【1】
前端·css·css3
短剑重铸之日2 小时前
《7天学会Redis》特别篇:Redis十大经典面试题2
数据库·redis·后端·缓存·架构
小二·3 小时前
Python Web 开发进阶实战:无障碍深度集成 —— 构建真正包容的 Flask + Vue 应用
前端·python·flask
niucloud-admin11 小时前
web 端前端
前端
Mr__Miss11 小时前
Redis的持久化
数据库·redis·缓存
胖者是谁14 小时前
EasyPlayerPro的使用方法
前端·javascript·css
EndingCoder14 小时前
索引类型和 keyof 操作符
linux·运维·前端·javascript·ubuntu·typescript