应使用自定义event结构体替代裸interface{},通过sync.map管理按topic分组的chan event切片,发布时遍历对应topic所有channel并select非阻塞发送。

用 chan interface{} 做事件通道,但别直接塞任意类型
Go 里没有内置的 pub/sub 库,靠 chan interface{} 搭建最轻量,但直接传 interface{} 容易在消费端类型断言失败,panic 静默发生。
- 统一用自定义事件结构体,比如
type Event struct { Type string; Payload interface{} },避免裸interface{} - 发布方只往 channel 发
Event,订阅方按Type分流,再安全地 castPayload - channel 容量设为 1 或带缓冲(如
make(chan Event, 16)),防止发布者阻塞——尤其在 HTTP handler 里发事件时
多个订阅者怎么不互相干扰?用 sync.Map 管理 chan Event 切片
一个全局 channel 无法让多个 goroutine 各自独立收消息;用 map 存每个订阅者的专属 channel,是最常见且可控的做法。
- 用
sync.Map存map[string][]chan Event,key 是 topic 名,value 是订阅者 channel 列表 - 订阅时
append新 channel 到对应 topic 的切片;取消订阅要遍历并删掉对应 channel(注意 copy-on-write 和并发安全) - 发布时遍历该 topic 下所有 channel,用
select { case ch 非阻塞发送,避免某个慢消费者拖垮整体
为什么不能用 close(ch) 当取消信号?
close channel 后再读会立即返回零值 + ok==false,看似能当退出通知,但实际极易出错。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 多个 goroutine 同时
close同一个 channel 会 panic,而订阅者主动 close 自己的 channel 又和发布逻辑耦合太紧 - 更稳妥的是加一个单独的
done chan struct{},订阅者监听它来退出for range ch循环 - 务必在 defer 里关
done或显式发送,否则 goroutine 泄漏——这是最常被忽略的点
要不要用第三方库?github.com/ThreeDotsLabs/watermill 太重,github.com/bsm/watermill 已归档
真要上生产,简单场景手写够用;中等复杂度建议用 github.com/segmentio/kafka-go(仅限 Kafka 场景)或轻量替代 github.com/robfig/cron 不适用,但 github.com/antham/strum 这类纯内存事件总线可考虑。
- 如果已有 Redis,用
github.com/go-redis/redis/v9的Publish/Subscribe更可靠,天然支持多进程 - 纯内存方案永远要面对:进程重启后事件丢失、无持久化、无重试——这些不是 bug,是设计选择
- 测试时最容易漏掉“订阅后才发事件”的时序问题,务必用
time.Sleep或sync.WaitGroup控制节奏
事情说清了就结束。真正难的不是 channel 怎么发,而是谁负责关 channel、谁负责清理 map 里的 dead channel、以及下游处理失败时要不要重试——这些没标准答案,得看你的业务容忍度。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










