应默认用消息队列替代HTTP/gRPC调用;RabbitMQ适合强可靠场景,NATS JetStream适配Go生态高频事件,Kafka适用于高吞吐日志场景;事件需结构体+JSON+Version定义,主题带领域和版本;消费者须幂等、ACK后置、context超时;生产端异步发送并错误兜底。

用消息队列替代 HTTP/gRPC 调用,是 Golang 微服务实现异步通信最可靠、最主流的方式——不是“可以选”,而是“应该默认这么做”。
选哪个消息队列?看场景,别堆配置
选错中间件,后期改起来比重写还疼。RabbitMQ、NATS JetStream、Kafka 并非性能排行榜,而是分工明确的工具:
-
RabbitMQ:适合需要强可靠性、死信队列(DLX)、延迟消息或复杂 Exchange 路由的业务,比如订单创建后必须通知库存、风控、积分三个系统,且任一失败不能丢消息;streadway/amqp客户端成熟,但注意Connection和Channel的复用,别每次发消息都新建 -
NATS JetStream:Go 生态亲和力最强,部署轻、延迟低、API 简洁;nats.go一行连 JetStream,PullSubscribe自带重试与确认语义;适合中等规模微服务内部高频事件(如用户登录成功广播、配置变更通知) -
Kafka:吞吐高、日志式存储、支持事件溯源;但单条消息延迟偏高,小服务用它容易“杀鸡用牛刀”;segmentio/kafka-go是当前最稳的 Go 客户端,注意WriteTimeout和ReadTimeout必须显式设,否则网络抖动时生产者会卡死
消息体怎么定义?结构体 + JSON + Version 字段是底线
别传 map[string]interface{} 或裸字符串——那是给调试留的坑,不是给生产环境用的。
- 所有事件必须用
struct显式定义,带json:标签和必要注释,例如:type OrderCreatedEvent struct { OrderID string `json:"order_id"` UserID string `json:"user_id"` Total float64 `json:"total"` Timestamp int64 `json:"timestamp"` Version string `json:"version"` // 必加,如 "v1" } - 主题(topic/subject)命名要带领域和版本:
events.order.created.v1,避免消费者升级时解析失败 - 序列化只用
json.Marshal,禁用gob——跨语言、跨服务、未来加 Python 或 Node.js 消费者时你就谢天谢地了
消费者怎么写才不翻车?幂等 + ACK + context 超时三件套
消息重复、乱序、延迟是常态,不是异常。指望队列“不重发”等于指望网络永不丢包。
立即学习“go语言免费学习笔记(深入)”;
- 消费逻辑开头必须做幂等校验:用
order_id查数据库是否已处理,或用redis.SetNX("processed:order_123", "1", time.Hour)记录痕迹 -
msg.Ack()(NATS)或delivery.Ack(false)(RabbitMQ)必须在业务逻辑**完全成功后**才调,早了等于告诉队列“我干完了”,其实没干 - 每个消费任务必须绑定
context.WithTimeout(ctx, 30*time.Second),防止某条消息卡住整个 goroutine;同时用defer recover()捕获 panic,否则一个 panic 就会让整个消费者 goroutine 退出
生产端怎么发才不阻塞主流程?Fire-and-forget 不等于放任不管
HTTP handler 里同步等 publisher.Publish() 返回?这是把异步变成伪同步,接口 P99 直接拉爆。
- 一律用
go publisher.Publish(ctx, msg)启动 goroutine 发送,但必须配套错误兜底:发送失败时记录日志 + 写入本地failed_messages表,由后台定时任务重试 - 别在 handler 里传
req.Context()给消息发送——请求结束了,context 就 cancel 了,发到一半被中断;应使用独立的context.Background()或带超时的context.WithTimeout(context.Background(), 5*time.Second) - 如果业务强依赖“至少一次送达”,就别用纯 fire-and-forget;得加本地落库 + 最终一致性校验,比如订单服务发完消息后,定期扫描“已创建未确认通知”的订单并补发
真正难的从来不是“怎么连上 RabbitMQ”,而是当某天凌晨三点告警说“死信队列积压 2 万条”,你能不能 5 分钟内定位是幂等漏判、ACK 忘写,还是消费者 panic 后静默退出——这些细节,全藏在每一条 msg.Ack() 的位置和每一处 if err != nil 的处理里。










