ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Mongoose 多租户连接如何选:useDb 切换数据库还是每租户独立连接池

2026/9/13 11:54:01 拓冰建站 浏览量
Mongoose 多租户连接如何选:useDb 切换数据库还是每租户独立连接池 Mongoose 多租户连接如何选useDb 切换数据库还是每租户独立连接池【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose当你用单个 Mongoose 应用服务多个客户端每个客户端在同一个 MongoDB 集群中有自己独立的数据库时就需要做多租户连接管理。Mongoose 官方在 连接文档 的 Multi Tenant Connections 一节中给出了两种推荐模式一是维护一个连接池、用Connection.prototype.useDb()在各租户数据库间切换二是为每个租户维护独立的连接池把连接存进一个映射表。两种模式的操作路径、适用负载和维护成本都不同下面按先判断选哪种、再按对应模式落地的顺序说明。先判断两种模式的适用条件连接文档 给出了明确的选型边界模式 1单连接池 useDb()切换适合租户数量不多或单个租户负载较轻的场景文档给出的量化参考是大约每秒不到 1 个请求且每个请求的数据库处理时间小于 10ms。它只有一个连接池实现简单、生产上更好管理但高负载下某个租户的慢操作会拖慢其他租户即文档所称的 slow trains。模式 2每租户独立连接池适合 10k 以上租户、且每秒 1 个请求以上的场景。每个租户的连接池相互隔离单个租户的慢操作对其他租户影响很小但实现和管理更复杂而且 MongoDB 对打开的连接数有上限MongoDB Atlas 另有自己的连接数限制需要确保所有连接池的 socket 总数不超过 MongoDB 的限额。两种模式共同的背景知识也在同一篇文档中每个connection无论通过mongoose.connect还是mongoose.createConnection创建背后都有一个连接池默认最大maxPoolSize为 100可以通过连接选项或连接串参数如?maxPoolSize10调整。模式 1单连接池 useDb() 切换租户数据库这个模式先建立一个基础连接然后在请求到来时通过useDb()拿到指向目标租户数据库的连接对象再在该连接上注册模型、执行查询。useDb()的第二个参数中useCache: true会按数据库名缓存连接对象同一名字多次调用返回同一个连接实例——在多租户场景下应使用这个选项。下面这段代码来自 连接文档 的示例其中tenant_${req.params.tenantId}是文档示例中的数据库命名方式替换为你的租户 ID 来源即可const express require(express); const mongoose require(mongoose); mongoose.connect(mongodb://127.0.0.1:27017/main); mongoose.set(debug, true); mongoose.model(User, mongoose.Schema({ name: String })); const app express(); app.get(/users/:tenantId, function(req, res) { const db mongoose.connection.useDb(tenant_${req.params.tenantId}, { // useCache tells Mongoose to cache connections by database name, so // mongoose.connection.useDb(foo, { useCache: true }) returns the // same reference each time. useCache: true }); // Need to register models every time a new connection is created if (!db.models[User]) { db.model(User, mongoose.Schema({ name: String })); } console.log(Find users from, db.name); db.model(User).find(). then(users res.json({ users })). catch(err res.status(500).json({ message: err.message })); }); app.listen(3000);几个必须注意的点模型要注册在useDb()返回的连接上。模型始终绑定到单个连接文档示例里先检查db.models[User]再注册就是避免对同一连接重复注册模型。useDb()连接的close()会连带关闭基础连接。这是源码 lib/drivers/node-mongodb-native/connection.js 中useDb()的文档注释明确写出的行为。如果只需要移除某个租户的数据库连接而不关闭基础连接使用Connection.prototype.removeDb(name)它会关闭该useDb()连接并将其从基础连接的relatedDbs/otherDbs中移除找不到时会抛出No connections to database ${name} found错误。Mongoose 9 中useDb()的第二个参数只支持{ useCache }。noListener选项已在 9.x 移除8.x 中conn.useDb(myDb, { noListener: true })可用9.x 中同样的调用会抛出TypeError: noListener is not a supported option而conn.useDb(myDb, { useCache: true })仍然可用。这一点在 migrating_to_9 中有说明如果你从 8.x 迁移并复用旧代码需要删掉noListener。示例中的mongoose.set(debug, true)用于打印调试日志属于文档示例的排障手段生产环境按需关闭。模式 2每租户独立连接池这个模式用一个普通对象文档称为 map 或 POJO缓存tenantId到连接的映射新租户首次请求时通过mongoose.createConnection()建立到该租户数据库的连接、注册模型并等待asPromise()连接完成之后请求直接复用缓存的连接。const express require(express); const mongoose require(mongoose); const tenantIdToConnection {}; const app express(); app.get(/users/:tenantId, function(req, res) { let initialConnection Promise.resolve(); const { tenantId } req.params; if (!tenantIdToConnection[tenantId]) { tenantIdToConnection[tenantId] mongoose.createConnection(mongodb://127.0.0.1:27017/tenant_${tenantId}); tenantIdToConnection[tenantId].model(User, mongoose.Schema({ name: String })); initialConnection tenantIdToConnection[tenantId].asPromise(); } const db tenantIdToConnection[tenantId]; initialConnection. then(() db.model(User).find()). then(users res.json({ users })). catch(err res.status(500).json({ message: err.message })); }); app.listen(3000);这里的要点createConnection()返回的是连接实例而不是 Promise文档明确要求用asPromise()来等待连接成功或失败示例中首次建连的请求会先 await 这一步再执行查询。每个租户连接独立调maxPoolSize即可控制该租户的并发上限但整体上要自行核算所有池的 socket 总数是否超过 MongoDB 的连接数限制这是文档对模式 2 的主要运维提示。多连接下的模型组织方式无论选哪种模式只要涉及多个连接连接文档 都建议导出 schema 而不是模型导出模型export model pattern会把它绑定到默认连接只能用于单连接场景。推荐写法是各 schema 文件只module.exportsschema然后按以下两种组织方式之一集中创建模型// 工厂函数模式每次调用都新建连接并注册全部模型 const mongoose require(mongoose); module.exports function connectionFactory() { const conn mongoose.createConnection(process.env.MONGODB_URI); conn.model(User, require(../schemas/user)); conn.model(PageView, require(../schemas/pageView)); return conn; };// 导出连接模式在文件顶层创建连接并注册模型业务逻辑直接 require 该连接 // connections/index.js const mongoose require(mongoose); const conn mongoose.createConnection(process.env.MONGODB_URI); conn.model(User, require(../schemas/user)); module.exports conn;验证与后续排查验证连对了租户数据库useDb()返回的连接对象上有db.name属性文档示例中用console.log(Find users from, db.name)打印当前操作的数据库名请求/users/:tenantId应返回该租户数据库下的用户响应 JSON 中是users字段。验证建连是否成功模式 2 中asPromise()在连接失败时会 reject可以据此报错模式 1 中useDb()复用基础连接池建连问题表现为mongoose.connect()的 Promise reject按文档的 Error Handling 一节用.catch()或try/catch处理并监听连接上的error/disconnected事件。模型未注册两个模式的查询前都必须确认目标连接上有对应模型模式 1 的if (!db.models[User])检查和模式 2 中建连时注册模型都是文档示例给出的做法。两种模式不需要混用租户少或负载轻就固定用模式 1租户规模大、流量高就用模式 2。更完整的官方讨论可参考 连接文档 的 Multi Tenant Connections 一节及其引用的多租户模式概述文章。【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考