NestJS+Knex/ObjectionJS MySQL多租户SaaS架构方案求助
NestJS 多租户SaaS数据库连接方案选型
背景说明
- 技术栈:基于NestJS框架,搭配Knex、ObjectionJS及MySQL数据库开发SaaS应用
- 选定多租户架构:租户分组共享数据库模式,即按租户维度分组,同组租户共享同一个数据库实例,区别于单库tenantId字段隔离、独立Schema隔离、全租户独立数据库三种常规方案
- 已实现基础能力:已完成多数据库连接的内存持久化存储逻辑,向Objection模型传入对应Knex实例即可完成对应库的数据操作
三种连接管理方案生产选型
当前梳理的3种可选连接管理方案如下:
- 方案1:应用启动时预先连接所有数据库,所有连接常驻内存,每次请求时切换使用对应Knex连接
- 方案2:应用启动时不预先建立数据库连接,首次请求对应数据库时才建立连接,后续请求复用该常驻连接
- 方案3:创建不指定初始数据库的MySQL服务端连接池,每次请求时切换连接对应的数据库,以此访问不同租户组的数据库,基础连接配置示例:
connection: { host : '127.0.0.1', port : 3306, user : 'your_database_user', password : 'your_database_password', }
生产环境优先选择方案2(懒加载常驻连接),核心原因:
- 应用启动无额外压力:不需要在发布/重启阶段等待所有租户组的连接建立完成,启动速度快,也不会因为个别租户组数据库故障导致整个应用启动失败
- 资源利用率合理:仅会为实际有流量的租户组建立连接,不会给长期无访问的租户组占用无效数据库连接,避免MySQL服务端连接数被无效占满
- 稳定性足够:连接建立后常驻复用,不需要在请求维度做动态库切换,不会出现跨租户状态污染问题
其余两个方案存在明确的生产缺陷:
- 方案1仅适合租户组数量在个位数的场景,租户组规模上涨后,预建的大量空闲连接会快速耗尽MySQL服务端连接配额,运维成本极高
- 方案3的动态切库逻辑存在本质安全风险,Knex/ObjectionJS的连接池没有内置切库后的上下文隔离机制,高并发场景下极容易出现连接复用时的租户数据串库事故,生产环境不建议使用
定时任务、队列场景的数据库访问实现
- 不需要为每个任务单独创建Knex实例:Knex实例本身是自带连接池的重量级对象,频繁创建销毁会带来极大的性能开销,还容易引发连接泄漏问题
- 正确实现逻辑:
- 单租户归属的定时任务、队列消费任务,在任务初始化时就明确绑定所属租户组标识,直接从全局缓存的Knex实例集合中取对应实例复用即可
- 跨租户的全局定时任务(如全租户账单结算、数据巡检),按租户组维度遍历需要访问的Knex实例,受控并行或串行执行逻辑,不要复用HTTP请求链路的上下文动态绑定逻辑
- 任务执行全程固定使用绑定的Knex实例,不要做动态切库操作,从根源避免跨租户数据串流
- 如果任务触发时对应租户组的Knex实例还未建立(从未被HTTP请求触发过懒加载),直接复用现有懒加载逻辑创建连接、放入全局缓存后再使用即可,不需要单独维护一套连接创建逻辑
内容的提问来源于stack exchange,提问作者Harish Jangra
相关产品推荐
相关产品推荐

