商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 实战:使用 Golang 构建一个简单的发布/订阅模式

实战:使用 Golang 构建一个简单的发布/订阅模式

  发布于2026-07-10 阅读(0)

扫一扫,手机访问

先梳理几个关键点:sync.Map 最适合读多写少、键动态增删的高并发场景。无锁读的设计让它性能更稳,但有两个坑要注意——遍历时不能直接删除,得先拷贝键列表再广播;取消订阅必须用 LoadAndDelete,而发布广播则推荐通过带缓冲的 channel 做异步解耦。

实战:使用 Golang 构建一个简单的发布/订阅模式

为什么直接用 sync.Map 而不是 map + sync.RWMutex

原因很简单:订阅者列表在实际运行中会频繁增删,而典型的场景是读多写少。sync.Map 在并发读写时避免了全局锁的争用,性能自然更稳。但有个代价——它不支持在遍历中删除。所以广播消息时必须先拷贝键列表,再逐个取值调用回调,否则要么 panic,要么漏通知。

常见的错误现象是 fatal error: concurrent map iteration and map write。说到底就是没做键拷贝,直接一边遍历一边从 sync.Map 里删订阅者——比如取消订阅逻辑和广播混在一起触发。

  • 订阅时用 Store(subID, callback)
  • 广播前用 Range 遍历并 append 到临时 slice,不操作原 map
  • 取消订阅必须用 LoadAndDelete,不能只 Load 后手动删——这是竞态的最常见源头

如何安全地在 goroutine 中广播消息而不阻塞发布者?

发布者调用 Publish 时,绝不能被某个慢订阅者拖住。常见做法是把消息分发逻辑扔进新 goroutine,但必须控制并发量,否则海量消息可能直接把内存撑爆。

用带缓冲的 channel 做轻量级异步队列是最稳妥的方案:启动一个长期运行的 dispatcher goroutine,从 channel 拉消息、执行广播;发布者只负责往 channel 写,写满就非阻塞丢弃或返回错误——具体选哪种取决于业务容忍度。

  • 缓冲区大小建议设在 100~1000,太小易阻塞,太大易积压
  • dispatcher 里对每个订阅者调用 callback 前加 select { case... },支持整体超时/取消
  • 不要用 go f(msg) 每次都起 goroutine——无节制 spawn 是 goroutine 泄漏的主因

Subscribe 返回的 unsubscribe 函数为什么必须是闭包?

因为要捕获当时生成的唯一 subID,并在内部调用 subs.LoadAndDelete(subID)。如果只是返回一个固定的函数,那所有取消操作都会删错 key。

这里有个容易踩的坑:用 defer unsubscribe() 在 handler 里注册取消,结果 handler 还没执行完就提前解除了订阅——比如 HTTP handler 中 defer 写在开头,但实际业务逻辑需要持续接收后续消息。

  • 正确模式:subID := uuid.NewString() + subs.Store(subID, cb) + 返回 func(){ subs.LoadAndDelete(subID) }
  • 测试时可以让 callback 故意 sleep 3s,验证多次 unsubscribe() 调用是否只生效一次
  • 别把 unsubscribe 存到 struct 字段里复用——不同订阅实例必须隔离 subID

怎么处理订阅者 panic 导致整个广播流程中断?

一个订阅者 callback 发生 panic,若不做 recover,dispatcher goroutine 会直接退出,后续消息全丢。所以必须在单次回调调用处兜底。

不能在 dispatcher 外层用 recover——那只能救一次;必须在每次 cb(msg) 前加 defer func(){ recover() }(),确保单个失败不影响其他订阅者。

  • 建议封装成工具函数:safeCall(cb, msg),内部做 recover 并 log 错误栈
  • panic 日志里至少带上 subIDmsg 的类型/长度,方便定位是哪个订阅者写的烂代码
  • 别忽略 recover 后的错误——至少打 warn 级日志,否则问题静默发生

实际跑起来你会发现,最难的不是实现 publish/subscribe 接口,而是当几十个服务同时订阅、其中几个 callback 偶尔卡死或 panic 时,系统能否持续吞吐且不雪崩。这些细节不提前想清楚,上线后查问题的时间远大于写代码的时间。

本文转载于:https://www.php.cn/faq/2393006.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注