如何用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
相关产品推荐
相关产品推荐

