mercure默认即发即弃导致消息丢失,因其基于sse无内置存储,服务重启或客户端断连时未送达事件彻底消失;需业务层结合rabbitmq/redis stream等自行实现持久化与幂等推送。

FrankenPHP 的 Mercure 服务本身不自动持久化消息,重启后未被客户端接收的广播消息会丢失——这不是 bug,而是 Mercure 协议设计使然:它默认是“即发即弃”的发布/订阅模型,不保证消息可达性。
Mercure 的默认行为为什么会导致消息丢失
Mercure 的核心是 HTTP Server-Sent Events(SSE),它没有内置的消息队列或存储层。当 publisher 调用 POST /.well-known/mercure 发布更新时,FrankenPHP 会立即尝试将事件推送给当前已连接的 subscriber;如果客户端断连、网络中断,或服务重启,那些尚未送达的事件就彻底消失。
- Mercure hub(FrankenPHP 内置实现)不保存历史事件,也不维护 per-topic 消息 backlog
- 没有类似 RabbitMQ 的 durable queue 或 Redis Stream 那样的写入磁盘机制
- 重启 FrankenPHP 进程 = 清空所有活跃连接 + 丢弃所有待投递事件
需要持久化?取决于你的业务语义
不是“要不要”,而是“你能否接受丢失”。以下场景必须自行补充持久化:
- 订单状态变更通知(用户离线时不能收不到“已发货”)
- 协作文档的最终编辑记录(需确保所有成员最终看到一致版本)
- 审计日志广播(要求强可靠性,不能因服务抖动漏播)
而像实时打字提示、在线状态刷新这类弱一致性场景,原生 Mercure 就够用。
如何补上 Mercure 的持久化缺口
FrankenPHP 不提供开箱即用的持久化 Mercure,但你可以用标准组件低成本接入。关键路径是:publisher → 持久化中间件 → Mercure hub → subscriber。
- 用
RabbitMQ或Redis Stream先存消息,再由消费者异步调用 Mercure API 推送(注意避免重复推送,需幂等处理) - 在 Laravel/Symfony 中,可复用现有
Event+Queue机制:触发领域事件 → 推入redis队列 → worker 拉取后先写Stream,再发 Mercure - 若坚持纯 FrankenPHP,可借助其内置
FFI调用 Go/C 编写的轻量 WAL 日志模块,但开发成本高、维护负担重,不推荐
示例伪代码(Laravel + Redis Stream):
Redis::xAdd('mercure_events', '*', ['topic' => '/orders/123', 'data' => json_encode($payload)]);<br>// 后台 worker 拉取后执行:<br>Http::asJson()->post('http://localhost:8000/.well-known/mercure', $payload);
真正容易被忽略的一点:Mercure 的 retry 参数和 SSE 的 Last-Event-ID 仅对**重连瞬间的断连恢复**有效,它不解决服务重启导致的 gap。持久化必须落在应用层,且要明确谁负责“重放”、重放几次、是否跳过过期事件——这些逻辑无法交给 FrankenPHP 自动完成。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











