基于Fastify+SSE的类Twitter项目实时订阅系统扩容架构咨询
架构方案推荐与实现细节
针对你的分布式实时订阅场景,以下是几种可行的架构方案,结合优缺点和落地细节给出建议:
1. 集中式订阅存储(Redis/DragonflyDB)
这是分布式场景下最通用、可靠的方案,核心是将用户订阅关系统一存储在共享内存数据库中,服务实例无状态,扩容不受限制。
键设计方案
- 用户订阅列表:使用键
user:subscriptions:{userId},值用集合(Set)存储用户订阅的项目ID。操作时:- 订阅项目:
SADD user:subscriptions:{userId} {projectId} - 取消订阅:
SREM user:subscriptions:{userId} {projectId} - 查询用户订阅的所有项目:
SMEMBERS user:subscriptions:{userId}
- 订阅项目:
- 项目更新频道:按项目拆分Pub/Sub频道,命名为
project:updates:{projectId},服务实例仅订阅当前连接用户对应的项目频道,避免接收无关消息。 - 用户活跃标识:用键
user:active:{userId}存储当前连接的服务实例ID,设置TTL(比如30分钟,与SSE心跳间隔匹配),用于清理无活跃连接的实例订阅。
流程细节
- 用户建立SSE连接时,服务实例从数据库拉取该用户的订阅项目列表,自动订阅对应的
project:updates:{projectId}频道。 - 当项目有更新时,发布者将消息推送到对应项目的频道,订阅该频道的服务实例直接将消息推送给连接的客户端。
- 用户发起订阅/取消请求时,先更新数据库中的用户订阅集合,再通过专属通知频道(如
user:subscriptions:update:{userId})告知当前用户连接的实例,实例收到通知后调整订阅的项目频道。 - 当SSE连接断开或
user:active:{userId}的TTL到期,实例自动取消订阅该用户对应的项目频道,减少资源占用。
优缺点
- 优点:服务实例完全无状态,扩容/缩容无需额外处理;订阅关系持久化,不会因实例故障丢失;负载均衡更均匀。
- 缺点:需要维护频道订阅逻辑,增加少量开发复杂度;依赖共享数据库的可用性(可通过主从集群或高可用部署解决)。
2. 粘性会话(Sticky Sessions)
通过负载均衡器将用户的SSE连接和订阅请求固定路由到同一个服务实例,让订阅关系可以安全存储在实例内存中。
实现方式
- 配置负载均衡器基于Cookie或客户端IP开启粘性会话,确保同一用户的所有请求都落到同一实例。
- 服务实例内存中维护
userId -> 订阅项目列表的映射,收到Pub/Sub消息时直接检查内存中的订阅关系再推送。
注意事项
- 必须处理实例故障场景:当实例下线,用户连接会切换到新实例,此时需要从持久化存储(如数据库)拉取用户的历史订阅关系,恢复内存映射,否则用户会丢失订阅。
- 负载均衡可能不均:如果某实例连接的高活跃用户过多,会导致实例压力集中。
优缺点
- 优点:无需额外依赖共享存储,架构简单,开发成本低。
- 缺点:服务实例有状态,扩容/缩容需要处理会话迁移;实例故障会导致用户连接中断、订阅关系临时丢失;负载均衡不均风险高。
3. 混合方案(粘性会话+集中存储备份)
结合前两种方案的优势:平时用粘性会话保持内存存储的高效,同时将订阅关系同步到共享存储做备份,兼顾性能和可靠性。
流程细节
- 用户订阅/取消时,同时更新实例内存和共享存储中的订阅关系。
- 当用户因实例故障切换到新实例时,新实例从共享存储拉取用户的订阅列表,恢复内存映射并订阅对应的频道。
- 正常情况下,实例直接用内存中的订阅关系处理推送,无需每次查询共享存储。
优缺点
- 优点:兼顾内存存储的低延迟和集中存储的可靠性;粘性会话减少共享存储查询次数。
- 缺点:需要维护内存与共享存储的同步逻辑,复杂度略高。
方案推荐
如果你的服务需要长期支持分布式扩容,优先选择集中式订阅存储方案,它是分布式实时系统的标准架构,能避免状态带来的各种问题,适合大规模用户场景。
如果是初期小规模部署,粘性会话可以快速落地,但建议同时做好订阅关系的持久化备份,避免实例故障导致的数据丢失。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

