You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ioredis在Redis中存入字典并实现LRU图片缓存

实现基于ioredis的用户级LRU图片缓存

问题描述

我正在用ioredis做API限流,现在需要给用户最近访问的图片实现大小为x的LRU缓存(比如每个用户最多缓存5张)。想知道能不能以用户ID为键,把数组字典存在Redis里,让Redis自动剔除旧图片并维持缓存大小?预期格式如下:

userId -> [{imgId:1, data:{}} , {imgId:2, data:{}} ...]

以下是我的getImagewithId控制器代码:

const getImagewithId = async (req, res) => {
  const id = parseInt(req.params.id);
  const key = `${req.user.id}/images`;
  // todo : implement cache using ioredis 
  const image = await prisma.image.findUnique({
    where: {
      id: id,
    },
  });
  if (!image) return res.status(404).json({ error: 'Image not found' });
  if (image.userId != req.user.id)
    return res.status(403).json({ error: 'Forbidden' });

  return res.status(200).json({ image: image });
};

解决方案

直接存数组的方式不可行——Redis没法自动帮你维护数组的LRU顺序和大小,所有更新、排序、截断逻辑都得自己写,效率低还容易出问题。推荐用**Redis有序集合(Sorted Set)+哈希表(Hash)**的组合,精准实现每个用户独立的LRU缓存控制。

1. 缓存结构设计

  • 哈希表(Hash):键为user:{userId}:images:data,用图片ID作为字段名,存储图片的JSON序列化数据,负责快速读取图片内容。
  • 有序集合(Sorted Set):键为user:{userId}:images:lru,用图片ID作为成员,以访问时间戳作为score,负责维护图片的LRU访问顺序。

2. 修改后的控制器代码

假设你已经初始化好了ioredis实例redis,代码修改如下:

const getImagewithId = async (req, res) => {
  const imgId = parseInt(req.params.id);
  const userId = req.user.id;
  const dataKey = `user:${userId}:images:data`;
  const lruKey = `user:${userId}:images:lru`;
  const MAX_CACHE_SIZE = 5; // 每个用户最多缓存5张图片

  // 优先从缓存读取
  let cachedImage = await redis.hget(dataKey, imgId);
  if (cachedImage) {
    // 缓存命中,更新该图片的访问时间,刷新LRU顺序
    await redis.zadd(lruKey, Date.now(), imgId);
    return res.status(200).json({ image: JSON.parse(cachedImage) });
  }

  // 缓存未命中,查询数据库
  const image = await prisma.image.findUnique({
    where: { id: imgId },
  });
  if (!image) return res.status(404).json({ error: 'Image not found' });
  if (image.userId !== userId)
    return res.status(403).json({ error: 'Forbidden' });

  // 将图片写入缓存
  await redis.hset(dataKey, imgId, JSON.stringify(image));
  // 记录当前访问时间到LRU集合
  await redis.zadd(lruKey, Date.now(), imgId);

  // 检查缓存大小,超过限制则删除最久未访问的图片
  const cacheSize = await redis.zcard(lruKey);
  if (cacheSize > MAX_CACHE_SIZE) {
    // 取出最早访问的图片ID(score最小的元素)
    const oldestImgId = await redis.zrange(lruKey, 0, 0);
    if (oldestImgId.length) {
      // 从LRU集合和哈希表中同时删除
      await redis.zrem(lruKey, oldestImgId[0]);
      await redis.hdel(dataKey, oldestImgId[0]);
    }
  }

  return res.status(200).json({ image: image });
};

3. 核心逻辑说明

  • LRU顺序维护:每次访问缓存中的图片时,用zadd更新它的score为当前时间戳,确保它处于有序集合的末尾(代表最新访问)。
  • 缓存大小控制:每次写入新图片后,用zcard获取当前缓存的图片数量,超过阈值时,用zrange取出最早访问的图片ID,同时从哈希表和有序集合中删除,保证每个用户的缓存不超过设定大小。
  • 性能保障:Sorted Set的zadd/zcard/zrange操作都是O(logN)时间复杂度,Hash的hget/hset是O(1),整体性能高效,适合高并发场景。

替代方案:全局LRU淘汰

如果不需要严格控制每个用户的缓存数量,也可以给每个图片设置独立的缓存键(比如user:{userId}:image:{imgId}),然后在Redis配置中开启全局LRU淘汰策略(maxmemory-policy allkeys-lru)并设置内存上限。但这种方式没法精准控制单个用户的缓存大小,适合全局缓存场景。

内容的提问来源于stack exchange,提问作者Hmm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:37:07