JeongSeop Byeon

免责声明:作为非英语母语者,我使用了 AI 来帮助组织和润色本文。

几天前,我分享了 在 Node.js 中实现零分配 S3-FIFO 缓存——一个探索性的 v0.1 版本,展示了如何使用预分配的 TypedArray 实现 S3-FIFO(简单且可扩展的扫描抵抗 FIFO) 缓存算法,以消除 Node.js 中的垃圾回收(GC)压力。

今天,经过广泛的压力测试、架构强化以及 100% 的单元测试覆盖率,我很高兴地宣布 s3fifo v1.0.0 正式发布!🚀

快速回顾:为什么选择 S3-FIFO + 零分配?

传统的 LRU(最近最少使用) 缓存存在两个主要的实际生产缺陷:

  1. 单次命中污染缓存:扫描或顺序查询会用仅访问一次的项淹没缓存,逐出您的高频工作集。
  2. 垃圾回收开销:基于对象的朴素缓存实现会在每次 set() 时动态分配节点,在高吞吐量工作负载下触发频繁的 GC 暂停。 S3-FIFO 通过将缓存条目组织成三个轻量级队列来解决缓存污染问题:
  3. 小队列 (S):快速过滤掉单次命中(通常占总容量的 ~10%)。
  4. 主队列 (M):保存多次访问的高频项目(约占 90% 的容量)。
  5. 幽灵队列 (G):记住从小队列中逐出的键,以便在重新请求时立即提升到主队列。 s3fifo 通过使用连续的 TypedArray(Uint32ArrayFloat64Array)和可重用索引池来支持这些队列,实现了 get/set 周期中的零动态对象分配。 --- ## v1.0.0 的新功能?(生产就绪) 虽然 v0.1 专注于核心算法速度,但 v1.0.0 提供了任务关键型 Node.js 微服务所需的所有开发者人体工程学、生命周期工具和安全保证。 ### 1. 💾 冷启动持久性 (dump & load) 防止容器重启或部署期间的 数据库惊群效应 / 缓存雪崩s3fifo 1.0 支持序列化活动驻留条目及其原始创建时间戳和剩余 TTL:
import fs from "node:fs";
import { S3Fifo } from "s3fifo";
const cache = new S3Fifo<string>({ max: 10000 });
// 1. 导出活动缓存项(可选择过滤掉临时键)
const dumpData = cache.dump((key, value) => !key.startsWith("temp:"));
fs.writeFileSync("cache-snapshot.json", JSON.stringify(dumpData));
// 2. 在服务器启动/预热时:立即恢复缓存状态
const snapshot = JSON.parse(fs.readFileSync("cache-snapshot.json", "utf-8"));
cache.load(snapshot);

进入全屏模式 退出全屏模式

2. ♻️ 安全资源生命周期 (dispose 回调)

当缓存项被逐出、覆盖或清除时,您通常需要释放外部资源(例如,关闭文件描述符、销毁数据库句柄或跟踪逐出指标)。

const cache = new S3Fifo<Buffer>({
  max: 500,
  dispose: (key, buffer, reason) => {
    console.log(`Key ${key} removed due to: ${reason}`); // 'evict' | 'set' | 'delete' | 'clear'
    // 安全地释放本地内存或资源
  },
});

进入全屏模式 退出全屏模式

🛡️ 重入保护:在缓存操作完成之前,dispose 回调被安全地延迟,从而防止如果回调递归调用 cache.set()cache.delete() 导致的内部状态损坏。

3. 🔍 无副作用检查 (peek)

需要检查缓存值以进行日志记录、健康检查或监控,而不增加频率计数器或更改逐出状态?peek() 允许纯净、无副作用的读取:

// 不修改 S3-FIFO 频率位字段或 TTL 时间戳
const val = cache.peek("user:1001");

进入全屏模式 退出全屏模式

4. 🔄 标准 ES6 迭代器和 Map API

s3fifo 1.0 与 JavaScript 的原生迭代协议无缝集成:

// 标准 JS Map 风格迭代器
for (const [key, value] of cache) {
  console.log(key, value);
}
const keys = Array.from(cache.keys());
const values = Array.from(cache.values());
const entries = Array.from(cache.entries());
cache.forEach((val, key) => {
  /* ... */
});

进入全屏模式 退出全屏模式

5. 🧹 无泄漏的生命周期拆除 (close)

在无服务器、热重载 (HMR) 或测试环境中,后台计时器可能会阻止进程终止。close() 方法会清除活动的后台 TTL 计时器,释放内存缓冲区,并防止内存泄漏:

// 关闭时进行清理
cache.close();
console.log(cache.isClosed); // true

进入全屏模式 退出全屏模式


📊 基准测试:命中率和吞吐量 vs lru-cache

在 Zipfian 分布(偏度 0.99,工作集为 100,000 个键)上测试,比较了 s3fifo v1.0 和 Node 流行的 lru-cache

平均命中率 (%)

缓存大小(占池的百分比) lru-cache s3fifo
1% 48.90% 58.30%
5% 65.00% 71.10%
10% 72.30% 76.40%
25% 82.10% 82.70%
50% 89.00% 86.30%

平均吞吐量 (ops/sec)

缓存大小(占池的百分比) lru-cache s3fifo
1% 10.8M 15.5M
5% 10.8M 14.4M
10% 10.2M 14.3M
25% 10.3M 13.5M
50% 10.3M 14.7M

关键要点:

  • 命中率最高可提高 +9.4%,当缓存容量相对于数据集大小较小时(1%–10% 范围)——非常适合前端数据库缓存或高并发微服务。

- ~40% 更高的吞吐量(~14.5M–15.5M ops/sec vs ~10.5M ops/sec),因为在 get/set 操作期间实现了零对象分配。

开始使用

通过 npm 安装:

npm install s3fifo

进入全屏模式 退出全屏模式

基本用法:

import { S3Fifo } from "s3fifo";
const cache = new S3Fifo<string>({
  max: 1000,
  ttl: 60000, // 60s 全局 TTL
});
cache.set("session:abc", "user_data");
console.log(cache.get("session:abc")); // 'user_data'
console.log(cache.size); // 1

进入全屏模式 退出全屏模式


链接和反馈

  • 📦 NPMs3fifo
  • 🐙 GitHub 仓库BJS-kr/s3fifo 如果您正在寻找 Node.js 中具有扫描抵抗和最小 GC 占用的内存缓存替代方案,请尝试 s3fifo!非常感谢 Bug 报告、反馈和 GitHub 星标!🙏