应默认用消息队列替代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 必须显式设,否则网络抖动时生产者会卡死别传 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"
}events.order.created.v1,避免消费者升级时解析失败json.Marshal,禁用 gob——跨语言、跨服务、未来加 Python 或 Node.js 消费者时你就谢天谢地了消息重复、乱序、延迟是常态,不是异常。指望队列“不重发”等于指望网络永不丢包。
立即学习“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 退出HTTP handler 里同步等 publisher.Publish() 返回?这是把异步变成伪同步,接口 P99 直接拉爆。
go publisher.Publish(ctx, msg) 启动 goroutine 发送,但必须配套错误兜底:发送失败时记录日志 +
写入本地 failed_messages 表,由后台定时任务重试req.Context() 给消息发送——请求结束了,context 就 cancel 了,发到一半被中断;应使用独立的 context.Background() 或带超时的 context.WithTimeout(context.Background(), 5*time.Second)
真正难的从来不是“怎么连上 RabbitMQ”,而是当某天凌晨三点告警说“死信队列积压 2 万条”,你能不能 5 分钟内定位是幂等漏判、ACK 忘写,还是消费者 panic 后静默退出——这些细节,全藏在每一条 msg.Ack() 的位置和每一处 if err != nil 的处理里。