跳转到主内容
极星编程网:以代码为星,赴技术山海!

Go语言如何实现发布订阅_Go语言发布订阅模式教程【精讲】

Go原生无Pub/Sub组件,需用sync.Map+chan实现:须防goroutine泄漏、send on closed channel panic、并发写map;每个订阅者独占带缓冲chan,Subscribe返回取消函数,Publish用select非阻塞发送。 Go 原生没有发布订阅(Pub/Sub)内置组件,但用
sync.Map
+
chan
搭出来的轻量方案,在单机多协程场景下完全够用——前提是避开 goroutine 泄漏、
send on closed channel
panic、并发写 map 这三类高频翻车点。 为什么直接用
map[string][]chan interface{}
会 panic 普通
map
不支持并发读写。哪怕只是多个 goroutine 同时调用
Subscribe
,内部做
append
,就会触发
fatal error: concurrent map writes
。 必须用
sync.Map
存 topic → 订阅通道切片的映射,但它只保证键值对的原子存取,不保证对 value(比如切片)的并发安全 所以不能直接对
sync.Map
中取出的切片做
append
;得先
Load
出原切片,复制一份,
append
后再
Store
回去 更稳妥的做法是:每个 topic 对应一个带
sync.RWMutex
的封装结构,锁住切片本身,而不是依赖
sync.Map
的原子性 订阅者退出后,怎么防止
send on closed channel
这是最常被忽略的内存泄漏源头:订阅者 goroutine 结束了,但它的
chan
还挂在
sync.Map
里,发布者一发消息就 panic。 每个订阅者必须持有自己专属的
chan
(例如
make(chan Event, 16)
),不能复用同一通道
Subscribe
方法要返回一个取消函数,内部执行两件事:
close(ch)
+ 从 topic 的订阅列表中安全移除该
chan
别指望靠
defer close(ch)
自动清理——如果订阅者是 HTTP handler 启动的,handler 返回了,goroutine 却没结束,
ch
就一直开着 推荐绑定
context.Context
:订阅时传入
ctx
,在接收循环里监听
ctx.Done()
,收到信号就调用取消函数 发布时如何避免卡死或丢消息 发布者不能因为某个订阅者消费慢、channel 满、甚至已关闭,就被拖住或崩溃。 go语言参考手册 中文CHM版 Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译 下载 立即学习 “ go语言免费学习笔记(深入) ”; 遍历每个订阅
chan
时,必须用
select { case ch 非阻塞发送
不要用
go ch 异步发——这会产生大量无法回收的 goroutine,尤其在高频发布时
缓冲区大小要合理:太小(如
chan int
)容易满;太大(如
10000
)会掩盖背压问题,还浪费内存;常见取值是
16
或
64
如果业务要求“至少一次”,非阻塞失败时应记录丢弃数或打 warn 日志;若要求“至多一次”,静默跳过即可 要不要上
github.com/ThreeDotsLabs/watermill
或 Redis Pub/Sub watermill 是重型框架,适合 Kafka/RabbitMQ 场景;Redis Pub/Sub 天然支持多进程,但断连期间消息全丢。 纯内存方案只适用于单机、进程不重启、且能接受消息丢失的场景(比如配置热更新通知) 一旦需要跨实例、持久化、通配符订阅(如
logs.*
)、或服务重启后消息不丢,就别硬扛——直接换
github.com/go-redis/redis/v9
的
PubSubConn
或
nats.go
Redis Pub/Sub 不保证可达,适合实时通知;NATS JetStream 可做到 exactly-once,但需额外部署和运维成本 别用 SQLite 或文件模拟 broker:并发写、崩溃恢复、锁竞争全是坑,不是 bug,是设计越界 真正难的从来不是怎么把消息发出去,而是谁负责关 channel、谁负责清理 dead channel、下游处理失败时要不要重试——这些没有标准答案,得看你的业务能容忍什么。

相关文章