联犀WebSocket实时推送架构升级:从订阅键到频道模型
约 3556 字大约 12 分钟
2026-05-16
很多系统在做 WebSocket 实时推送时,前期都能跑得很快。连上、发出、收到,几乎第一版就能工作。但这类系统真正的难点从来不在“把连接建起来”,而在于系统规模上来之后,如何组织订阅、如何分发权限、如何表达多维度消息,以及如何在多节点环境中继续保持结构清晰。联犀这次从订阅键升级到频道模型,解决的正是这些在早期实现里常常被暂时绕过、但迟早会正面碰上的问题。
如果把这次升级的核心结论先说出来,那就是:当实时推送开始承载设备、项目、区域、用户、租户等多个业务维度时,基于哈希订阅键的设计会越来越难演进,而频道模型才更适合作为长期架构基础。因为它解决的不是某一个性能瓶颈,而是整个推送系统的表达能力问题。
WebSocket 架构为什么经常在“能用之后”开始失控
老式 WebSocket 系统之所以常常在后期变复杂,不是因为开发者不会写连接管理,而是因为早期设计通常默认了一个过于简单的前提:订阅只是客户端发几个 key,服务端按 key 把消息转发出去。这种前提在单一场景里完全成立,但一旦系统开始面对多维度订阅,就会迅速暴露出三个问题。
第一个问题是订阅表达贫乏。联犀旧方案里,订阅更接近“某种 code 加上一组参数的哈希键”。这种方式的优势是实现直接,但代价也明显。订阅 key 对人不友好,调试时难以从字符串直接看出它代表什么;更重要的是,它很难自然表达层级和范围。订阅单个设备、整个项目、某个区域下所有设备,本质上是不同粒度的同一类关注关系,但在哈希键模型里,它们只会退化成一批彼此独立的 key。
第二个问题是发布方负担越来越重。每当一个设备事件要同时被设备级、项目级、区域级的订阅者感知时,发布逻辑就不得不手工列出所有可能维度。一次属性上报看似只是一条消息,背后却要在业务代码里显式写出多个 params 组合。短期看这只是“多写几行”,长期看则意味着每新增一种订阅粒度,所有发布点都要一起补改,遗漏的风险也会持续放大。
第三个问题是鉴权与订阅模型互相牵制。旧模型基于 {code, params} 做权限判断时,很多业务模块其实已经形成了一套可用逻辑,但一旦想引入更清晰的订阅表达方式,就会发现原有鉴权接口与新模型之间没有天然映射。结果往往是订阅表达、鉴权逻辑和消息发布各自发展,最后只能靠转换层不断缝合。
从订阅键到频道模型,本质上是在重建订阅语言
联犀这次升级最值得关注的地方,不是换了几个核心结构体,而是重新定义了“系统如何描述一个订阅”。新模型采用点分层级频道,把原本隐含在 code 和 params 里的业务语义,变成了显式的命名空间和层级段。例如设备属性、项目聚合、区域聚合、用户通知、系统告警,不再只是不同类型的哈希 key,而是具有清晰结构的频道路径。
这种变化看上去像是命名规范调整,实际上意义很大。因为一旦订阅表达从“计算出的键”变成“可理解的频道”,整个系统的很多能力就会随之出现。首先是可读性显著提升。无论是开发者调试、运维排查还是前端联调,都可以直接从频道字符串理解它代表的业务含义。其次是层级语义终于可以被系统原生理解。设备、项目、区域不再只是平铺的不同 case,而是一个天然适合表达聚合关系的频道树。
更重要的是,频道模型让通配符匹配成为一种自然能力,而不是额外拼接逻辑。订阅单个设备的全部属性、某个产品下所有设备的事件、某个项目下所有连接状态,背后其实都是对层级路径做不同粒度的匹配。用 Trie 树处理这类问题,比在一堆哈希键上做逆向推导要合理得多。也正因为如此,联犀把旧的基于 map 的订阅索引升级成了 SubTrie,这不是为了“换个数据结构试试”,而是因为订阅模型本身已经从平面变成了树形。
Hub、Conn、SubTrie、Publisher 分别解决了不同层面的复杂度
频道模型成立之后,连接管理、订阅路由和消息发布才能真正解耦。联犀新架构里,Hub 负责中心化管理连接与订阅分发,Conn 负责单连接生命周期,SubTrie 负责频道匹配,Publisher 则负责业务层消息发布。这组拆分看起来常规,但它的重要性在于:每个组件终于围绕一种清晰职责工作,而不是都被旧订阅键模型拖着走。
Hub 的价值在于把“谁在线、谁订阅了什么、消息该发给谁”从业务逻辑中完全剥离。随着连接数上升,管理问题会迅速从业务问题变成系统问题,比如锁竞争、慢连接拖垮整体、同用户多端登录互相覆盖等。联犀在这里采用分片连接池和更轻量的连接封装,本质上是在把连接管理当作基础设施重新做一遍,而不是继续堆在旧 dispatcher 上修修补补。
SubTrie 的价值则更偏语义层。它让频道匹配的复杂度从“我得枚举所有订阅看能不能对上”转成“我沿着频道层级做匹配遍历”。只要订阅表达是分层的,Trie 就几乎是最自然的结构。更重要的是,它让 *、> 这类通配符不再只是前端协议里看起来好看的功能,而是真正由底层路由结构支持的能力。
Publisher 则把另一个长期被低估的问题拉到了显性层:发布接口到底应该对业务代码暴露什么抽象。旧模式下,业务侧往往需要自己知道哪些 params 组合对应哪些订阅维度。新架构不再要求业务层枚举这些组合,而是允许业务代码只声明主频道,再通过 ChannelExpander 自动派生项目级、区域级等关联频道。这个变化对长期维护非常关键,因为它把“消息需要被哪些粒度消费”的知识,从业务发布点收回到了平台发布层。
频道模型真正释放价值的地方,在于多维度聚合
WebSocket 实时推送在 IoT 场景下最容易变复杂的地方,是同一条消息往往天然对应多个观察视角。设备属性变化,对设备详情页来说是单设备事件,对项目看板来说是项目级聚合事件,对区域监控来说又是区域级事件。如果系统没有一个能表达层级关系的订阅模型,这些消费视角就只能由业务代码手工拼接。
联犀引入 ChannelExpander 的意义正在这里。业务服务只需要围绕自身最原始的业务对象发布,例如设备级频道,系统再根据上下文自动推导出项目级和区域级派生频道。这样做不是为了少写几行代码,而是为了把“多维度分发”从业务职责变成平台职责。只要设备归属、项目关系、区域关系这些上下文能够被扩展器获取,任何新增发布点都能在不重复枚举的前提下获得一致的分发效果。
这一点往往比性能优化更重要。因为手工列举多维度订阅的真正问题,不是代码长,而是容易不一致。今天设备属性上报记得同时发项目和区域,明天设备事件上报漏掉区域,后天某个新模块又自己发明一套组合方式。系统最后表现出来的不是单点 bug,而是整体推送语义越来越不可预测。频道模型加展开器,本质上是在消灭这类语义漂移。
鉴权必须跟着订阅模型一起升级,但不能破坏模块边界
很多实时推送系统在做订阅升级时,最难处理的不是路由,而是权限。因为旧系统通常已经积累了与业务强相关的鉴权逻辑,而新订阅模型一旦变化,权限入口也必须跟着变化。联犀在这里的处理方式很有代表性:不强行把所有订阅权限逻辑都吸进 core,而是按频道命名空间分发鉴权责任。
对于用户、租户、系统这类可以直接从 UserCtx 推导的频道,Hub 内置鉴权就足够了;对于设备、项目、区域这类依赖业务域知识的频道,则通过适配层继续走既有的扩展调用机制,让具备领域模型的服务来解释权限。这种做法表面上看是“保守”,实际上非常务实。因为实时推送平台的职责不是重新发明所有业务权限,而是为不同业务域提供统一订阅入口和一致的调用协议。
这也是这套架构里很值得强调的一点:频道模型升级并不意味着所有旧能力都要推倒重写。真正好的架构演进,通常不是彻底否定原有体系,而是重新组织原有能力的边界。联犀在 WebSocket 鉴权里保留了原来跨服务解耦的思路,只是把它从旧订阅键世界平移到了频道命名空间世界。这样做既避免了 core 反向依赖业务模块,也让新订阅模型能够平稳承接既有权限资产。
自动订阅说明,服务端终于开始理解“默认该听什么”
频道模型还有一个很自然的延伸能力,就是服务端自动订阅。旧模式下,前端通常需要在连接建立后再逐条发送订阅请求,这不仅带来页面侧管理负担,也会产生一个短暂但真实存在的空窗期:连接已建立,但还没完成订阅,期间的消息可能直接错过。对于通知、告警、个人频道这类本就应该默认收到的消息,这种模式并不理想。
自动订阅能力的真正价值,不只是省掉前端几行代码,而是让订阅关系的一部分从“页面状态”回到“身份状态”。也就是说,哪些频道应该由当前用户默认感知,不再完全由前端临时决定,而是可以由服务端基于用户身份、角色、租户和项目上下文直接给出。这样做之后,实时系统开始具备一种更强的平台视角:它知道某类用户天然应当在线接收哪些信息。
这类设计在多端、多页面和复杂权限场景里尤其有用。因为前端页面擅长描述局部视图,不擅长长期维护全局订阅语义。服务端自动订阅等于把那些稳定、通用、基于身份的订阅收回平台,前端则只保留真正与当前界面强相关的局部订阅。
这次升级解决的,归根到底是演进问题
如果只从“连接能不能扛住更多并发”来理解这次升级,结论会过于狭窄。联犀这套新架构当然包含了连接池分片、有界缓冲、非阻塞发送、多节点广播等工程优化,但这些优化真正成立的前提,是系统先拥有了更合理的订阅抽象。没有频道模型,发布层、鉴权层和订阅路由层就不会形成清晰边界,再多工程优化也只是维持旧模型继续运行。
从订阅键到频道模型,本质上是把 WebSocket 从“一个可用的长连接组件”升级成“一个可持续演进的实时推送平台”。前者关注的是连接是否打通,后者关注的是当业务维度持续扩张时,系统是否还能用统一方式表达订阅、统一方式做权限收口、统一方式完成多节点分发。
这也是这篇文章最想强调的设计经验。实时系统最容易被误判的地方,是大家常常把问题看成网络问题或性能问题,而忽略它首先是一个建模问题。只要订阅模型不稳,后面的连接管理、鉴权分发和消息路由都会越来越重。把订阅语言重建好,系统很多复杂度才有机会被正确安放。联犀这次升级真正换掉的,不只是几个内部组件,而是整个实时推送系统的思考方式。
更新日志
2026/5/18 10:48
查看所有更新日志
43ef3-docs(blog): 新增后端与架构系列技术博客 18 篇,更新原有 7 篇于
