缓存,是支撑抖音、头条等超大流量平台“毫秒级响应”的关键组件。字节跳动的缓存体系通过多级缓存架构+一致性控制+高可用设计实现了从容应对亿级访问压力。本篇文章从架构设计讲起,结合 Node.js 演示一个多级缓存系统原型,揭示其背后的设计思路。
🧠 一、为什么要构建多级缓存?
- 性能需求:数据库查询慢,访问延迟高成本控制:热点数据频繁访问,数据库成本高可用性保障:即使后端宕机,也能提供服务
字节典型多级缓存结构:
[Browser Cache] → [Local Cache] → [Redis/Memcached] → [DB/存储系统]
🏗️ 二、字节跳动的缓存体系设计亮点
组件 | 功能作用 |
---|---|
浏览器缓存 | 静态资源走 CDN,页面级缓存(强缓存、协商缓存) |
本地缓存(Local) | 单机内存缓存热点数据,极致低延迟 |
分布式缓存 | Redis 作为统一热点数据共享层,支持集群+哨兵 |
异地缓存 | 火山引擎多地部署,减少跨区访问延迟 |
一致性策略 | 数据更新触发清除 + 延迟双删 + TTL 策略 |
旁路缓存 | 请求未命中时异步更新缓存 |
⚙️ 三、Node.js 演示:多级缓存系统原型
我们模拟一个系统,先查本地缓存,再查 Redis,最后查数据库(模拟)。
1. 安装依赖
npm install redis
2. 代码实现
const redis = require("redis");const client = redis.createClient();client.connect();const localCache = new Map();async function getUserInfo(userId) { // 1. 本地缓存查找 if (localCache.has(userId)) { console.log("命中本地缓存"); return localCache.get(userId); } // 2. Redis 查找 let redisData = await client.get(`user:${userId}`); if (redisData) { console.log("命中 Redis 缓存"); localCache.set(userId, redisData); return redisData; } // 3. 模拟数据库查找 console.log("命中数据库"); const dbData = `user-${userId}-from-db`; // 缓存数据 await client.set(`user:${userId}`, dbData, { EX: 60 }); // 设置 60s 过期 localCache.set(userId, dbData); return dbData;}
3. 使用示例:
getUserInfo(123).then(console.log); // 第一次命中 DBsetTimeout(() => getUserInfo(123).then(console.log), 2000); // 第二次命中本地缓存
🔄 四、工程中进阶策略
- 缓存穿透:空数据写入 Redis,防止频繁打到 DB缓存击穿:热门 key 失效瞬间加锁防止并发打爆 DB缓存雪崩:热点 key 设置随机 TTL 避免集中失效延迟双删:更新 DB 时延迟删除 Redis 缓存,确保一致性异步预热:提前加载热门数据,减少冷启动抖动
✍️ 五、总结与思考
- 缓存不只是 Redis,而是一个完整的分层设计本地缓存是成本最低、速度最快的一层字节跳动将缓存当作系统级能力在构建,而不是简单的优化手段
🎁 拓展推荐
- Redis 官方文档《缓存:从入门到亿级流量》by 美团技术团队字节跳动技术博客:《缓存系统在字节跳动的演进》