watermill 必须通过 message.newrouter 统一调度消息流,禁用手写 for range 消费循环;handlerfunc 需经 router.addhandler 注册以启用中间件、隔离 panic、保障生命周期管理,并严格遵循返回值语义控制消息确认与发布。

Watermill 不是“注册个 Publisher 就能发事件”的轻量库,它强制你用 message.NewRouter 统一调度消息流;跳过 Router 直接 subscriber.Subscribe + for range,上线后必然出现 goroutine 泄漏、panic 全局中断、中间件失效等问题。
必须用 Router.AddHandler 注册 HandlerFunc,不能手写消费循环
很多人抄示例里 subscriber.Subscribe("events") 后直接 for msg := range messages,这在本地跑得通,但违背 Watermill 的设计契约:
- 每个
for range是独立 goroutine,topic 多了就失控;Router内部用 channel 复用 +sync.WaitGroup管理生命周期,更安全 - 中间件(如
RetryMiddleware、TracingMiddleware)只对AddHandler生效,手写循环完全绕过 - 一个 handler panic 会导致整个
for range退出,而Router中每个 handler 隔离运行,挂掉不影响其他 topic -
HandlerFunc签名固定为func(*Message) ([]*Message, error),返回nil, nil表示仅消费,返回[]*Message, nil才触发下游发布
Kafka/RabbitMQ 消息卡住或重复消费?检查 ConsumerGroup 和 Ack 逻辑
Watermill 把“何时算消费成功”完全交给底层驱动和你的 handler 返回值,常见症状和解法:
-
context deadline exceeded错误 + 消息反复重试 → handler 执行超时触发 Kafka rebalance;解决:用ctx, cancel := context.WithTimeout(msg.Context(), 5*time.Second)包裹业务逻辑,并确保该 timeout 小于 Kafka 的session.timeout.ms - 所有实例都收到同一条消息 → 没设
ConsumerGroup,Kafka 认为每个 subscriber 是独立消费者;必须显式配置group.id(Kafka)或queue(AMQP) - 消息处理完却持续重试 → handler 返回了
error或 panic;记住:msg.Ack()只在 handler 返回nil时自动调用;若启用ManualAck模式,必须手动调msg.Ack(),否则 offset 永远不提交
防丢消息不能靠 Publisher 配置,必须结合 Outbox 模式
kafka.NewPublisher 只是发送客户端,和数据库事务完全无关。想保证“DB 写入成功 + 消息发出”原子性,必须自己实现 Outbox:
- 在事务内把事件写入
outbox_events表(和业务表同库) - 另起 goroutine 轮询该表,用
Publisher.Publish发送,成功后标记已投递 - Watermill 本身不提供 Outbox 实现,需自行封装或集成第三方库(如
watermill-sql) - 别依赖
Publisher的RequiredAcks或MaxRetries——它们只管网络层可靠性,不管业务一致性
Router.CloseTimeout 影响服务平滑下线,别用默认值硬扛
Router 关闭时会等待正在运行的 handler 完成,超时则强制终止。默认 CloseTimeout = 30s,但实际业务中容易踩坑:
- handler 里有长耗时 IO(如调外部 HTTP、生成大文件),30s 不够用,导致进程被 kill,消息丢失
- handler 里用了
time.Sleep或阻塞 channel 操作,会卡死关闭流程 - 应根据最长可能 handler 执行时间设置
CloseTimeout,例如:router.Config.CloseTimeout = 5 * time.Minute - 同时在 handler 内部用
select { case 响应取消信号,避免无限等待
Router 是 Watermill 的唯一正统入口,所有可靠性机制(重试、隔离、生命周期、中间件)都建立在它之上;试图绕过它做“简单封装”,最终都会在并发量上来时暴露设计债务。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











