不能用make(chan interface{}, 0)做全局事件通道,因其导致类型不安全、并发写map崩溃、单点阻塞拖垮全局;应封装map[string][]chan *event,配sync.rwmutex保护、带缓冲typed channel及非阻塞select分发。

Go 语言没有内置事件总线,用 chan interface{} 直接广播事件,90% 的项目会在上线后两周内遇到阻塞、panic 或 goroutine 泄漏——不是写法错,而是它根本不是为这种场景设计的。
为什么不能用 make(chan interface{}, 0) 做全局事件通道
这是最常被复制粘贴的“快速开始”写法,但立刻踩中三个硬伤:
-
interface{}擦除类型,type switch漏分支或写错字段名,运行时 panic 不报堆栈来源,调试像盲人摸象 - 多个 goroutine 并发往同一个 map 写 handler(比如按
event.Type分发),触发fatal error: concurrent map writes - 某个 handler 处理慢或 panic,整个
for range events循环就卡死或退出,其余订阅者全部失联
怎么用 map[string][]chan *Event 安全分发
真正可控的做法是把 topic 当 key,每个 topic 对应一组带缓冲的 typed channel:
- 定义具体事件结构体,如
type OrderCreatedEvent struct { OrderID int64; UserID int64 },字段全导出、值类型优先 - 总线内部用
sync.RWMutex保护subscribers map[string][]chan *Event,注册/注销必须加锁 - 发布时遍历对应 topic 的所有
chan,每条都用select { case ch 防阻塞 - 每个
chan必须带缓冲,make(chan *Event, 16)是较稳妥起点;别设 0(易阻塞)也别设 1024(内存堆积)
如何防止 goroutine 和 channel 泄漏
泄漏不是“没关 channel”那么简单,而是“没人通知它该停了”:
- handler 启动的 goroutine 持有
chan引用,但总线注销时只从 map 删除 key,没close(ch) - 在 HTTP handler 里直接
go handle(event),却没传context.Context,请求结束、context cancel后 goroutine 还在跑 - 用
time.Ticker触发事件,但没调用ticker.Stop(),它不会随context自动停止 - 订阅方法必须返回
func()取消函数,且内部要:① 从 map 删除 channel、②close(ch)、③ 清理关联资源(如*http.Client)
什么时候该放弃自己写,换 Kafka / Redis
当出现以下任一信号,说明你已超出 channel 的能力边界:
- 需要保证事件顺序(
channel无法跨 goroutine 保序,尤其多消费者时) - 要求失败重试、死信队列、消费确认(ACK),而你不想在内存里手写重试逻辑和持久化队列
- 事件要跨进程、跨机器分发(
channel是纯内存的,连 goroutine 都跨不了) - 已有 Kafka 或 Redis,直接用
github.com/segmentio/kafka-go或github.com/go-redis/redis/v9更可靠
真正难的不是写出能跑的事件循环,而是想清楚:这个事件要不要保证顺序?失败了重不重试?上下游是否异构?这些决定了你该用 channel、还是 redis pub/sub、还是云服务的 event bridge。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











