1.4 技术栈对齐:Node 与云产品地图
选型定了长连接、房间、事件流之后,要把技术栈摊开对齐:Node 承担什么、云上的 Redis/COS/CDN 各在哪个位置、谁来管状态、谁来存快照、谁来加速。本篇把这张地图画清楚,后面每篇的代码和配置都往这里挂。先把每个组件的「角色、数据、扩法」三件事对齐,避免后面各篇各说各话。
各组件的分工
| 组件 | 角色 | 在白板里的职责 |
|---|---|---|
| Node.js | 长连接服务 | 房间会话、事件广播、心跳保活 |
| Redis | 房间状态 + Pub/Sub | 在线成员、最新状态、多节点房间共享 |
| COS | 对象存储 | 白板快照、导出图片 |
| CDN | 静态加速 | 回放数据、导出资源就近下发 |
| CVM | 承载 | 上面三样都跑在云服务器上 |
组件分工定了,每章的「代码放哪、配置放哪、扩容怎么扩」都有归属。这张地图还有一层意义:技术栈对齐本质上是责任划分。出问题时,「这笔笔触存在哪、是谁推出去的、从哪恢复」三个问题能立刻定位到组件,而不是几个团队互相甩锅。地图上每条边都是一次责任交接,交接点越清晰,故障定位越快。
Node 在地图里的边界
Node 在这张地图里只做一件事:管连接、管中继、不管存储。这是刻意收窄的边界——长连接节点一旦承担存储职责(把日志、快照放本地),扩容就会和存储耦合,加机器变得绑手绑脚。Node 只把事件收进来、广播出去、必要时写一跳 Redis,剩下全交出去。边界收窄的代价是跨进程调用增多,收益是每个组件都能独立扩、独立换。实战里把这叫「让有状态的归有状态,让无状态的尽量无状态」,Node 节点本身越接近无状态,横向扩容越丝滑。
| 职责 | 归属 | 理由 |
|---|---|---|
| 连接保活 | Node | 进程内会话,快 |
| 事件中继 | Node | 收到即广播,不落地 |
| 房间状态 | Redis | 跨节点共享 |
| 日志快照 | COS / Redis | 持久化与回放 |
| 权限元信息 | 数据库 | 事务一致性 |
各组件的扩容线
地图画完,还要给每个组件定一条独立扩容线,否则一个组件涨大家都跟着乱扩。Node 按「连接数」扩,Redis 按「房间数与消息频率」扩,COS 按「快照量与留存天数」扩,CDN 按「分享与回放的带宽」扩。四条线分开看,哪条吃紧扩哪个,才不会出现「明明是存储瓶颈却在加机器」的错配。建议给每个组件配上一个主监控指标,扩容决策直接对着指标下,而不是凭感觉。
| 组件 | 主监控指标 | 扩容触发示例 |
|---|---|---|
| Node | 峰值连接数、广播延迟 | 连接数达规格 70% |
| Redis | 房间数、Pub/Sub 消息延迟 | 消息延迟 P95 > 20ms |
| COS | 存储容量、请求量 | 容量月增超 20% |
| CDN | 回源带宽、命中率 | 命中率跌破 90% |
为什么房间状态放 Redis
长连接节点是有状态的,单节点扛几千连接就到顶;要多节点,房间里「现在有谁在线」就不能只存某台机器的内存,要放共享存储。Redis 合适:读快、能存哈希/集合、支持 Pub/Sub。 | 数据 | Redis 结构 | 用途 | |---|---|---| | 在线成员 | Set | 进出房、计数 | | 房间最新态 | Hash | 新成员进房补齐 | | 跨节点广播 | Pub/Sub | 多节点房间共享 | | 热日志 | List / Stream | 近期增量 |
跨节点的「把这笔推给同房间别人」,靠 Redis 发布订阅广播给其他节点,再走各自的本地长连接推出去,这就是多节点房间共享的骨架。
快照为什么放 COS 而不是数据库
白板画了三天,中间状态可能上万笔。全塞数据库,查询和回放都会被撑爆;正确做法是日志存细、快照存粗:操作日志是事件流(可重放、可撤销),而「某一时刻的整板」生成图片快照放 COS。 | 数据 | 存哪 | 原因 | |---|---|---| | 操作日志 | 热在 Redis 流 / 冷在 COS | 只增可重放 | | 快照 | COS | 大对象、可 CDN | | 导出资源 | COS | 写一次读多次 | | 房间元信息 | 数据库 | 事务、查询 |
导出分享、快速恢复、做封面都走快照,把数据库从「存海量笔」的负担里解放出来。
日志、快照、数据库三者边界
既然引入了 COS 快照,就要把「操作日志、快照、业务数据库」三者的边界划清楚,否则很容易互相串戏。操作日志负责可重放:只增不改,热段放 Redis Stream 供近期回放,冷段归档 COS;快照负责快速恢复:某个时刻的整板,回放时作为锚点,从锚点之后只重放增量;业务数据库只存房间元信息与权限:房间名、创建人、成员表,事务强、量小。三者各管一段,查询路径明确:实时状态问 Redis,回放问日志加快照,权限问数据库。
| 数据 | 载体 | 查询路径 | 生命周期 |
|---|---|---|---|
| 实时房间状态 | Redis | 读 Hash / Set | 房间关闭即清 |
| 近期操作日志 | Redis Stream | 按序重放 | 滚动几分钟 |
| 历史日志 | COS 归档 | 拉取后重放 | 按留存策略 |
| 整板快照 | COS | 直接加载 | 里程碑留存 |
| 房间元信息 | 数据库 | 事务查询 | 长期 |
边界划清后,一个常见误解也顺带消解:快照不是备份的替代。快照能快速恢复「画面」,但丢了一段时间的日志,撤销栈就断了,快照只是让你「不用从第 1 笔重放」。备份策略仍要覆盖日志归档,别用快照糊弄备份。
为什么 CDN 要进来
回放数据、导出图片都是「写一次、读多次」的典型——适合走对象存储加 CDN:COS 存原件,CDN 按地域就近下发,分享链接点开不用跨域拉源站,体验和带宽成本都更优。把静态资源从长连接节点和数据库后面摘出去,是白板扩容的关键一刀。CDN 进来之后还要关注一个指标:回源率。分享链接如果带的是随机参数(比如时间戳),CDN 缓存会被逐条击穿,回源率飙升、加速形同虚设。正确做法是导出资源用内容哈希命名,分享链接固定指向 CDN 路径,让缓存命中率稳定在高位。
地图验证:上线前逐条过
地图画得再漂亮,不验证也会漏。上线前建议拉着各方逐条过一遍:断开 Redis,多节点房间是否还能降级运行;COS 不可用时,导出与快照是否优雅报错而不是白屏;CDN 命中率达标后,回放链接的打开耗时是否在预期内。每个组件都问一句「挂了会怎样、降级到哪、谁看到报警」,答得上来才算地图落地。
| 验证项 | 操作 | 期望 |
|---|---|---|
| Redis 断连 | 摘掉 Redis | 房间降级提示,不崩 |
| COS 不可用 | 拒写快照 | 导出报错可重试 |
| CDN 回源 | 带随机参数 | 监控命中率与耗时 |
| 单节点宕机 | 关一个 Node | 连接自动重连到存活节点 |
整张地图串起来
用户 → 长连接节点(Node)→ 房间/状态(Redis)→ 快照(COS)→ 加速(CDN)。节点有状态就共享到 Redis,海量笔不压库就走日志加快照,静态资源下沉到 COS 加 CDN。四个组件各司其职,扩容时分别按「连接数、房间数、快照量、带宽」四条线独立扩,互不牵连。这张地图的组件划分与 Node.js 云开发实战 里长连接服务的落地方式一致,数据库侧的冷热分层可对照 MySQL 实战。
踩坑提示
最典型的坑是把「共享状态」当「共享连接」:多节点时,A 节点的成员和 B 节点的成员在同一房间,但长连接不能跨节点直推,必须靠 Redis Pub/Sub 把事件从 A 广播给 B,再由 B 推给本地连接。漏掉这层「发布订阅」,多节点上线就出现「画了别人看不见」。再一个坑:把「共享连接」当「共享状态」——反向思维,把会话对象放内存、想当然以为跨节点可见,结果多节点各存各的,房间串不起来。
小结
Node 管连接与广播,Redis 管房间状态与多节点共享,COS 存快照与导出,CDN 加速静态。把有状态的和无状态的、写多的和读多的分开,白板才扩得动。组件边界定清楚,后面每篇的优化与扩容才有靶子。
延伸阅读
- 白板持久化分工,见下一节 4.1。
- 快照与 CDN 加速,见下一节 4.2。
- 长连接服务落地,见 Node.js 云开发实战。
- 数据库冷热分层,见 MySQL 实战。
