Rocket.Chat:面向任务关键型组织的自托管 CommsOS 架构拆解
posts posts 2026-06-17T21:04:44+08:00Rocket.Chat 是面向任务关键型组织的开源通信平台,用 Yarn 4 workspaces + Turborepo 组织上百个 TS 包,并通过 NATS/moleculer 拆微服务。技术笔记Rocket.Chat, TypeScript, Monorepo, 微服务, CommsOS目录
- 项目定位
- 学习目标
- 项目位置
- 系统地图
- 一次消息从发送到订阅
- Apps-Engine 的运行时边界
- 微服务边界:从 monolith 拆出的 4 个核心域
- 联邦、E2EE 与合规的工程实现
- 部署路径与陷阱
- 谁该用、谁可以等
- 采用顺序与决策建议
- 自测题
- 边界与未覆盖
项目定位
Rocket.Chat 不是"又一个 Slack 克隆"。仓库 README 把产品定位写得很直白——“The Secure CommsOS™ for mission-critical operations”,并把 Deutsche Bahn、The US Navy、Credit Suisse 列为典型客户。当一家客户的核心需求是"通信数据不能离开自己的网络、必须可审计、可联邦、可被本国监管框架接受"时,它要解决的就不再是"消息能不能送达",而是一整套"通信操作系统"的命题:自托管、气隙部署、端到端加密、跨组织联邦、模块化扩展、企业级 SSO/SAML/LDAP。本文关注的不是 README 上的功能清单,而是仓库里那一百多个 TypeScript 包、微服务拆分边界和 Apps-Engine 这层扩展框架是怎么把这些需求落地的。
学习目标
读完本文,你应该能够:
- 识别 Rocket.Chat monorepo 的四层结构(主应用 / 微服务 / UI 包 / Apps-Engine),并说明每层承担的职责
- 解释 DDP 协议 + MongoDB Oplog 在消息订阅推送链路中的角色,以及单节点 Mongo 会带来什么退化
- 描述 Apps-Engine 的沙箱模型与钩子链,判断一个集成需求应该写 App 还是 fork 主应用
- 区分 NATS/moleculer 私有事件与公开 REST API 的边界,避免自接入服务时反向 hack
- 评估联邦、E2EE、合规审计三项能力对部署姿态的影响,并给出 PoC 前的检查清单
项目位置
RocketChat/Rocket.Chat 是 GitHub 上规模最大的自托管团队通信仓库之一(以下数据统计时点为 2026-06-17,Stars/Forks 与 Release 版本会随后续发布变动):
| 指标 | 数值 |
|---|---|
| Stars / Forks | 45,503 / 13,656 |
| 主要语言 | TypeScript |
| 包管理 | Yarn 4.12.0(Berry workspaces) |
| 构建编排 | Turborepo |
| Node 运行时 | 22.22.3 |
| 协议 | MIT(apps/meteor/ee/、ee/ 下为独立 EE 许可证) |
| 最近 Release | 8.5.1(2026-06-15) |
| 仓库组织 | monorepo:apps/* + packages/* + ee/apps/* + ee/packages/* |
要理解这个仓库的形状,先抛掉"Rocket.Chat 是一个 Meteor 应用"的旧印象。apps/meteor 仍是承担实时消息主链路的核心应用,但它已经不是孤岛——apps/ 旁还有 uikit-playground,packages/ 里有 50 多个 TS 包,ee/ 下另有一套企业版二进制和微服务。
系统地图
仓库根目录的 package.json 直接声明了 workspace 范围(apps/*、packages/*、ee/apps/*、ee/packages/*),并把 turbo.json 作为构建与测试编排入口。这套结构对应的不只是工程便利,而是 Rocket.Chat “主应用 + 微服务 + UI 包 + 扩展框架"四件套的产品布局:
┌─────────────────────────────────────────────────────────────────────┐
│ apps/meteor (主应用,消息主链路) │
│ - Meteor + React + TypeScript │
│ - DDP 协议、WebSocket、消息存储、订阅推送 │
│ - Apps-Engine 宿主 │
└──────────────┬─────────────────┬────────────────────┬───────────────┘
│ │ │
┌───────────▼──────┐ ┌───────▼──────┐ ┌───────────▼──────────┐
│ Microservices │ │ UI 工具包 │ │ 扩展框架 │
│ (ee/apps/) │ │ (packages/) │ │ (packages/ │
│ │ │ │ │ apps-engine) │
│ - authorization │ │ - fuselage │ │ │
│ - account │ │ - ui-kit │ │ 第三方 App 加载 │
│ - presence │ │ - ui-video │ │ (Apps-Engine) │
│ - omnichannel │ │ - ui-voip │ │ │
│ │ │ - ui-composer│ │ │
│ 通信:NATS / │ │ - gazzodown │ │ │
│ moleculer │ │ - i18n │ │ │
└─────────┬────────┘ └──────────────┘ └───────────────────────┘
│
┌─────────▼──────────────────────────────────────────────────────┐
│ 数据层 │
│ - MongoDB(必须 replica set,使用 Oplog 做 change streams) │
│ - 文件存储:本地 FS / AWS S3 + CDN │
│ - 实时事件总线:NATS(TRANSPORTER 环境变量切换) │
└─────────────────────────────────────────────────────────────────┘读这张图要锁定四件事,否则后面所有判断都会跑偏:
- monorepo 不是简单聚合,而是分层。
apps/meteor仍是核心后端,承担实时消息、DDP(Distributed Data Protocol,Meteor 自有的发布订阅协议)、WebSocket、订阅推送;packages/提供跨边界复用的 UI 组件、HTTP 路由、REST 类型、消息解析器、国际化等;ee/apps/是从主应用剥离出来的微服务,承担鉴权、账号、在线状态、客服等独立可扩展的子域;ee/packages/则是 EE 二进制配套的私有能力包。 - 微服务之间靠 NATS/moleculer 通信,而不是直接 HTTP。
docker-compose-ci.yml里所有微服务都共用同一个TRANSPORTER环境变量(缺省TCP,可换 NATS),并通过mongo+nats两个有状态依赖相互连接。这意味着服务间调用走的是 moleculer 的事件总线,而不是 Rocket.Chat 自己暴露的 REST API——这是它在 7.x 之后从单体走向分布式的关键架构选择。 - 数据层强依赖 MongoDB 副本集 + Oplog。
docker-compose-ci.yml里的MONGO_URL形如mongodb://mongo:27017/rocketchat?replicaSet=rs0,订阅推送依赖 Oplog;如果部署单节点 Mongo 或没开 replica set,消息实时性会直接劣化。这一点在所有官方部署文档里都是 P0 警告。 - Apps-Engine 是独立子框架,不属于 Meteor 应用内部。
packages/apps-engine是一个独立的 TypeScript 运行时,允许第三方把"机器人、集成、UI 扩展"打包为 zip 并热加载到主应用里。它是 Rocket.Chat 平台化能力的承载体,也是一道把"主应用代码"和"市场里第三方代码"隔离开的安全边界。
一次消息从发送到订阅
光看结构容易把 Rocket.Chat 误读成"普通的实时聊天后端”。它真正的差异在于"DDP 协议 + Meteor 发布订阅 + Apps-Engine 钩子 + 微服务事件总线"这四件套。下面用"用户 A 在 channel X 发一条文本消息、用户 B 在另一台设备立刻看到"为例,看一次消息在系统里的实际流转(具体类名以仓库当前版本为准,命名遵循 Meteor 约定):
- 前端组装消息。客户端(Web / Desktop / Mobile)用 React + Fuselage UI Kit 把消息封装成 DDP 方法调用
sendMessage,连同 channelId、文本、附件、@mention 一并送出。 - Meteor 路由与方法执行。
apps/meteor/server/methods/下的sendMessage在服务端校验身份(鉴权可能由authorization-service微服务在 NATS 上代为校验)、权限(角色 + 权限位)、消息长度与速率限制,然后把消息写入 MongoDBmessages集合,并生成对应subscriptions更新。 - Oplog 触发订阅推送。Meteor 通过 MongoDB Oplog 监听该 channel 的变更,所有通过 DDP
subscriptions订阅了该 channel 的客户端会收到一条added/changed事件;用户 B 的客户端在不动用额外请求的前提下,UI 立刻把消息卡片 push 出来。 - Apps-Engine 拦截器触发。消息写入前后,Apps-Engine 会按 App 注册顺序触发
IPreMessageSentPrevent、IPostMessageSent等钩子;第三方 Bot 可在这里拦截、改写、转发——这也是 Rocket.Chat 集成的核心扩展点。 - 微服务侧异步消化。如果是客服场景,omnichannel 微服务在 NATS 上订阅到
livechat.message事件后,会更新坐席会话状态、写入reporting_event,并把这条消息同步到外部 CRM。 - 推送与离线补齐。未在线的设备由推送服务(FCM/APNs)经
apps/meteor/server/services/收到通知,用户回到客户端后 DDP 自动 reconnect + 增量补齐未确认消息。
理解这条链路再看部署清单,瓶颈就清晰了:MongoDB 的 Oplog 是订阅推送的唯一可靠源(停掉它就退化为定时轮询,延迟和压力都会放大);NATS 是微服务之间唯一可靠的事件通道(不可降级为同步 HTTP,否则背压处理会复杂);Apps-Engine 的执行时间计入消息发送路径,第三方 App 卡住会反过来卡住主链路。
Apps-Engine 的运行时边界
packages/apps-engine 是 Rocket.Chat 把第三方 App 当作一等公民承载的运行时,定义了四类边界:
- 权限沙箱:每个 App 拥有独立的
Accessors,只能访问显式授权过的 HTTP endpoint、消息流、UI 区域。 - 声明式 UI 扩展:App 可以声明按钮、模态框、表单字段(基于
ui-kit包),UI 渲染由主应用代理执行,App 自身只声明 schema。 - 事件钩子链:消息前/后置、用户加入/离开、room 创建/关闭、Livechat 事件等都有标准钩子;钩子以 await 串行执行,超时会被记入审计日志。
- 持久化沙箱:App 拥有自己的 KV 存储(
AppPersistence),不能直接读写主数据库。
“集成 vs 自研"的判断线就在 Apps-Engine 这一层。如果一个功能能在 App 里用钩子 + UI 扩展实现,就不要去 fork apps/meteor。这是 50+ packages 之所以能稳定演化的核心约束——主应用边界稳定,第三方能力在引擎里横向扩张。
微服务边界:从 monolith 拆出的 4 个核心域
docker-compose-ci.yml 列出了当前 EE 路径下的微服务家族,下面这张表把每个微服务的职责和通信依赖写出来,方便评估"应该接哪个、不该接哪个”:
| 微服务 | 职责 | 通信依赖 | 适合独立扩展的理由 |
|---|---|---|---|
authorization-service | 角色、权限、OAuth scope 校验 | NATS + Mongo | 权限校验是热点路径,独立扩缩容可降低主应用压力 |
account-service | 用户账号 CRUD、跨服务账户合并 | NATS + Mongo | 账号域改动频繁,独立部署可让主应用少升级 |
presence-service | 在线状态、最后在线时间、跨设备聚合 | NATS + Mongo | 状态写多读多,独立成服务便于单独优化 |
omnichannel-service | Livechat / 客服会话、坐席分配 | NATS + Mongo | 客服业务规则复杂,单独演进不影响主聊天 |
这些微服务的协议和数据格式都是 NATS/moleculer 私有事件,不是公开 HTTP API。在自部署里接入自己写的服务时,最稳的路径是写一个独立的"适配层"用 moleculer client 接入,不要尝试通过 REST 反向 hack,否则会失去 NATS 的事件回放与背压能力。
联邦、E2EE 与合规的工程实现
Rocket.Chat 把联邦、端到端加密和合规做成了产品级而非"加挂"级的能力:
- 联邦(Federation):基于 Matrix Federation 协议,允许不同 Rocket.Chat 实例(甚至不同厂商的通信服务)互相发现、加入房间、交换消息。README 直接给出"Enable federation"的开关与文档链接,意味着联邦不再是 Roadmap 项,而是已经可用的生产功能。部署联邦实例需要打开
Federation_Matrix_enabled等开关,并正确处理 DNS SRV 记录与证书。 - 端到端加密(E2EE):通过
e2e-keys和 DDP-S 二进制通道交换密钥,加密消息以加密 blob 落库;服务端无法解密,审计需要从客户端侧获取密钥。E2EE 与合规审计天然冲突——开启后合规日志会失去可读性,这一点在政府/金融场景必须提前与合规团队对齐。 - 合规与审计:Trust Center 与 Compliance Center 提供 SOC2、ISO27001、GDPR、HIPAA 等合规说明;EE 版提供审计日志、Retention 规则、Legal Hold 等企业级能力。这些能力不是某个隐藏开关,而是
ee/目录下的代码与服务,是 Rocket.Chat 敢于叫 “CommsOS” 的工程支撑。
部署路径与陷阱
README 列了四条官方路径,本文不复制,只把"哪个路径对应哪种合规姿态"梳理出来:
- Docker / Podman / Kubernetes(推荐生产路径):和
docker-compose-ci.yml几乎一一对应;MongoDB 副本集与 NATS 是必备依赖,K8s 路径还需要为 Oplog 留出磁盘 IO 余量。 - Launchpad:Rocket.Chat 自家的 Kubernetes 一键脚手架,省去管理每个依赖的成本,适合不想自建 K8s 又想跑生产路径的中小团队。
- Air-gapped 部署:完全离线网络下的部署模式,对应国防、能源等高敏感场景;离线包需要预先拉取 MongoDB、NATS、Docker 镜像,并通过内网 registry 拉起。
- Cloud(付费):Rocket.Chat 官方运维的商业云,按 SLA 收费;适合"想用但不想自己运维"的政企客户。
工程上最容易踩的三个坑:
- MongoDB 没开 replica set。Oplog 是实时推送的依赖,单节点 Mongo 在压力下会把 DDP 推送退化成定时轮询,CPU 立刻被打满。
- 把 Apps-Engine 当成普通 npm 包来 fork。
packages/apps-engine的版本与主 Meteor 应用强绑定,跨版本升级经常需要同步迁移 App schema。 - 微服务通信用 REST 反代。失去 NATS/moleculer 的事件回放后,任何"分布式事务"都会退化为补偿逻辑,最终会重写到主应用里。
谁该用、谁可以等
适合选 Rocket.Chat 的场景:
- 政府、国防、能源、金融等强合规场景,需要通信数据完全自托管、可审计、可气隙;
- 已有 Kubernetes + MongoDB + NATS 运维能力,需要"统一通信平台 + 客服 + 联邦"三件套;
- 大型组织(数千至上万坐席),需要 SSO/SAML/LDAP、Retention、Legal Hold、审计日志等 EE 能力;
- 不想把通信数据放在 SaaS 第三方,但又不愿意投入 Slack/Teams 替代品的从头研发。
可以再等等或换方案的场景:
- 团队 < 50 人,纯内部沟通,不涉及合规与联邦需求——Mattermost、Element 这类更轻量的方案维护成本更低;
- 客服场景单一,且已经买了 Zendesk/Intercom——Rocket.Chat 的客服能力对标的是 omnichannel,不一定比专业客服系统更省事;
- 团队没有 Kubernetes 或 MongoDB 副本集的运维经验——Meteor 主应用在单节点 Mongo 下能跑,但实时性会显著劣化,长期不划算。
采用顺序与决策建议
按团队现状给出推进顺序,避免一步到位踩坑:
- 先验证主链路:用
docker-compose-ci.yml起一套带 MongoDB 副本集 + NATS 的最小集群,跑通 Web 端发消息、订阅推送、文件上传。这一步确认运维基线,不接 EE。 - 再评估扩展边界:把第一个集成需求(机器人、外部系统通知、自定义审批流)写成 Apps-Engine App,而不是 fork
apps/meteor。如果 App 跑得通,后续 80% 的集成需求都可以走这条路。 - 按需启用 EE 微服务:当主应用出现权限校验或在线状态热点时,再引入
authorization-service/presence-service,并提前规划 NATS 集群与 moleculer client 的运维。 - 最后处理合规姿态:联邦、E2EE、Legal Hold 这三项必须在合规团队介入后再启用,E2EE 一旦开启不可逆地影响审计日志可读性。
自测题
- 在
apps/meteor单节点 + 单节点 Mongo(无副本集)的部署下,用户 B 收到用户 A 消息的延迟特征会发生什么变化?为什么? - 一个"消息发出后自动同步到外部 CRM"的集成需求,应该写成 Apps-Engine App 还是 fork 主应用?给出判断依据。
- 自部署里要接入一个自研的审计服务,直接 REST 调用
omnichannel-service的接口会有什么后果?正确的接入路径是什么? - 开启 E2EE 后,企业合规团队要求保留可读的聊天审计日志,这两项要求能否同时满足?如何对齐?
authorization-service与presence-service都依赖 NATS + Mongo,如果 NATS 集群不可用,主应用apps/meteor还能正常发消息吗?说明调用链。
边界与未覆盖
- 本文没有展开
packages/fuselage-ui-kit的具体组件层级、http-router与rest-typings的生成机制,这些是 UI/类型层细节,需要结合二次开发目标再读。 - 本文没有涉及 E2EE 的密钥轮换、审计日志与 Retention 规则的合规配置流程——这部分企业落地前必须单独跑一遍 PoC。
- 本文没有跑 benchmark。仓库没有公开"每秒能处理多少 DDP 订阅"“微服务在 NATS 上的事件吞吐"这类数字,自部署规模评估只能结合自己压测;不要直接套用其它 Meteor 应用的通用经验。
Rocket.Chat 是一份"成熟且边界清晰"的开源答案:主链路稳定、扩展边界明确、企业能力集中在 ee/。剩下的工程取舍在合规姿态、运维能力和微服务规模这三个维度上。