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

如何避免实时事件流触发重复的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 12:07:46