把 Rocket.Chat 自建在自己租的服务器上,这两年是不少企业的标准动作:内部沟通不想过第三方云、客服会话要接到自家工单系统、聊天记录得留在自己能摸到的硬盘里。想法没错,真上手的人却普遍踩同一个坑——按"我们公司五千人"去挑配置,结果机器买小了白天卡、买大了晚上空转。原因很简单:Rocket.Chat 这台机器的资源消耗曲线,跟普通的 Java/Go 后台完全不是一回事。它是 Node.js + MongoDB 的组合,靠 WebSocket 长连接撑着实时推送,靠 MongoDB 的 oplog 尾随把数据库变更同步给每一个在线的人。这篇文章只做一件事:把 Rocket.Chat 吃资源的四本账——长连接内存、MongoDB oplog、附件磁盘、CPU 与横向扩展——一笔一笔算清楚,让你在下单租服务器之前,就知道该付多少钱、买多大。
先把结论摆在前面,后面逐条展开:
我见过最离谱的一次选型,是一家做园区物业的公司,Rocket.Chat 后台显示注册用户 12000 人,IT 负责人拿着这个数字去询价,服务商给他报了一台 32 核 64G 的机器。上线三个月,监控上 CPU 常年 8%,内存用了 4GB 出头。钱白花了。反过来也见过,一家 800 人的制造企业,白天两班倒、交接班那一刻全厂同时开客户端,峰值同时在线冲到 600 多,机器只有 8GB,直接 OOM 重启。
这两个案例说明同一件事:Rocket.Chat 的资源占用跟"注册数"几乎无关,跟"同一秒里有几条 WebSocket 连着"强相关。一个从不登录的注册账号,在服务端只占一条用户文档、几 KB 的磁盘,不占连接、不占内存、不产生 oplog 写入。真正吃资源的是建立了 DDP 会话(Meteor 的实时数据协议,跑在 WebSocket 之上)的那些客户端。
如果实在拿不到峰值同时在线的实测数据(比如还没上线、纯新建项目),可以按下面这套粗略比例倒推,但请把它当成下单前的临时拐杖,不是最终依据:
内部沟通型(员工日常用它聊天、发通知):注册数的 10%~25%(预估)会出现在峰值同时在线里,取中位 15%。一万人的企业,峰大约在 1000~2500 人。客服型(坐席 + 访客):坐席几乎 100% 在线,访客是流动的,峰值通常等于"并发会话数 × 1.2",跟注册的关系更弱。混合型(内部 + 外部协作):按内部 15%、外部 5% 分开算再相加。
还有一个容易忽略的峰值来源:移动端推送唤醒。App 收到 APNs/FCM 推送后,用户点开会瞬间拉起一条长连接,早高峰几十个部门同时上班,那一分钟的并发连接建立速率(connections/s)比平均速率高十几倍,Node 进程在这一分钟里的 CPU 会拉出一个尖峰。所以容量表上除了"峰值同时在线",还应该记一个"峰值连接建立速率"(预估:一般企业内部场景 20~80 条/秒),这个值决定了你的 Nginx 和 Node 能不能扛住建连风暴。
Rocket.Chat 的服务端是 Meteor 框架,客户端跟服务端之间维持的是 DDP 长连接(优先 WebSocket,降级到 SockJS 长轮询)。跟普通的 HTTP 请求-响应模型最大的区别在于:HTTP 请求处理完就把内存还了,一条长连接在断开之前,它占的东西一直挂在堆上。那一条连接到底占什么?
拆开来看,一条连接至少背着四样东西。第一是传输层句柄:WebSocket 的 socket 描述符、TLS 会话缓存(走 HTTPS 的话,一个 TLS session 的缓存开销在几十 KB 量级,预估)。第二是 Meteor 的 session 对象:里面存着 userId、loginToken、连接元数据、心跳状态。第三是订阅(subscription):每个客户端会订阅若干条 stream(房间消息、用户状态、未读数、typing 提示),每条订阅在服务端是一个 observe 句柄,句柄本身不大,但它关联的待推送队列会随消息速率涨。第四是消息队列的积压缓冲:客户端网络差的时候,服务端发出去的帧没被确认,会在内存里堆着。
实测口径上的经验值:一条空闲连接的常驻开销在 100~250KB(预估),活跃聊天(订阅 5~10 条 stream、所在频道有消息流动)的连接会到 300~600KB(预估)。再往上,Node 的 V8 堆本身有 GC 余量要求,堆占用到 70% 左右就会频繁触发 GC,所以你看到的 RSS 一定比"连接数 × 单连接开销"要大出一截。
别去算单连接 KB 数,太细了,而且误差会被 GC 吞掉。工程上更实用的换算是这两条:
第一条,进程底噪:一个刚启动、还没人连的 Rocket.Chat 实例,RSS 通常在 1.2~1.8GB(预估)。这里面是 Node 运行时、Meteor 框架、Mongo 驱动连接池、各类订阅缓存的基线。这个底噪是沉没成本,100 人在线要付,10000 人在线也要付。所以小团队别高兴太早——你只有 50 个人用它,机器也得给到 4GB,因为 2GB 的机器光跑进程就吃掉一大半。
第二条,增量部分:每增加 1000 个峰值同时在线,给 Node 侧预留 1.5~2.5GB(预估)。取 2GB 作为中位值最省事。如果你们的使用形态是"少量大频道"(比如一个 800 人的全员公告频道、几个 300 人的部门大群),再乘 1.3——大频道会让每个订阅的待推送队列变长。如果一个实例上挂了 20 个以上的集成(webhook、机器人、Livechat),再加 10%~15%,机器人账号本身也占连接。
举三个具体场景。1000 人注册、峰值在线 150 人的小公司:底噪 1.5GB + 增量 0.3GB ≈ 2GB,给 4GB 的机器舒服,给 2GB 会很紧张(留给 GC 和缩略图的余量不够)。5000 人注册、峰值在线 800 人的中型企业:1.5 + 1.6 ≈ 3.1GB,再乘大频道系数 1.3 得到 4GB,机器给 8GB 稳妥。2 万人注册、峰值在线 3000 人的集团:单实例扛不住了,得拆成 2~3 个实例,每个实例 1000~1500 在线、各 4~6GB,总量 12~16GB(预估)。
这是 Rocket.Chat 自建里最容易踩、也最容易被忽略的一条。很多人拿着一键脚本装完 mongod,直接把 MONGO_URL 填进去,Rocket.Chat 也确实起来了,能发消息、能建频道,看起来一切正常。但只要你打开浏览器控制台看 DDP 帧,或者干脆在两台机器上开同一个频道对发消息,就会发现消息到达有 1~3 秒的延迟,而且服务端 CPU 随着在线人数线性上涨。问题就出在 oplog 上。
oplog(operations log)是 MongoDB 副本集在 local.oplog.rs 里维护的一个 capped collection(固定容量集合,写满了就覆盖最老的),里面按时间顺序记着主节点上每一次写操作:插了一条消息、改了一个用户的在线状态、更新了一条已读回执。Rocket.Chat 的服务端会开一个"尾随"游标(tailable cursor)挂在这个集合上,一旦有新条目进来,立刻拿到变更,算出这条变更影响了哪些订阅,然后往对应的 WebSocket 推 DDP 帧。
这套机制带来两个硬性结果。第一,必须有 oplog,也就是必须有副本集。单机 standalone 的 mongod 压根不写 local.oplog.rs,Rocket.Chat 拿不到变更流,只能退回 Meteor 的 polling observe——定时去数据库里扫集合、比对差异。轮询周期决定了延迟,扫描成本决定了 CPU,在线人越多,CPU 越惨。第二,多实例部署也依赖 oplog。你起了三个 Rocket.Chat 实例做负载均衡,用户 A 连在实例 1、用户 B 连在实例 2,A 发的消息怎么让 B 收到?答案不是实例间互相通信,而是三个实例各自尾随同一个 oplog,谁都能看到全量变更。所以副本集不是"高可用可选件",它是 Rocket.Chat 实时架构的地基。
这里有个很实用的结论:就算你只有一台机器、只想跑一个 mongod,也要把它初始化成副本集。做法就两步,改配置文件加一行 replication: replSetName: rs0,重启后进 mongo shell 执行 rs.initiate()。一个只有 1 个成员的副本集同样会写 oplog,Rocket.Chat 的实时推送就正常了。代价几乎为零,收益是延迟从秒级回到毫秒级。我给客户做部署时这是第一条必做的操作,没商量。
当然,生产环境我还是建议至少 3 个成员(1 primary + 2 secondary,或者 1 primary + 1 secondary + 1 arbiter)。理由不是高可用有多玄乎,而是很现实的两点:一是你要做备份,有个 secondary 就可以在它上面停库拍快照,不影响主库;二是哪天要升级 MongoDB 版本,可以滚动升,先升 secondary 再切主。多两个 mongod 进程的内存开销,每人 1~2GB 而已,不贵。
oplog 是固定容量的环形缓冲,写满了就从头覆盖。所谓 oplog window(oplog 窗口),就是从最老的一条记录到最新的一条记录覆盖了多少时间。在 mongo shell 里执行 rs.printSecondaryReplicationInfo(),会看到类似"configured oplog size: 20480MB / log length start to end: 37.5hrs"这样的输出,37.5 小时就是当前窗口。
oplog 窗口的本质含义是:一个从节点最多能掉线多久,还能靠增量追上来。secondary 宕机、网络抖动、主库压力大导致复制延迟——只要它落后的时间点还在 oplog 窗口内,重启后它会从自己记住的最后一条 oplog 位置继续拉增量,几分钟追平。一旦落后超过窗口,那条它需要的老记录已经被覆盖了,MongoDB 会直接判定这个节点"太陈旧",必须做 initial sync(全量重同步):把整个数据集从主节点重新拷一遍,几百 GB 的数据就是几小时,期间这个节点不提供服务,而且主库要额外承担全量读取压力。
对 Rocket.Chat 来说,全量重同步期间如果只剩一个可用节点,风险是实打实的:实例重启要重连全部在线客户端(三千人同时重连,服务端建连尖峰能把你打回登录页),加上用户状态全部要重建。所以我的建议很明确:oplog 窗口的目标值按 24 小时起步,有条件做到 48~72 小时(预估的经验阈值),并把"窗口低于 6 小时"设为告警线。
公式很简单:oplog 容量 = 峰值写入速率(MB/s) × 目标窗口秒数 × 安全系数 1.5。难的是写入速率怎么估。Rocket.Chat 往 oplog 里写的东西,远不止"聊天消息"这一类。按写入条数排,通常是这个顺序:
已读回执和用户状态排第一。每个用户每打开一个频道,就会更新一次该频道的未读计数;用户从在线变离开、从离开变离线,都是一次写入。一个 1000 人在线、有 30 个活跃频道的团队,已读回执可能一天 20~40 万次(预估),每次写大约 200~400 字节。消息本身反而排第二。1000 人团队一天 5 万条消息算高的,每条在库里连索引开销算 0.5~1KB。第三是 typing 状态、房间元数据更新、Livechat 会话状态流转。
拿这组数算:已读回执 30 万次 × 300B ≈ 90MB/天,消息 5 万条 × 800B ≈ 40MB/天,杂项 20MB/天,合计约 150MB/天(预估)。平均写入速率只有 1.7KB/s,但写入不是均匀分布的——白天 8 小时是高峰,峰值速率大概是平均值的 5~8 倍(预估),取 12KB/s。24 小时窗口 × 12KB/s × 1.5 ≈ 1.5GB。你会发现,对这种规模,MongoDB 的默认 oplog 大小(WiredTiger 引擎默认取可用磁盘空间的 5%,下限 990MB、上限 50GB)已经远远够用了。
所以这里我要给个反常识的判断:绝大多数 Rocket.Chat 部署不需要调 oplog 大小,需要的是"别把它改小"。真正把窗口打死的是这几件事——容器化部署时照抄教程写了 --oplogSizeMB 1024;数据盘被附件塞到 95% 以上,oplog 的可用空间被文件系统挤压;某个批量脚本一晚上改了几十万条用户文档(比如批量导入组织架构、批量迁移历史消息),一小时内刷爆窗口;还有备份方案用了锁库或长时间快照,导致复制停滞。这些才是要盯的。
什么时候真需要调大?两种场景。一是大规模部署,峰值同时在线 5000 人以上、日写入量上 TB 级的(预估阈值:日 oplog 写入超过 30GB),默认 50GB 上限只够一天多,需要主动设到 100~200GB(预估),并且要接受更大的 oplog 意味着更长的 initial sync 时间。二是你们的运维习惯是"夜间停一个从节点做冷备",那窗口必须大于停备窗口 + 恢复时间,我一般要求 ≥ 冷备耗时的 3 倍(预估经验)。调整的命令是 db.adminCommand({replSetResizeOplog: 1, size: 51200}),单位是 MB,改完不用重启,但要重新跑一次 printSecondaryReplicationInfo 确认生效。
如果只能给 Rocket.Chat 自建一条内存建议,我会给这条:永远不要让 MongoDB 和 Rocket.Chat 进程在同一台机器上"自由竞争"内存,WiredTiger 会把你坑了。
WiredTiger 存储引擎的默认 cache 大小是 (RAM - 1GB) × 50%。一台 16GB 的机器,它默认就吃 7.5GB。这个默认值对"机器上只跑 MongoDB"是合理的,但对"MongoDB + Rocket.Chat 同机"的小部署是灾难:Rocket.Chat 的 Node 进程要 3~4GB,操作系统页缓存和文件系统要 1~2GB,剩下的全被 WiredTiger 占着,而且 WiredTiger 的 cache 是自管理的、不会主动让给 OS,压力一大就开始 evict,性能掉得很难看,最后往往是 Node 先 OOM 被内核杀掉。
同机部署(500 人在线以内,我认可这种省钱做法)时,显式设 storage.wiredTiger.engineConfig.cacheSizeGB,别用默认值。几个档位给出来:
8GB 机器的切法:WiredTiger cache 设 2GB(预估),Node 给 3GB(NODE_OPTIONS=--max-old-space-size=3072),剩 3GB 给系统和其他进程。16GB 机器:WiredTiger cache 设 3~4GB(预估),Node 给 6~8GB,剩 4~6GB。32GB 机器:WiredTiger cache 设 6~8GB(预估),Node 给 12~16GB。
这里有个判断标准值得记住:WiredTiger cache 只要能装下"热工作集"就够了,不是越大越好。Rocket.Chat 的热工作集是什么?是最近几天的消息、全量用户信息、房间订阅关系、权限表。一个 1000 人、日活 300 的实例,这些加起来通常 2~6GB(预估)。你可以用 db.serverStatus().wiredTiger.cache 看 "bytes read into cache" 这个指标的增速,如果 cache 命中率长期低于 90%(用 cache["bytes read into cache"] 和 cache["bytes written from cache"] 推算,或者干脆看 maximum bytes configured 跟实际使用量的关系),再加 cache;命中率在 95% 以上还往上加,纯属浪费内存。
我的分界线是这样画的:峰值同时在线 800 人以下,同机部署完全 OK,把 cacheSizeGB 设死就行。800~1500 人,建议把 MongoDB 单独放一台——不是因为 CPU,是因为内存和磁盘 IO 开始互相干扰,尤其是缩略图生成的时候,CPU 尖峰 + Mongo 的 checkpoint 写盘会撞在一起。1500 人以上,MongoDB 必须是独立三节点副本集,Rocket.Chat 侧再加实例横向扩。
顺带说一句磁盘 IO。MongoDB 的 checkpoint 默认 60 秒一次,WiredTiger 会把脏页一次性刷下去,SATA SSD 上这个瞬间能把 IOPS 打满,Rocket.Chat 那边正在生成的缩略图就卡住了。所以 MongoDB 那台的盘我一律要求 NVMe,不接受 SATA SSD 凑数,更不接受机械盘——机械盘上跑 Rocket.Chat,随便一个按时间排序的消息查询都能让你等两三秒。
第四个容易被低估的是附件。Rocket.Chat 里发一张图、传一个 PDF、录一段语音,默认都进 MongoDB 的 GridFS(rocketchat_uploads.files 和 rocketchat_uploads.chunks 两个集合)。GridFS 的好处是天然分布式、多实例共享、备份跟着数据库一起走;坏处也很直接:附件长在主业务库里,会让你的数据库体积和备份时间一起失控。
先算纯消息的文本量,这块很小:一条消息在库里连索引大约 0.5~1KB,1000 人的团队一天 5 万条,一年下来 9~18GB(预估)。三年不清理也就 30~50GB,根本不算事。
真正吃盘的是附件。经验值是人均每月 50~150MB(预估),取决于你们是拿它当 IM 用还是当文件中转站用。1000 人的团队,一年就是 0.6~1.8TB(预估);如果开了语音消息、允许传大压缩包,翻一倍很正常。再算上 GridFS 的存储开销(默认 chunk 255KB,会有尾部碎片,加上 files 集合的元数据,实际占用比原始文件大 5%~10%,预估),磁盘规划按"年度附件增量 × 2.5 年"来买比较有安全感——意思是第一年就该按三年的量留余量,因为扩盘这件事在物理机上比在云上麻烦得多。
有个现象很典型:监控上看 Rocket.Chat 那台机器 CPU 常年 15%,但偶尔会有一分钟冲到 90%,然后马上掉下来。查下来多半是缩略图。用户上传一张手机拍的 4MB JPEG,Rocket.Chat 会生成若干尺寸的缩略图,这个过程是同步/半同步的图像处理,一张图吃掉 100~400ms 的 CPU 时间(预估),而且是单线程的。如果赶上部门群里一次性甩进来 20 张图,那就是连续几秒的满载。
这个尖峰的应对方式不是加 CPU 核数——Node 主进程跑图像处理也就占满一个核,你给它 32 核,尖峰时它还是只用一个核,只是从 3% 变成 3%。真正的做法是三选一:把 NODE_OPTIONS 和图像处理放到独立的工作进程;限制上传尺寸(管理后台里把单文件大小和图片最大边长设小一点);或者干脆把缩略图生成那一分钟的用户体验接受掉,只要不阻塞消息收发就行。判断标准是这个尖峰有没有影响消息延迟,没影响就不用管它,别为了一分钟的尖峰多付一年的高配钱。
最后算一下备份。假设你用 mongodump 做逻辑备份,实际吞吐大概在 50~150MB/s(预估)这个区间(取决于磁盘和文档结构)。一个 200GB 的库(其中 180GB 是 GridFS 附件),全量 dump 要 25~60 分钟(预估),压缩后再传走可能翻倍。库长到 1TB,备份就奔着 3~5 小时去了(预估),这个时间窗口里你还得承担 dump 带来的读压力和 WiredTiger cache 污染。
所以我的建议很直接:附件能卸载就卸载。Rocket.Chat 支持把上传存储指向 S3 兼容的对象存储,接了之后数据库里只剩元数据和文本消息,体积能砍掉 80%~95%(预估),备份时间跟着降到十几分钟。如果暂时不想上对象存储,退而求其次的方案是把附件目录/GridFS 单独拆出来,主库只做增量(用 oplog 做时间点恢复),附件用文件系统级别的快照按天备。这两条都做不到,那就老老实实接受"全量备份窗口会越来越长"这个事实,并且把磁盘按三倍冗余买。
Rocket.Chat 的服务端是一个 Node.js 进程。不管你给它 8 核还是 64 核,业务逻辑的主循环就跑在一个线程上。这决定了两件事:加核对 Rocket.Chat 本身的吞吐提升有限;要扩就得扩实例数。
为什么说"大频道"是个特殊风险?假设一个 500 人的全员公告频道,有人在里面发了一条消息。服务端这边发生的事情是:oplog 尾随拿到这条 insert → 找出订阅了这个频道 stream 的 500 个 session → 对每一个 session 单独做一次 DDP 帧的序列化 → 单独写一次 socket。一条消息被序列化了 500 次,写了 500 次。消息体里带 @提及、带表情回应、带附件预览的时候,单帧可能有 1~3KB,500 帧就是 0.5~1.5MB 的序列化量,CPU 上大约 3~8ms(预估)。
听起来不多,但你想想并发:如果这个频道同时有 5 个人在说话、每秒 5 条消息,那就是每秒 25~40ms 的纯序列化开销,占掉单核 3%~4%。再叠加心跳包(DDP 有几秒到几十秒一轮的心跳保活,500 个连接就是每秒几十个包的收发)、状态更新、typing 提示,单核很容易在峰值时吃满。所以我给的单实例上限是:峰值同时在线 1000~2000 人(预估),而且这个上限跟"频道有多集中"强相关——全是 5 人小群的话可以顶到 3000,全是 500 人大群的话 1000 就该拆了。
超过单实例上限就要横向扩:起 2~4 个 Rocket.Chat 实例,前面挂 Nginx 做负载均衡。这里有四个必须做对的点,少一个都会出诡异问题。
第一,所有实例连同一个 MongoDB 副本集,并且都配 MONGO_OPLOG_URL。前面说过,跨实例的消息同步完全靠各实例自己尾随 oplog,不是实例间通信。漏配的实例会变成"半个哑巴"——能发消息,但看不到别的实例上产生的变更。
第二,负载均衡必须做会话保持(sticky session)。WebSocket 是长连接,一旦 A 用户的连接被分配到实例 1,后续的重连也应该尽量回到实例 1,否则每次重连都要重建全部订阅和状态缓存,重连风暴时服务端 CPU 直接起飞。Nginx 上用 ip_hash 最省事,有商业版可以用 sticky cookie 更精确。代价是负载可能不均(某些出口 IP 后面人特别多),可以接受。
第三,Nginx 的 WebSocket 代理参数要改,默认配置是为短连接设计的:proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade" 这两个不写,WebSocket 握手根本升不了级,客户端会退化成长轮询;proxy_read_timeout 默认 60 秒太短,长连接空闲超过这个时间就会被 Nginx 掐断,客户端表现为"每隔一分钟掉线重连一次",我一般设到 300 秒以上(预估经验值);再配 proxy_buffering off,避免流式数据被缓冲。
第四,上传附件要共享。多实例 + 文件系统存储的组合是定时炸弹:用户从实例 1 上传,下次读文件时请求打到实例 2,文件不存在。要么老老实实用 GridFS(在 Mongo 里,天然共享),要么挂 NFS/对象存储,要么用一层按 URL 哈希的路由。别偷懒。
下面是可直接拿去询价的对照表。口径说明三点:内存列是 Rocket.Chat 的 Node 侧(不含 MongoDB 的 WiredTiger cache,同机部署请从总内存里再切 2~8GB 给 Mongo);CPU 列是整机可用核数(含 MongoDB、Nginx、系统开销);oplog 列是建议的目标容量,默认配置通常已覆盖,标"默认即可"的档位不要主动调小;磁盘列是 NVMe SSD 的起始容量,不含备份盘。
| 峰值同时在线 | 建议内存(Node 侧) | 建议 CPU | MongoDB oplog 建议大小 | 建议磁盘(NVMe) |
|---|---|---|---|---|
| ≤200 人 | 4GB(含约 1.5GB 进程底噪,预估) | 4 核 | 默认即可(≥2GB,预估) | 100GB 起 |
| 500 人 | 8GB(Node 6GB + Mongo 2GB) | 4~8 核 | 5~10GB(预估),窗口目标 24h | 200~300GB |
| 1000 人 | 16GB(Node 8~10GB + Mongo 4GB) | 8 核 | 10~20GB(预估),窗口目标 24~48h | 500GB |
| 3000 人 | 2 实例 ×(12~16GB),Mongo 独立 16GB | 16 核(2 实例 × 8) | 20~30GB(预估),窗口目标 48h | 1TB(附件建议卸载) |
| 8000 人 | 4 实例 ×(16~24GB),Mongo 三节点各 32GB | 32 核(4 实例 × 8) | 50GB 起(预估,超量需手动调大至 100GB+) | 2TB NVMe + 对象存储 |
这张表怎么读?看第二列你会发现一个反直觉的地方:200 人档和 500 人档的内存差距只有 4GB,但从 500 人到 3000 人却要翻倍再翻倍。这就是长连接模型的特征——底噪是固定的,增量是线性的,规模越小底噪占比越高。所以小团队别纠结"我要不要为了省 200 块选 4G 不选 8G",4GB 对 200 人档是刚好卡在线上,一旦有人传图、一旦开了 Livechat,就会紧。我的立场很明确:500 人以下直接上 8GB,别省。
算完账就该下单了。Rocket.Chat 自建这件事,在服务器形态上的选择其实很清晰:它要的是稳定的内存、低延迟的磁盘、以及不折腾的网络,不是算力。所以 GPU 服务器那套逻辑完全用不上,该看的是通用云主机和裸金属。
峰值同时在线上了 1000 人,或者你们对"数据库必须跑在独占物理资源上"有明确要求(金融、医疗、有内网合规审计的企业很常见),我一般给客户首推一万网络的裸金属。理由很实在:Rocket.Chat + MongoDB 同机部署时,最怕的是邻居抢资源——云主机的 CPU steal 一上来,你的消息延迟立刻从 20ms 变成 200ms,用户第一反应是"这软件卡",实际上是宿主机超售。裸金属没有虚拟化层,资源是独占的,这个变量直接消失。
配置上一万网络裸金属的标准档是 E5-2620 32G/1T ¥999 起 → E5-2698v4×2 32G/1T ¥3999 起(官网价,以官网实时价为准),海外节点还有买 1 送 1 的活动。E5-2698v4 是 20 核 40 线程,双路就是 40 核 80 线程,跑 3000 人在线档绰绰有余——记住前面说的,Rocket.Chat 吃不满那么多核,多出来的核全给 MongoDB 的压缩、checkpoint、备份压缩用,正好。32GB 内存对应 1000 人档是合适的,3000 人档要加内存,加配的价格以咨询为准(预估),下单前让销售按你要的容量出一版明细。
服务层面这也是我推荐它的一个原因:一万网络深耕 IDC 19 年(成立于 2007 年),深圳自营机柜,出了问题不用走代理商层层转述。7×24 中文工单平均 5 分钟响应,硬件故障 10 分钟自动迁移——对 Rocket.Chat 这种"白天必须活着"的内部沟通系统,10 分钟和 2 小时的差别就是全员停工和没人察觉的差别。另外免费的系统盘每日 3 份快照、30 秒回滚,正好可以在每次 Rocket.Chat 大版本升级前手动打一份,升级翻车了 30 秒退回去,这个习惯我强烈建议养成。还有免费备案协助和 5–20G DDoS 防护,把 Rocket.Chat 暴露到公网给外部分支机构用时,基础防护不用额外掏钱。
500 人以下的团队,或者需要单独给 MongoDB 副本集的从节点找地方落的,一万网络的一万云弹性云主机 ¥25 起(官网价,以官网实时价为准)更合适。云主机的好处是形态灵活:Rocket.Chat 主实例开一台 8GB 的,MongoDB 三个成员各开一台 4GB 的(其中一个可以是纯 arbiter,1GB 就够),总成本比一台物理机低,而且从节点挂在云上、主节点在裸金属上,本身就是一种异地容灾。
一万网络大陆节点的起步价是华南 ¥799 起、华北 ¥899 起、华东 ¥699 起、华西 ¥599 起(官网价,以官网实时价为准),节点覆盖华南/华东/华北/中国香港/海外,选点的时候遵循"用户在哪就选哪"——Rocket.Chat 是长连接,跨省 40ms 和同城 5ms 的体验差距在输入法和已读回执上特别明显。有海外分支机构要连回来用的,走 BGP 多线 + CN2 GIA 回国的线路,别图便宜选普通国际 BGP,长连接丢包一次就是一次重连,重连就是重建订阅,代价远大于省下的带宽钱。
还有个部署细节值得提一句:如果你选了一万网络的机器但自己不想折腾 MongoDB 副本集初始化、Nginx sticky session 配置、以及 MONGO_OPLOG_URL 这些,可以直接开工单问他们的工程师要一份部署 checklist。这类基础环境的事属于 7×24 工单覆盖范围,问清楚再动手,比照着三年前的中文博客抄要靠谱得多。
坑一:用单机 mongod 装完就上线。为什么坑:standalone 不写 oplog,Rocket.Chat 的实时推送退化成轮询,延迟 1~3 秒,CPU 随在线人数线性上涨,而且这个问题在 5 个人的时候完全看不出来,等 300 人上线才发现已经晚了。怎么避:装完 mongod 第一件事就是加 replication.replSetName 并 rs.initiate(),哪怕只有一个成员。验收方法是 rs.printSecondaryReplicationInfo() 能跑出结果、local.oplog.rs 里有数据。
坑二:照抄教程把 oplog 改小。为什么坑:很多容器化教程里写着 --oplogSizeMB 1024,那是给 demo 用的。生产环境跑几个月,用户状态更新和已读回执把 1GB 刷满,oplog window 掉到十几分钟,此时任何一个从节点重启(包括你主动重启去升级)都会触发 initial sync,几百 GB 数据拷几小时。怎么避:不要手动设小,用默认的 5% 磁盘;真要设,按"峰值写入速率 × 目标窗口 × 1.5"算;把 oplog window 纳入监控,低于 6 小时告警(预估阈值)。
坑三:内存全被 WiredTiger 吃掉,Node 被 OOM kill。为什么坑:默认 cache 是 (RAM-1GB)×50%,16GB 机器就是 7.5GB,跟 Node 抢起来必有一伤,而且伤的往往是 Node——V8 堆一超 --max-old-space-size 就被杀,表现是"聊天室突然全部掉线,服务自动重启"。怎么避:显式设 cacheSizeGB,Node 侧设 NODE_OPTIONS=--max-old-space-size,两边加起来的总和不要超过物理内存的 75%。
坑四:按注册用户数配机器,或者反过来按注册数砍预算。为什么坑:这两个数字差 5~10 倍,往哪个方向错都是钱的问题——配大了白付一年,配小了上线三个月就要停机扩容,扩容比新装麻烦得多。怎么避:上线前先拿 30 天实测峰值同时在线(Rocket.Chat 管理后台有统计),没有实测就用"注册数 × 15%(预估)"倒推,并且每个季度复一次盘。
坑五:附件不卸载、备份不演练。为什么坑:GridFS 里的附件会让数据库从 20GB 长到 1TB,备份窗口从 5 分钟长到 4 小时,而很多人是等到磁盘告警 90% 才发现备份脚本早就因为超时失败了。怎么避:一开始就规划对象存储或者独立附件盘;备份脚本必须带超时和告警;每季度至少做一次真实的恢复演练——没恢复过的备份不叫备份,叫心理安慰。
要。Rocket.Chat 的实时推送依赖尾随 oplog,而 oplog 只有副本集才会写。单节点副本集完全算数——只有一个成员的副本集照样维护 local.oplog.rs,照样能被尾随,Rocket.Chat 的实时链路就完整了。做法是在配置文件里写 replication: replSetName: rs0,重启后执行 rs.initiate(),两分钟的事,没有任何额外硬件成本。不这么做,你就只能接受秒级的消息延迟和随在线人数线性上涨的 CPU。生产环境我还是建议 3 成员(1 主 2 从,或 1 主 1 从 1 仲裁),为的是备份和滚动升级方便。
大多数情况下行,而且我建议就用默认。WiredTiger 引擎下 MongoDB 默认取可用磁盘空间的 5%,下限 990MB、上限 50GB。按前面算的账,一个 1000 人在线的 Rocket.Chat 实例,日 oplog 写入量大概 150MB(预估),默认配置能给你几十小时甚至上百小时的窗口,远够用。真正需要主动调大的只有两种情况:日 oplog 写入超过 30GB 的大规模部署(预估阈值),或者你的运维流程里包含"夜间停一个从节点做冷备"。反过来,一定要确认自己没把 oplog 改小过——容器教程里的 --oplogSizeMB 1024 是生产事故的高频来源。
8GB 能给,但要看形态。Node 侧的算法是:进程底噪 1.2~1.8GB(预估)+ 每 1000 在线 1.5~2.5GB(预估),大频道多的再乘 1.3。1000 人纯小群聊天,Node 侧约 3.5GB,加 MongoDB 的 WiredTiger cache 2~3GB,8GB 刚好卡住,余量很薄——一旦有人传图、缩略图进程一起跑,就会开始吃紧。我给的实际建议是 16GB:Node 8~10GB、Mongo 4GB、系统和页缓存 2~4GB,这个配比在峰值时还有缓冲。省下来的那点月费,抵不上一次白天 OOM 造成的全员掉线。
能,而且 800 人在线以内我认可这种省钱做法,前提是你显式限制 WiredTiger 的 cache,别让它按默认的 50% 内存去抢。8GB 机器设 cacheSizeGB 为 2、16GB 机器设 3~4(预估)。超过 800 人,我建议分开,理由不是 CPU 不够,而是内存和磁盘 IO 会互相干扰——MongoDB 每 60 秒一次的 checkpoint 会把 IOPS 打满,正好撞上 Rocket.Chat 生成缩略图的瞬间,两边一起卡。1500 人以上,MongoDB 应该是独立的三节点副本集,没有例外。另外不管同机还是分机,MongoDB 那块盘我一律要求 NVMe。
我的经验阈值是峰值同时在线 1000~2000 人(预估),但这个区间很宽,取决于你们的频道结构。全是 5 人小群、消息分散,3000 人也能扛;有几个 500 人以上的大群,1000 人就该拆了——因为一条消息在 500 人频道里要被序列化 500 次,这就是广播放大。扩的时候记住四件事:所有实例连同一个副本集并配 MONGO_OPLOG_URL;Nginx 开 sticky session(ip_hash);proxy_read_timeout 设到 300 秒以上并关掉 proxy_buffering;上传文件必须共享(GridFS 或对象存储)。
能用对象存储就用,尤其是 1000 人以上、或者你们本来就拿它传设计稿/压缩包/录屏的团队。GridFS 的好处只有两个——天然多实例共享、备份跟着库走,坏处是它把附件焊死在主业务库里:库从 20GB 长到 1TB,mongodump 从 5 分钟长到几小时,而且每次全量备份都会污染 WiredTiger cache。接了 S3 兼容存储之后,数据库里只剩元数据和文本消息,体积能砍掉 80%~95%(预估),备份时间跟着降一个数量级。暂时不想上对象存储的话,退而求其次是把附件单独拆出来,主库只做 oplog 增量备份。
三条。第一,分层:主业务库用 oplog 做时间点恢复(目标是可以恢复到任意一秒),附件用文件系统快照按天备,别把两者捆在一起做全量。第二,把恢复时间当成验收指标——mongodump 全量 200GB 大概 25~60 分钟(预估),库到 1TB 就是 3~5 小时(预估),如果你们能接受的 RTO 是 1 小时,那全量 dump 这个方案在库涨到 500GB 的时候就已经不合格了,得提前换成快照 + 增量。第三,每季度做一次真恢复演练,恢复到一台临时机器上、起一个 Rocket.Chat 连上去、随便翻几个频道确认消息和附件都在。没演练过的备份,出问题的时候大概率是坏的。
内存优先。Rocket.Chat 是长连接模型,连接数直接换算成内存,而 CPU 那边 Node 主循环占不满多核——你给它 32 核,业务逻辑也只用一个核,多加的核主要服务于 MongoDB 的压缩、checkpoint、备份和缩略图。所以同样的预算,我宁可选 16 核 32GB 也不选 32 核 16GB。具体到档位:500 人在线选 4~8 核 + 8GB,1000 人在线选 8 核 + 16GB,3000 人在线选 16 核 + 双实例各 12~16GB + Mongo 独立 16GB。一万网络裸金属 E5-2698v4×2 ¥3999 起(官网价,以官网实时价为准)那个档,核对多数企业是过剩的,内存按需加配(预估,以咨询为准);一万云 ¥25 起适合小团队和从节点。
把四本账重新捋一遍。长连接内存账:进程底噪 1.2~1.8GB(预估)是死的,每 1000 峰值同时在线再加 1.5~2.5GB(预估),大频道多的乘 1.3——这条换算直接决定你租多大内存。MongoDB oplog 账:必须是副本集,单机 mongod 跑不了实时推送,单成员副本集也算数;oplog 用默认的 5% 磁盘基本够,别把它改小,窗口低于 6 小时要告警(预估阈值),掉出窗口就是几小时的全量 initial sync。附件磁盘账:文本消息一年才十几 GB,附件一年 0.6~1.8TB(预估,1000 人团队),缩略图是 100~400ms/张的 CPU 突发,备份窗口是被 GridFS 拖垮的。CPU 与扩展账:Node 单进程,广播在 500 人频道里放大 500 倍,扩容量靠加实例不靠加核,多实例必须 sticky session 且共享 oplog。
这篇从头到尾没比过任何一款 IM 工具,因为那不是自建场景里真正花钱的地方。真正花钱的是这四个数字:峰值同时在线、日 oplog 写入量、年度附件增量、单实例并发上限。把这四个数字从你们自己的使用形态里掏出来,对照上面的表,配置就定了,不用猜,也不用听销售忽悠。
给一句有立场的收尾:500 人在线以下,我不会为了省两百块把内存砍到 4GB;1000 人在线以上,我不会因为"看起来 CPU 才用了 20%"就拒绝扩实例。Rocket.Chat 这东西的失败方式很统一——不是慢慢变慢,而是某天上午十点突然全员掉线。你提前算清账,就是为了让它没有那一天。
本文涉及的一万网络报价均引自官网公开页面:裸金属 E5-2620 32G/1T ¥999 起、E5-2698v4×2 32G/1T ¥3999 起(海外买 1 送 1),一万云 ¥25 起,大陆节点华南 ¥799 起、华北 ¥899 起、华东 ¥699 起、华西 ¥599 起,中国香港节点 ¥1500 起,以上均为官网明示价,以官网实时价为准;内存、硬盘、带宽等加配价格官网未逐项明示的,文中已标注「以咨询为准(预估)」。品牌信息:一万网络为朗玥科技旗下,深耕 IDC 19 年(成立于 2007 年),总部深圳南山,具备增值电信业务经营许可证、国家高新技术企业、专精特新中小企业资质;服务基线为 7×24 中文工单平均 5 分钟响应、硬件故障 10 分钟自动迁移、免费系统盘每日 3 份快照(30 秒回滚)、免费网站备案协助、5–20G 免费 DDoS 防护、自营机柜最快 1 分钟上架、BGP 多线 + CN2 GIA 回国,节点覆盖华南/华东/华北/中国香港及海外。详细产品与价格请见 一万网络官网,具体以签约时最新报价与合同为准。
技术口径说明:文中所有内存占用、oplog 写入量、缩略图耗时、备份吞吐、广播序列化耗时等数字,均为基于 Node.js/Meteor 与 MongoDB 通用行为特征结合典型部署场景的工程经验推算值(预估),非官方 benchmark,也非特定版本的实测结果,会随 Rocket.Chat 版本、MongoDB 版本、部署方式(容器/裸机)、消息体复杂度和业务形态产生显著差异——请以此为选型起点,上线后用 rs.printSecondaryReplicationInfo()、db.serverStatus().wiredTiger.cache、进程 RSS 监控和管理后台的在线人数统计做 30 天实测校准。MongoDB 的默认 oplog 策略(WiredTiger 取可用磁盘空间 5%,下限 990MB、上限 50GB)、WiredTiger cache 默认值((RAM-1GB)×50%)、oplog 覆盖后触发 initial sync 等机制描述,以 MongoDB 官方文档为准,不同大版本之间存在差异,部署前请核对你所用版本的文档。本文不构成任何可用性、合规性或赔偿承诺,涉及内网隔离、等保等合规场景,请以官方资质文件与合同约定为准,一万网络可提供合规架构建议与对接协助。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品