Go中Pub/Sub模式通过接口抽象行为,用map[string][]chan interface{}管理主题与订阅通道,各订阅者独占channel实现并发安全,并提供Subscribe等方法。

在 Go 语言中实现发布-订阅(Pub/Sub)模式,核心是用接口抽象消息的发布与订阅行为,借助通道(channel)和 goroutine 实现异步解耦,避免对象间直接依赖。
定义事件总线接口与基础结构
先设计一个轻量、可扩展的 EventBus 接口,隐藏内部实现细节:
- 使用 map[string][]chan interface{} 存储主题(topic)到订阅者通道的映射
- 每个订阅者通过独立 channel 接收消息,天然支持并发安全(只要不共享同一 channel)
- 提供 Subscribe(topic string, ch chan 和 Publish(topic string, msg interface{}) 方法
用 goroutine 安全分发消息
发布时不应阻塞调用方,尤其当某个订阅者 channel 满或未及时读取时。推荐做法是启动 goroutine 异步发送,并配合 select + default 避免阻塞:
- 对每个订阅 channel,用 select { case ch 尝试非阻塞投递
- 可选添加日志或丢弃计数,便于监控“背压”情况
- 避免直接 ch —— 一旦某个订阅者卡住,整个 Publish 就被拖慢
支持取消订阅与资源清理
长期运行的服务需防止内存泄漏。可通过返回取消函数(cancel func())来移除 channel:
立即学习“go语言免费学习笔记(深入)”;
- Subscribe 返回一个 func(),内部从 map 中删除对应 channel
- channel 本身无需关闭(除非明确语义要求),但应确保不再写入
- 若用 sync.Map 替代普通 map,可提升高并发读写性能
实际使用示例:用户注册后通知多模块
比如用户服务发布 "user.registered" 事件,邮件、积分、风控模块各自订阅:
- 邮件模块开一个 mailCh := make(chan interface{}, 10),启动 goroutine 消费并发送欢迎信
- 积分模块同理监听同一 topic,给新用户加 100 分
- 所有模块只依赖 EventBus 接口,彼此完全隔离,增删模块不影响其他逻辑
不复杂但容易忽略:真正的解耦不只靠 channel,更在于定义清晰的主题命名规范(如 "domain.action")、错误处理策略(消息丢失是否可接受)、以及是否需要持久化或重试——这些决定了你的 Pub/Sub 是玩具还是生产级。










