nats 生产环境必须显式配置连接超时、重连策略、jetstream 持久化语义、强类型事件结构及正确发布/订阅方式,否则必然出现连接失败、消息丢失、重启收不到旧消息等问题。

连不上、消息丢、重启收不到旧消息——这些问题不是 NATS 本身坏了,而是默认配置根本没为生产环境准备。必须显式控制连接行为、JetStream 策略和事件结构,否则写再多 handler 都白搭。
连接 NATS 必须设超时与重连,否则一卡就是无限 hang
本地用 nats.Connect("nats://localhost:4222") 能跑通,上测试环境就卡死或反复断连,本质是客户端默认不设单次连接超时、不控重试节奏。DNS 解析慢、防火墙拦截、服务未就绪时,它会一直等下去。
-
nats.Timeout(5 * time.Second):单次连接操作最多等 5 秒,超时立即报错,避免阻塞启动 -
nats.MaxReconnects(60):别用-1(永久重试),60 次后放弃,防压垮服务端 -
nats.ReconnectWait(2 * time.Second)+nats.ReconnectJitter(100*time.Millisecond, time.Second):每次重连前等 2 秒,并加随机抖动,避免集群所有实例同时冲击服务端 - 集群部署时 URL 写成逗号分隔:
"nats://n1:4222,nats://n2:4222",客户端自动轮询并剔除不可用节点
JetStream 订阅不配 DeliverPolicy 和 ManualAck 就等于没开持久化
用 js.Subscribe() 却发现消费者重启后收不到断连期间的消息,说明只是开了 JetStream 连接,没启用持久化语义。纯 nats.Subscribe() 是内存广播,js.Subscribe() 不配策略也默认只推当前在线消费者。
- 必须显式加
nats.DeliverPolicy(nats.DeliverAll):从流头部开始重放所有未确认消息 - 必须加
nats.ManualAck():关闭自动 ACK,否则消息投递后立刻被标记为已处理,下次重启照样收不到 -
nats.AckWait(30 * time.Second):消息投递后等待 30 秒内收到msg.Ack(),超时则重发;这个值要大于业务处理最大耗时 - 订阅名必须带
nats.Durable("worker-1"),否则 offset 不持久,重启即丢失
事件结构体没 Type/Version 字段,消费端迟早 panic 或静默丢数据
用 json.RawMessage 或 map[string]interface{} 解包事件,很快会遇到字段缺失 panic、类型断言失败,或无法路由到对应 handler——因为消费者根本不知道该用哪个逻辑处理这条消息。
- 定义强类型结构体,固定含
Type和Version字段:type OrderCreatedEvent struct { Type string `json:"type"` Version string `json:"version"` ... } -
Type用于路由匹配,比如js.Subscribe("order.created", ...);必须与发布时 subject 完全一致 -
Version控制演进:v1字段变更属 breaking change,v2新增字段需保持向下兼容 -
Timestamp字段用time.Time+stringtag:Timestamp time.Time `json:"timestamp,string"`,避免字符串时间解析失败
Publish() 和 QueueSubscribe() 选错,不是丢消息就是压垮 CPU
关键路径用 PublishAsync(),结果网络抖动时消息静默丢失;高吞吐消费端用 Subscribe(),所有消息串行进一个 goroutine,QPS 上不去还 CPU 闲置。
- 订单创建、支付回调等关键消息,必须用
nc.Publish()并检查返回的error;PublishAsync()只适合日志、埋点等弱一致性场景,且必须配nc.FlushTimeout(5*time.Second) - 微服务多实例部署,必须用
nc.QueueSubscribe(),相同queue name的多个消费者自动负载分摊 - 务必设
nats.MaxInflight(256)(或按业务调整),否则默认 inflight=1,又退化成串行 - 主题(subject)是纯字符串匹配:
"order.created"和"order.created.v1"完全无关,发布与订阅两端必须字面量完全一致
最易被忽略的是:JetStream 不是“开了就行”,必须连接后立刻调用 jetstream.New(nc) 初始化上下文,Stream 要显式 js.AddStream() 创建,消费者要手动 msg.Ack() —— 少一步,消息就掉进黑洞里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











