← 中文资源

简体中文版本

Redis LRU 缓存与 TTL 淘汰

用 Redis 构建旁路缓存层:TTL 过期、LRU 内存策略、命中统计与写入后按键模式显式失效,避免脏数据。

阅读英文原文

示例说明

数据库前挡一层 Redis 缓存是标准提速手段,但失效策略决定正确性。这里采用 cache-aside 模式:读操作先查缓存,未命中回源并把结果带 EX 过期时间写回,NX 选项防止并发回源时互相覆盖;写路径主动按前缀扫描删除相关键,让更新立即生效而不用等 TTL。

技术要点

  • cache-aside 读写路径职责分离清晰
  • SET 的 EX 与 NX 组合防止并发覆盖
  • scanIterator 替代 KEYS 避免阻塞主线程

代码解读

cached 函数封装命中与回源逻辑,命中与未命中分别累加统计键便于观测命中率。invalidate 用 SCAN 迭代匹配前缀的键并逐个删除,COUNT 控制单批扫描量。quit 优雅断开连接,避免悬挂句柄阻止进程退出。

使用建议

服务端建议开启 maxmemory 与 allkeys-lru,让内存上限触发自动淘汰。键前缀设计要支持业务域隔离,失效粒度越细越好。命中率应接入监控,低于阈值说明 TTL 或失效策略需要调整。

代码示例

import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL ?? 'redis://127.0.0.1:6379' });
redis.on('error', (err) => console.error('redis:', err.message));
await redis.connect();

const TTL_SECONDS = 300;

async function cached(key, loader) {
  const hit = await redis.get(key);
  if (hit !== null) {
    await redis.incr('cache:stats:hits');
    return JSON.parse(hit);
  }
  const fresh = await loader();
  await redis.set(key, JSON.stringify(fresh), { EX: TTL_SECONDS, NX: true });
  await redis.incr('cache:stats:misses');
  return fresh;
}

async function invalidate(prefix) {
  const pattern = prefix + ':*';
  for await (const key of redis.scanIterator({ MATCH: pattern, COUNT: 200 })) {
    await redis.del(key);
  }
}

const product = await cached('product:42', () => fetchProduct(42));
await invalidate('product');
await redis.quit();