打开网易新闻 查看精彩图片

一个社区平台要搞实时聊天,传统做法是上WebSocket。但Minoo团队算过账:自研WebSocket网关,光心跳重连、断线恢复、水平扩展这三块,至少得埋3万行代码的坑。他们选了另一条路——用Mercure的SSE(服务器发送事件)方案,把整个实时层压到4层,核心代码不到400行。

第一层:发布器的"降级开关"

第一层:发布器的"降级开关"

Minoo的发布器藏在waaseyaa/mercure包里,构造函数只收两个参数:hub地址和JWT密钥。真正有意思的是isConfigured()这个检查——如果本地开发没起Docker,或者密钥配错了,publish()直接返回false,不抛异常、不崩流程。

这个设计省了大量环境判断的if-else。上线走Mercure,本地开发可以假装它不存在,业务代码完全无感知。

发布逻辑用curl直调Mercure hub,5秒超时,200-299算成功。没有重试、没有队列、没有熔断——因为SSE本身就是"尽力交付",丢一条消息比卡死整个请求更可控。Minoo的产品经理算过:聊天场景里,用户刷新一下页面就能补全历史,比搞一套可靠消息队列划算十倍。

第二层:话题设计的"房间钥匙"

第二层:话题设计的"房间钥匙"

消息存库之后,控制器会往/threads/{$threadId}这个话题发事件。前端用原生EventSource订阅,URL里带?topic=/threads/123参数,withCredentials打开就能自动带上cookie鉴权。

打开网易新闻 查看精彩图片

话题设计是这套系统的隐形门槛。Minoo选的是"一话题一房间"模型,而不是"一用户一频道"。区别在于:前者让浏览器只收当前打开的对话,后者会让客户端被海量未读消息淹没。一个中型社区,活跃用户可能同时开着3-5个线程,但"一用户一频道"方案会推送全部几十上百个会话的更新。

带宽账单会说话。Minoo的运维看过数据:按用户频道方案,单个活跃用户每小时可能收500次无效推送;按线程频道方案,降到15次以内。

JavaScript端零依赖,三行代码起连接:

const url = new URL(hubUrl, window.location.origin);url.searchParams.append('topic', `/threads/${threadId}`);const eventSource = new EventSource(url, { withCredentials: true });

收到消息后,前端自己决定是追加DOM还是弹通知。Mercure不管UI,只管"谁该收到什么"——这个边界切得干净,也是SSE比WebSocket省心的地方。

第三层:离线用户的"邮件兜底"

第三层:离线用户的"邮件兜底"

实时推送有个死穴:用户不在线怎么办?Minoo的解法很朴素——每4小时扫一次未读消息,打包发邮件摘要。没有复杂的"在线状态"跟踪,没有"已读回执"的分布式锁,就是定时任务+邮件模板。

打开网易新闻 查看精彩图片

这个设计被团队内部争议过。产品经理想要WhatsApp式的"双勾已读",技术负责人拍了板:社区平台的消息时效性没那么强,4小时延迟 acceptable,但复杂度要可控。最终数据证明,90%的活跃用户每天打开平台超过两次,邮件摘要的实际触发率不到7%。

换句话说,"伪实时"覆盖了绝大多数场景,而真正的实时留给在线用户。

用户屏蔽功能也在这层实现。被屏蔽用户的消息照常入库、照常推送到线程频道,但接收方的客户端会过滤掉。Minoo试过在发布层拦截,发现要维护一张全局屏蔽表,每次发布都要查——性能账算不过来。客户端过滤多耗点内存,但省了一次网络往返。

第四层:JWT的"最小权限"

第四层:JWT的"最小权限"

Mercure的鉴权靠JWT,但Minoo没有让用户端直接持票。所有订阅请求走平台后端代签,前端只拿到一个临时token,有效期绑在session上。这个设计挡住了两种攻击:一是用户伪造JWT订阅别人的私聊,二是token泄露后的长期风险。

JWT的payload里只放topic列表,不放用户ID或其他业务字段。Mercure hub只验证签名和过期时间,不解析业务语义——这个解耦让Minoo可以换鉴权方案而不动hub配置。

代码里有个细节:$this->mercurePublisher?->publish(...)用了PHP 8.0的空安全运算符。如果发布器没初始化,整个表达式返回null,不会抛Fatal Error。这种"静默失败"在实时系统里很关键:消息丢一条可以补,请求崩了用户要骂娘。

Minoo这套架构跑了18个月,峰值时同时在线8000人,Mercure hub的CPU占用没超过15%。团队最近在做一件事:把邮件摘要的4小时间隔改成用户可配置,测试数据里,23%的用户选了"关闭邮件",只留推送——这些人是真正的重度用户,他们的在线时长比普通用户高4倍。

如果让你选,实时消息的"必达性"和"复杂度"之间,你会把红线画在哪?