nats 使用核心是 nats.connect()、nc.publish()、nc.subscribe() 三步,连接必须设超时与重连策略,主题大小写敏感且通配符规则严格,订阅需显式调用,消息可靠性依赖 jetstream 和手动 ack,reply 字段是 request/reply 模式关键。

不用“构建 NatsMessage”——Go 里根本没有这个类型;你真正要写的是 nats.Msg 结构体的使用逻辑,核心就三件事:nats.Connect() 连上、nc.Publish() 发出去、nc.Subscribe() 收回来。其他所有“轻量级”“极速”“微服务”都建立在这三个动作不出错的基础上。
连接 NATS 时必须显式设超时和重连,否则一卡就是永远
本地跑 nats://localhost:4222 能通,不代表上线能活。默认 nats.Connect() 不设超时、不控重试,遇到 DNS 慢、防火墙拦截、服务未就绪,会无限 hang 住,直到进程被 kill。
-
nats.Timeout(5 * time.Second):单次连接最多等 5 秒,超时直接报错,可快速失败并触发告警 -
nats.MaxReconnects(-1):-1 表示无限重试(别信默认值 60,它会在第 61 次放弃) -
nats.ReconnectWait(2 * time.Second):每次重连前等 2 秒,避免打爆 server - 集群地址必须写成逗号分隔:
"nats://n1:4222,nats://n2:4222",客户端自动轮询剔除坏节点 - 生产环境必须加认证:
nats.UserCredentials("nats.creds")或nats.Token("xxx"),否则日志刷满Authorization Violation
订阅消息收不到?不是没连上,是根本没调 Subscribe()
NATS 连接成功 ≠ 自动监听任何主题。漏掉 nc.Subscribe("order.created", handler) 这一行,服务会静默运行、无报错、但永远收不到事件——这是最常被忽略的“假正常”。
- 主题名大小写敏感,
"Order.Created"和"order.created"是两个不同 subject - 通配符规则严格:
*匹配单段(如order.*.processed→order.v1.processed),>只能放末尾(如order.>→order.v1.processed、order.v2.cancelled) - 回调函数里别做阻塞操作(如同步 HTTP 请求),否则卡死整个 subscription 的 reader goroutine
- 需要多实例负载均衡?用
nc.QueueSubscribe("order.created", "processor-group", handler),NATS 自动去重分发
消息总丢?默认是纯内存广播,想“不丢”就得开 JetStream
NATS 默认行为就是“即发即弃”:消费者掉线期间发布的消息直接丢弃。这不是 bug,是设计。想保证至少一次投递(at-least-once),必须启用 JetStream 并配对策略。
- 连接后立刻建 JetStream 上下文:
js, err := jetstream.NewContext(nc),别等到js.Publish()才初始化 - 订阅时必须指定投递策略:
js.Subscribe("order.created", handler, nats.DeliverPolicy(nats.DeliverAll)),否则重启后收不到断连期间的消息 - 必须配
nats.ManualAck()+ 显式调msg.Ack(),漏掉就等于没开持久化——即使写了DeliverAll,消息也会被立即标记为已处理 - 重复消息不可避免,业务层必须做幂等:
order_id去重 + 数据库唯一索引是最小可行方案
nats.Msg 里 Reply 字段不是摆设,request/reply 模式全靠它
很多人只把 *nats.Msg 当作数据容器,却忽略 Reply 字段——它是 NATS request/reply 模式的基础设施。没它,你就没法用 nc.Request() 做同步调用。
-
msg.Reply是 server 自动生成的临时 subject,用于把响应送回请求方 - 手动 publish 响应时,必须用
nc.Publish(msg.Reply, data),不能硬编码 subject - 如果你封装了自定义结构体,字段映射必须包含
Reply string `json:"reply"`,否则 request/reply 链路断裂 - JetStream 下的 request/reply 仍走原生 reply subject,不是流 ID,别混淆
最容易被忽略的其实是 Reply 字段的生命周期——它只在当前消息上下文中有效,且 server 不持久化。一旦你把它存进数据库或跨 goroutine 传递,大概率会因超时或 subject 失效导致响应丢失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











