可行但需避免json.stringify()的类型丢失问题,应使用自定义replacer/reviver处理objectid、date等bson类型,并规范key设计、原子更新及空值缓存策略。

直接缓存 MongoDB 查询结果到 Redis 是可行的,但必须避开 JSON.stringify() 和 JSON.parse() 的隐式陷阱,否则会出现类型丢失、Date 变字符串、ObjectId 变空对象等问题。
为什么不能直接用 JSON.stringify(doc) 存入 Redis
MongoDB 返回的文档包含原生类型,比如 ObjectId、Date、Buffer,这些在 JSON 序列化时会被丢弃或转成无意义字符串。例如:
const doc = { _id: new ObjectId("60a1b2c3d4e5f67890123456"), createdAt: new Date() };
console.log(JSON.stringify(doc));
// {"_id":"{}","createdAt":"2021-05-15T10:30:00.000Z"} ← _id 已损坏
-
ObjectId会变成空对象{},导致后续无法按_id查询或校验 -
Date虽然能转成 ISO 字符串,但取回后是 string 类型,不再是Date实例 -
Buffer直接被忽略或转成空数组
推荐做法:用 JSON.stringify() + 自定义 replacer 处理 BSON 类型
保留关键类型信息,同时兼容 Redis 存储和前端消费。核心是重写序列化逻辑:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
function bsonReplacer(key, value) {
if (value instanceof ObjectId) return { $oid: value.toHexString() };
if (value instanceof Date) return { $date: value.toISOString() };
if (value instanceof Buffer) return { $binary: value.toString('base64') };
return value;
}
// 存入
redis.setex(key, 300, JSON.stringify(doc, bsonReplacer));
// 取出后还原(需手动处理)
function bsonReviver(key, value) {
if (value && typeof value === 'object' && value.$oid) {
return new ObjectId(value.$oid);
}
if (value && typeof value === 'object' && value.$date) {
return new Date(value.$date);
}
return value;
}
const cached = JSON.parse(reply, bsonReviver);
- 不依赖第三方库(如
bson包的serialize),避免额外解析开销 - 只对真正需要的字段做类型标记,保持 payload 简洁
- 如果业务中
ObjectId是主键且高频用于查询,建议 key 中直接拼入_id.toHexString(),而非存进 value
缓存 key 设计要带业务上下文与版本标识
单纯用 title 做 key 容易冲突,也难以失效。应组合查询条件 + 数据结构版本:
- 错误示例:
redis.get(title)→ 多个集合、不同字段都可能叫 “JavaScript Guide” - 推荐格式:
book:find-by-title:v2:js-guide或user:profile:by-id:v1:U_10293 - 加入
v2这类版本号,可在数据结构变更时批量失效旧缓存(如用redis.keys("book:find-by-title:v1:*")+del) - 避免在 key 中拼接未清洗的用户输入(如含空格、斜杠),先做
encodeURIComponent或正则替换
更新缓存时必须保证原子性,尤其涉及多字段联动
比如用户修改昵称后,既要更新用户文档,又要刷新其所有相关缓存(个人页、评论列表、搜索结果)。常见漏点:
- 只删缓存没写新值 → 下次查库再缓存,但中间窗口可能读到脏数据
- 先删缓存再更新 DB → 并发请求可能在 DB 写完前就查库并写入旧值到缓存
- 正确顺序:更新 MongoDB → 成功后再
set新缓存(或del+ 后续懒加载) - 对高一致性要求场景(如余额、库存),可考虑用 Redis 的
WATCH+MULTI,但 Node.js 中需配合redis.pipeline()显式控制
最常被忽略的是缓存穿透:当大量请求查一个根本不存在的 title,每次都会击穿到 MongoDB。应在缓存层对空结果也设短 TTL(如 60 秒),并加前缀标识(如 empty:book:js-guide),避免反复查询。










