生产环境 NATS 连接需显式配置重连、凭证和 TLS,JetStream 必须连接后立即初始化并校验;流创建、MsgID、Durable 名称、DeliverPolicy 四者必须严格对齐,否则导致消息丢失、重复或乱序。

裸连 nats.Connect(nats.DefaultURL) 在本地能跑通,上生产就断连不恢复、消息收不到、重复消费、顺序错乱——问题不在 NATS 本身,而在 Go 客户端初始化和 JetStream 使用方式没对齐。
生产环境连接必须显式配重连和凭证
默认 nats.Connect() 不开重试抖动、不加载凭证文件、不校验 TLS,网络抖动时直接卡死或静默失败。
-
nats.MaxReconnects(60):设为有限值,避免无限重试压垮服务端 -
nats.ReconnectWait(2*time.Second)+nats.ReconnectJitter(100*time.Millisecond, time.Second):加随机抖动,防雪崩重连 - 密码绝不能写进 URL(如
"nats://user:pass@host:4222"),改用nats.UserCredentials("user.creds") - 启用了 TLS 就必须用
"tls://"前缀,且服务端证书要被系统信任,否则Connect()会卡住不报错
JetStream 初始化必须在 connect 后立刻做
nc.JetStream() 不是 lazy init,不调就不会准备上下文;等第一次 js.Publish() 才触发,会 panic:"nil pointer dereference" 或 "JetStream not enabled on server",堆栈里看不到根源。
- 连接成功后立即执行:
js, err := nc.JetStream(),并检查err - 建议加健康检查:
js.AccountInfo()确认 JetStream 可用,而不是等到业务 publish 才暴露问题 - 流(Stream)创建是一次性动作:
js.AddStream()必须显式调用,改配置得删了重建,不支持热更新
消息“丢了”或“收不到旧消息”,90% 是流和订阅策略没配对
纯 NATS 是内存转发,消费者离线 = 消息丢弃。JetStream 不是自动开启的“开关”,而是需要三者协同:流定义、发布方式、订阅参数。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 流必须指定
RetentionPolicy:jetstream.InterestPolicy(只存活跃订阅者关心的)或jetstream.WorkQueuePolicy(每条只投一次) - 订阅必须带
nats.Durable("my-durable"),否则 offset 不保留,每次都是新消费者 - 想从头消费,得用
nats.DeliverPolicy(nats.DeliverAll);用DeliverLastPerSubject会跳过中间所有事件 - 开发验证可用
StorageType: jetstream.MemoryStorage,但服务器重启即清空,切勿用于生产
去重和顺序不是 JetStream “自动保障”的能力
服务端只提供去重窗口和有序投递的基础设施,关键逻辑全在客户端:ID 要业务唯一、发布要串行、耗时不能超窗口。
- 去重靠
nats.WithMsgID("order-20260406-001")—— ID 必须是业务主键(如 order_id),uuid.New()无效 -
Duplicates: 2*time.Minute是去重窗口,不是消息保留时间;若单条处理耗时 >2 分钟,仍会重复 - 并发
js.Publish()同一 subject,顺序无法保证;强序场景必须单 goroutine + channel 缓冲,或加业务序列号 + 启用AllowDirect配合确认 - subject 通配符写错也会“收不到”:
*匹配单层,>匹配多层,"orders.*.created"收不到"orders.us.created",但"orders.>"可以
最常被忽略的是:JetStream 的流定义、客户端发布的 MsgID、订阅时的 DeliverPolicy 和 Durable 名称,这四个点只要一个没对齐,就会表现为“消息丢了”或“重复了”,而错误日志里往往只显示泛泛的 timeout 或 nil pointer。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










