NocoBase 共享内存多应用模式(local)实战指南:单实例多应用的环境变量、生命周期管理与隔离原理
2026/9/16 20:25:44 网站建设 项目流程

NocoBase 共享内存多应用模式(local)实战指南:单实例多应用的环境变量、生命周期管理与隔离原理

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

共享内存多应用模式是 NocoBase 多应用管理(Multi App)能力中的轻量级方案:在一个 NocoBase 实例、一个进程内同时运行多个独立应用,每个应用可连接独立数据库、拥有独立 JWT 密钥与自定义域名,但用户只需维护一个实例。本文将基于官方文档 共享内存模式 为主体,结合仓库源码(app-supervisor、plugin-multi-app-manager 等)深入讲解环境变量配置、应用创建/启动/停止/删除的完整操作、数据库创建与适配器体系原理,读完你可以独立完成业务应用级拆分并理解其隔离边界。

一、什么是共享内存多应用模式

当用户希望对业务进行应用级别的拆分(例如将 CRM、售后、运营后台、数据分析拆分为独立应用),但又不希望引入复杂的部署和运维架构时,可以使用共享内存的多应用模式。

在这种模式下,一个 NocoBase 实例中可以同时运行多个应用。每个应用是独立的:

  • 可以连接独立的数据库(或独立的 Schema);
  • 可以单独创建、启动和停止
  • 共享同一个进程和内存空间,用户仍然只需要维护一个 NocoBase 实例。

从资源角度看,相比多进程或多容器方案,共享内存模式资源占用更低;但也正因为所有应用运行在同一进程中,它们会共享 CPU、内存等资源,单个应用的异常或高负载可能影响其他应用的稳定性,因此它更适合业务拆分初期、应用数量可控的阶段。当应用规模持续增长、对隔离性和稳定性提出更高要求时,可以进一步演进到「多环境混合部署」架构(参见 多应用管理总览)。

从代码层面看,这套能力由AppSupervisor(应用监管器)统一承载,它是整个多应用体系的核心控制器,负责应用的发现(Discovery)、进程管理(Process)与命令下发(Command),下文会详细展开。

二、开启共享内存模式:环境变量配置

在使用多应用功能前,请确保在 NocoBase启动时设置以下环境变量:

APP_DISCOVERY_ADAPTER=local APP_PROCESS_ADAPTER=local
  • APP_DISCOVERY_ADAPTER:应用发现适配器名称,负责应用模型的读取、状态维护与 Web 流量分发;
  • APP_PROCESS_ADAPTER:应用进程适配器名称,负责应用实例的创建、启动、停止、删除与错误管理。

在共享内存模式下,两者都取值为local,表示应用模型存储在本机、应用也运行在本进程内。

环境变量的底层解析机制

AppSupervisor在构造时会解析这两个环境变量,源码位于 packages/core/server/src/app-supervisor/index.ts:

private resolveDiscoveryAdapterName() { return process.env.APP_DISCOVERY_ADAPTER || AppSupervisor.defaultDiscoveryAdapterName; } private resolveProcessAdapterName() { return process.env.APP_PROCESS_ADAPTER || AppSupervisor.defaultProcessAdapterName; }

即:未设置时回退到默认适配器名(仓库中默认注册了main-only适配器,多应用管理插件会额外注册legacy适配器并设为默认,见 plugin-multi-app-manager/src/server/server.ts)。适配器采用工厂注册表机制扩展:

AppSupervisor.registerDiscoveryAdapter('main-only', ({ supervisor }) => new MainOnlyAdapter(supervisor)); AppSupervisor.registerProcessAdapter('main-only', ({ supervisor }) => new MainOnlyAdapter(supervisor));

如果解析出的适配器工厂不存在,createDiscoveryAdapter/createProcessAdapter会尝试回退到默认适配器;仍找不到时抛出'No AppDiscovery adapter available'/'No AppProcess adapter available'错误。因此,必须确认所用 NocoBase 版本支持local适配器名称,并保证两个环境变量配对设置,否则实例可能无法按预期进入共享内存模式。

此外,AppSupervisor 还支持APP_COMMAND_ADAPTER(命令适配器)以及可选的APP_SUPERVISOR_AES_SECRET_KEY(用于解密子应用配置中加密的数据库密码),可在 index.ts 中查看相关初始化逻辑。

三、创建子应用:配置项逐项解析

在系统设置菜单中点击「应用监管器」,进入应用管理页面;点击「新增」按钮,创建一个新应用。

注意:应用管理页面由@nocobase/plugin-multi-app-manager插件提供,且该插件只能在主应用(main)中启用(见 server.ts)。

创建表单的配置项说明如下:

配置项说明
应用名称应用在界面中显示的名称(对应displayName字段)
应用标识应用标识,全局唯一(对应name字段,自动生成a_前缀的 UID)
启动方式- 首次访问时启动(autoStart: false):当用户首次通过 URL 访问该子应用时才启动
- 随主应用一同启动(autoStart: true):在主应用启动时同时启动子应用(会增加主应用启动时间)
环境在共享内存模式下,只有本地环境可用,即local
数据库连接用于配置应用的主数据源,支持三种方式:
- 新数据库(new_database):复用当前数据库服务,创建独立数据库
- 新的数据连接(new_connection):连接到其他数据库服务
- Schema 模式(new_schema):当前主数据源为 PostgreSQL/Kingbase 时,为应用创建独立的 Schema
升级若连接的数据库中存在低版本的 NocoBase 应用数据时,是否允许自动升级到当前应用版本
JWT 密钥为应用自动生成独立的 JWT 密钥,确保应用会话独立于主应用及其他应用(对应options.authManager.jwt.secret
自定义域名为应用配置独立访问域名(对应cname字段,全局唯一)

配置项对应的数据模型

应用信息存储在applications数据表中,其集合定义见 collections/applications.ts:

export default defineCollection({ name: 'applications', filterTargetKey: 'name', fields: [ { type: 'uid', name: 'name', primaryKey: true }, // 应用标识,主键 { type: 'string', name: 'displayName' }, // 应用名称 { type: 'string', name: 'cname', unique: true }, // 自定义域名,唯一 { type: 'boolean', name: 'pinned' }, // 是否固定到菜单 { type: 'string', name: 'icon' }, { type: 'string', name: 'status', defaultValue: 'pending' }, // 应用状态 { type: 'json', name: 'options' }, // 应用配置(启动方式、JWT 密钥、数据库连接等) ], });

前端创建表单的定义(client/settings/schemas/applications.tsx)与上述字段一一对应:displayNamenameuid校验,创建时自动生成a_${uid()})、options.autoStart(两种启动方式单选)、cname(自定义域名)、pinnedoptions.authManager.jwt.secret(独立 JWT 密钥,带「独立 JWT 密钥确保数据与会话与其他应用隔离」的说明文案)。

创建时的底层流程

当应用创建成功后,服务端会触发applications.afterCreateWithAssociations事件(见 server.ts),核心逻辑为:

  1. 校验名称:main为保留名称,不可作为子应用标识(对应测试用例 multiple-apps.test.ts);
  2. 通过supervisor.registerApp({ appModel, mainApp })注册应用实例;
  3. 调用supervisor.createDatabase({ app, appOptions })dbConnType创建数据库/Schema;
  4. 若请求上下文中带waitSubAppInstall,则同步执行subApp.runCommand('start', '--quickstart')完成安装与启动;否则异步执行。

仓库测试 multiple-apps.test.ts 覆盖了「创建应用后状态为 running」「合并数据库选项」「按需注册数据库创建器」「创建带自定义插件的应用」等关键路径,可作为理解该流程的验证参考。

四、启动子应用:两种启动方式

创建完成后,点击「启动」按钮可启动子应用;如果创建时勾选了**“首次访问时启动”**,则首次访问时会自动启动。

两种启动方式的行为差异:

  • 首次访问时启动options.autoStart: false):应用模型被创建但不随主应用启动,首次请求命中该应用时才触发启动。主应用启动更快,适合低频或按需使用的应用;
  • 随主应用一同启动options.autoStart: true):主应用afterStart时会自动注册并启动所有autoStart: true的子应用(见 server.ts)。代价是增加主应用的启动时间

「停止」操作通过applications:stop资源动作转发给supervisor.stopApp,「启动」对应applications:startsupervisor.startApp,两者在 server.ts 中注册。

值得注意的是,AppSupervisor 为每个应用维护了互斥锁,并通过可配置并发的引导队列(bootstrapQueue)来串行化启动过程,避免同一应用被并发启动,相关实现可参考 legacy-adapter.ts(其中SUBAPP_BOOTSTRAP_CONCURRENCYSUBAPP_BOOTSTRAP_INTERVAL_CAPSUBAPP_BOOTSTRAP_INTERVAL三个环境变量可控制子应用启动的并发度与间隔)。测试用例「should get same obj ref when asynchronously access with same sub app name」也验证了并发访问同一子应用时只会创建一个实例。

五、访问子应用:路径访问与自定义域名

点击「访问」按钮,会在新标签页中打开该子应用。默认使用/apps/:appName/admin/路径访问子应用,例如:

http://localhost:13000/apps/a_7zkxoarusnx/admin/

其中a_7zkxoarusnx即创建时自动生成的应用标识(name)。主应用网关(Gateway)会根据 URL 中的应用标识把请求路由到对应子应用实例(见 server.ts 中的addAppSelectorMiddleware机制)。

同时,也可以为子应用配置独立的域名

  1. 在创建/编辑表单的「自定义域名」中填写域名(对应cname字段,全局唯一);
  2. 将域名DNS 解析到当前服务器 IP
  3. 如果使用了 nginx,也需要在 nginx 配置里添加该域名的反向代理配置。

底层路由逻辑:网关中间件会读取请求的 Host(x-hostname头或 hostname),在applications表中按cname精确匹配应用,命中后把请求解析到对应子应用(server.ts)。客户端侧的访问地址生成与自定义域名判定逻辑可参考 plugin-client/src/server/appPortals.ts。

建议为不同应用配置独立域名:同一域名子路径下,不同应用切换时可能涉及会话重新登录问题(详见下文「应用会话」)。

六、停止与删除应用

  • 停止:点击「停止」按钮可停止子应用(有二次确认)。停止后应用实例从进程中被移除,状态更新为stopped,再次访问时会按启动方式重新拉起;
  • 删除:点击「删除」按钮可移除应用。服务端监听applications.afterDestroy事件并调用supervisor.removeApp,将应用实例从监管器中移除(server.ts)。

仓库测试 multiple-apps.test.ts 验证了「删除应用记录后,AppSupervisor 中不再持有该应用实例」。

七、应用状态解析

在应用监管器列表中可查看每个应用的当前状态。结合 client/settings/schemas/applications.tsx 与 AppSupervisor 的状态机,完整状态枚举如下:

状态含义
preparing准备中:应用即将进入引导启动流程
initializing初始化中:正在创建/初始化应用实例
initialized已初始化:实例已创建但尚未启动完成
running运行中:应用正常对外服务
commanding执行命令中:正在执行升级、插件安装等维护命令
stopped已停止:实例已停止
error错误:启动或命令执行出现致命错误
not_found未找到:引导后未找到对应应用实例

状态由discoveryAdapter维护,并在appStatus中缓存。应用事件(__started/__stopped/maintaining)会驱动状态流转:例如启动完成后置为running,停止后置为stopped,维护命令期间进入commanding,出现fatal级别错误时置为error(见 index.ts)。应用列表接口会实时附加每个应用的状态(applications:list后置处理器)。

八、底层原理:AppSupervisor 与数据库创建器

8.1 适配器体系(Discovery / Process / Command)

AppSupervisor是单例(getInstance()),内部由三类适配器协作完成多应用管理:

  • DiscoveryAdapter(发现适配器):负责应用模型(AppModel)的增删改查、应用状态(appStatus)、环境注册与心跳、以及 Web/WebSocket 流量代理(proxyWeb/proxyWs);
  • ProcessAdapter(进程适配器):负责应用实例的创建、启动、停止、删除(createApp/startApp/stopApp/removeApp/upgradeApp),以及应用错误记录;
  • CommandAdapter(命令适配器):负责跨环境维护命令的分发。

local模式下发现与进程适配器同名,AppSupervisor 会优先复用发现适配器作为进程适配器(当它实现了addApp/startApp方法时,见 index.ts),这正是「共享内存、单进程运行多个应用」的实现基础。

8.2 数据库创建器(AppDbCreator)

应用创建时如何落库,取决于数据库连接配置项。AppSupervisor 使用条件注册表ConditionalRegistry)把三种创建策略与dbConnType条件绑定(db-creator.ts):

export const createDatabaseCondition = ({ appOptions }) => !appOptions?.dbConnType || appOptions.dbConnType === 'new_database'; // 新数据库 export const createConnectionCondition = ({ appOptions }) => appOptions?.dbConnType === 'new_connection'; // 新的数据连接 export const createSchemaCondition = ({ appOptions }) => appOptions.dbConnType === 'new_schema'; // Schema 模式

三种策略的实际行为:

dbConnType适用场景底层实现
new_database(默认)复用当前数据库服务创建独立数据库MySQL/MariaDB:CREATE DATABASE IF NOT EXISTS;PostgreSQL/Kingbase:检查pg_databaseCREATE DATABASE
new_connection连接到其他数据库服务对目标服务执行建库;PostgreSQL 下若指定了schemaCREATE SCHEMA IF NOT EXISTS
new_schema主数据源为 PostgreSQL/Kingbase 时创建独立 SchemaCREATE SCHEMA IF NOT EXISTS仅支持 postgres/kingbase,其他方言直接抛错'Schema is only supported for postgres/kingbase'

8.3 应用选项工厂(AppOptionsFactory)

子应用的默认配置由appOptionsFactory基于主应用生成(app-options-factory.ts),关键逻辑:

  • 默认dbConnType = 'new_database',数据库名取应用标识(rawDatabaseOptions.database = appName);
  • 主数据源为SQLite时,为每个应用生成独立的.sqlite文件(与主应用存储同目录,命名为${appName}.sqlite);
  • USE_DB_SCHEMA_IN_SUBAPP === 'true'且方言为 PostgreSQL/Kingbase 时,自动切换为new_schema模式,以应用标识作为 Schema 名;
  • 每个子应用的缓存(cacheManager)使用appName作为前缀,避免多应用缓存键冲突;
  • 子应用默认加载['nocobase']插件集,并沿用主应用的日志配置。

子应用实际的配置为「默认选项」与「创建时填写的选项」深度合并的结果,测试用例「should merge database options」验证了这一点。

九、常见问题与最佳实践

1. 插件管理

其他应用可以使用的插件与主应用一致(包括版本),但每个应用可以独立配置和启用/停用插件。子应用创建时也可通过options.plugins指定启用的插件及插件配置(见测试「should create with plugins」)。

2. 数据库隔离

其他应用可以配置独立的数据库(或独立 Schema),实现数据层面的物理隔离。如果希望应用之间共享数据,可通过外部数据源功能实现,而不应依赖共享同一数据库。

3. 数据备份和迁移

目前主应用上的数据备份不包含其他应用的数据(只包含应用的基本信息),需要在其他应用内分别手动备份和迁移各自的数据。规划多应用方案时,请提前为每个子应用建立独立的备份策略。

4. 部署与更新

在共享内存模式下,其他应用的版本会自动跟随主应用升级,自动保证应用版本一致。主应用执行upgrade时,subAppUpgradeHandler会遍历应用记录,为每个子应用执行升级(server.ts)。仓库测试分别覆盖了「主应用升级时自动升级 autoStart 子应用」与「未设置 autoStart 的子应用不随主应用升级」两种行为。因此升级主应用前,应确认所有子应用均处于可升级状态。

5. 应用会话(安全边界)

  • 若应用使用独立的 JWT 密钥:应用会话独立于主应用及其他应用。但通过同一域名的子路径访问不同应用时,由于应用 TOKEN 缓存在 LocalStorage 中,在不同应用间切换时需要重新登录。建议为不同应用配置独立域名,以实现更好的会话隔离;
  • 若应用未使用独立的 JWT 密钥:会共享主应用的会话,同一浏览器访问其他应用后返回主应用无需重新登录,体验更顺滑。但存在安全隐患:如果不同应用的用户 ID 重复,可能导致用户越权访问其他应用的数据

出于安全考虑,多租户/多客户场景下强烈建议为每个应用开启独立 JWT 密钥,并配合独立域名使用。

十、小结

共享内存多应用模式(APP_DISCOVERY_ADAPTER=local+APP_PROCESS_ADAPTER=local)是 NocoBase 在「单应用」与「多环境混合部署」之间的理想折中:通过AppSupervisor的适配器体系与数据库创建器,在一个进程内实现多个物理隔离应用的创建、启动、停止与访问,适合业务模块拆分、团队并行开发、演示与测试环境等场景。需要牢记其两个边界:资源上共享 CPU/内存备份与升级以主应用为中心;同时通过独立 JWT 密钥与独立域名守住会话隔离的安全底线。当应用规模进一步增长时,可参考 多应用管理总览 演进到多环境混合部署架构。

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询