如何避免实时事件流触发重复的Mongoose find查询
优化重复Mongoose查询的方案
这个场景我之前在处理高并发事件流的时候遇到过,核心就是通过缓存机制避免相同hash的重复数据库查询,下面给你几个分场景的实用方案:
1. 单实例服务:本地内存缓存
如果你的服务是单实例部署,用本地Map做缓存是最简单高效的方式,不需要额外依赖。我们可以给缓存加过期时间,防止内存占用过高:
// 初始化缓存容器,用Map存储hash对应的查询结果和时间戳 const hashCache = new Map(); // 缓存过期时间,这里设为5分钟(可根据业务调整) const CACHE_TTL = 300000; async function getHashData(hash) { // 先检查缓存 const cachedEntry = hashCache.get(hash); if (cachedEntry) { // 判断缓存是否过期 if (Date.now() - cachedEntry.timestamp < CACHE_TTL) { return cachedEntry.data; } else { // 过期则移除缓存,后续走数据库查询 hashCache.delete(hash); } } // 缓存未命中,执行数据库查询 const data = await Model.find({ hash }); // 将结果存入缓存 hashCache.set(hash, { data, timestamp: Date.now() }); return data; }
注意点:
- 适合单实例场景,多实例下每个实例的缓存不共享,还是会有重复查询
- 可以根据业务调整TTL:如果hash对应数据更新频繁,就缩短过期时间;如果数据很少变动,可以设长一点
- 对于查询为空的结果(比如hash不存在),也建议缓存,但TTL设短一些,避免新插入的数据无法被查询到
2. 多实例集群:分布式缓存(Redis)
如果你的服务是多实例部署,本地缓存就不适用了,这时候需要用Redis这类分布式共享缓存,确保所有实例共用同一个缓存源:
首先安装Redis客户端(比如ioredis),然后实现缓存逻辑:
const Redis = require('ioredis'); // 初始化Redis连接(根据你的Redis配置调整) const redis = new Redis({ host: 'localhost', port: 6379 }); // 缓存过期时间,设为5分钟 const CACHE_TTL = 300; async function getHashData(hash) { const cacheKey = `hash:${hash}`; // 先查询Redis缓存 const cachedStr = await redis.get(cacheKey); if (cachedStr) { // 解析缓存的JSON字符串 return JSON.parse(cachedStr); } // 缓存未命中,查询数据库 const data = await Model.find({ hash }); // 将结果存入Redis并设置过期时间 await redis.set(cacheKey, JSON.stringify(data), 'EX', CACHE_TTL); return data; }
额外优化:解决缓存一致性问题
如果数据库中hash对应的数据发生更新,缓存里的旧数据就会和数据库不一致。这时候需要在更新数据时主动删除对应缓存:
async function updateHashData(hash, newData) { // 更新数据库 await Model.updateOne({ hash }, newData); // 删除对应缓存键 const cacheKey = `hash:${hash}`; await redis.del(cacheKey); }
3. 高并发场景:避免缓存击穿
在高并发下,如果某个hash的缓存刚好过期,大量请求会同时打到数据库,造成数据库压力。这时候可以加个“分布式锁”,确保同一时间只有一个请求去查询数据库:
async function getHashData(hash) { const cacheKey = `hash:${hash}`; const lockKey = `lock:${hash}`; const lockTTL = 10; // 锁的过期时间,防止死锁 // 先查缓存 let cachedStr = await redis.get(cacheKey); if (cachedStr) { return JSON.parse(cachedStr); } // 尝试获取分布式锁 const lockAcquired = await redis.set(lockKey, '1', 'NX', 'EX', lockTTL); if (!lockAcquired) { // 没拿到锁,等待100ms后重试 await new Promise(resolve => setTimeout(resolve, 100)); return getHashData(hash); } try { // 拿到锁后查询数据库 const data = await Model.find({ hash }); // 更新缓存 await redis.set(cacheKey, JSON.stringify(data), 'EX', CACHE_TTL); return data; } finally { // 无论结果如何,释放锁 await redis.del(lockKey); } }
内容的提问来源于stack exchange,提问作者luiquao
相关产品推荐
相关产品推荐

