Next.js为何要缓存MongoDB连接?实现原理及两种方案差异对比
问题1:Next.js中数据库连接缓存普遍存在的原因、原理及必要性
- 运行架构差异是核心原因
首先Express是典型的长驻单进程服务:服务启动时完成一次数据库连接初始化,整个进程运行周期内所有请求都复用这组连接,默认就不会重复创建连接,自然不需要额外做缓存处理。
而Next.js有两个特性导致必须做连接缓存:- 开发环境的模块热重载机制:每次修改代码后Next.js会重新加载涉及的模块,若连接存在模块级变量中,重载后变量会被重置,每次请求都会新建数据库连接,很快就会占满MongoDB的默认连接数上限,抛出连接错误。
- 生产环境默认的无服务器(Serverless)运行模式:无服务器函数的实例是按需启动、短时间内复用的,若每次函数调用都新建连接,不仅会拉高接口响应延迟,也会持续消耗数据库连接资源。
- 缓存实现原理
本质是将已建立的数据库连接实例、连接pending状态的promise存储在不会被轻易销毁的存储空间中,每次调用连接函数时优先复用已有连接,仅当无可用连接时才执行新建逻辑,既降低了连接创建开销,也避免了连接数泄露。 - 是否值得使用
非常值得,是Next.js对接数据库的标准最佳实践:开发环境可以避免连接数打爆的问题,生产环境可以减少冷启动次数、提升接口响应速度,只要实现逻辑正确没有副作用,所有Next.js + 数据库的项目都应该做连接缓存。
问题2:模块级let变量缓存与global全局变量缓存的差异及方案选择
- 二者核心差异
- 作用域与生命周期不同:普通
let声明的变量是模块级作用域,仅在当前模块内可访问,且模块热重载、模块重新加载时变量会被销毁重置,缓存的连接会丢失,你给出的第一个示例就是这种实现,开发环境下基本都会失效。而global是Node.js进程级的全局对象,整个进程运行周期内都会存在,模块热重载不会销毁global上的属性,缓存的连接可以持续保留,你给出的第二个示例就是这种通用实现。 - 适配场景不同:模块级let变量的缓存仅在生产环境非Serverless模式下(比如Next.js用standalone模式启动长驻服务)可以正常工作,开发环境和Serverless模式下都会失效;而global缓存可以适配所有Next.js的运行场景。
- 作用域与生命周期不同:普通
- 更优方案选择
优先选择基于global的缓存方案,也是MongoDB、Mongoose官方推荐的Next.js适配方案。实现时注意给global上的缓存属性起一个辨识度高的唯一名称,避免和其他依赖的全局属性冲突即可,示例如下:
如果你确定你的项目只会用standalone长驻模式部署、不需要考虑开发环境热重载的问题,也可以用模块级let变量的方案,但通用性远不如global缓存方案。// 用唯一属性名避免全局污染 let cached = global._myAppMongoCache || (global._myAppMongoCache = { conn: null, promise: null })
内容的提问来源于stack exchange,提问作者scripter2021
相关产品推荐
相关产品推荐

