高可靠依赖三个关键动作:连接须设超时与无限重连、JetStream连接后立即初始化、订阅必须显式配置DeliverPolicy;缺一即导致丢消息或启动失败。

直接说结论:高可靠不是靠堆配置,而是守住三个关键动作——连接必须带重连与超时、JetStream 必须连接后立刻初始化、订阅必须显式设 DeliverPolicy。漏掉任一环,服务上线就丢消息或启动失败。
连接不设 nats.Timeout 和 nats.MaxReconnects(-1),服务大概率启动卡死或秒挂
本地 nats://localhost:4222 能连,不代表生产能活。DNS 解析慢、TLS 握手卡顿、集群节点临时不可达,都会让 nats.Connect() 阻塞或 panic。
- 必须显式加
nats.Timeout(5 * time.Second),防止启动阶段被网络问题拖住 -
nats.MaxReconnects(-1)不是可选项——默认 60 次重试后放弃,意味着节点故障超过 2 分钟,你的服务就永久失联 - 搭配
nats.ReconnectWait(2 * time.Second)和nats.ReconnectJitter(100*time.Millisecond, time.Second),避免重连风暴打爆 NATS server - 集群地址必须写成逗号分隔:
"nats://nats-1:4222,nats://nats-2:4222,nats://nats-3:4222",单 URL = 单点依赖
nc.JetStream() 不能延迟调用,否则第一次 js.Publish() 就 panic
NATS 客户端不自动探测 JetStream 状态。nc.JetStream() 只返回一个包装器,不触发任何服务端检查。等你真要发消息才初始化,等于把错误推迟到业务路径上。
- 连接成功后立刻执行:
js, err := jetstream.New(nc),并严格检查err - Stream 不存在时,别手动 curl 或后台建——用
js.CreateStream()显式创建,配好jetstream.RetentionLimits和jetstream.Duplicates(2 * time.Minute) - 别复用同一个
*nats.Conn多次调jetstream.New(),它不幂等;不同业务模块应持独立jetstream.Context
订阅不设 nats.DeliverPolicy,断连重启后消息全丢
NATS 默认纯内存转发,消费者掉线期间发布的消息直接丢弃——这不是 bug,是设计。想“不丢”,得靠 JetStream + 显式策略。
立即学习“go语言免费学习笔记(深入)”;
- 必须设
nats.DeliverPolicy(nats.DeliverAll),否则订阅者重启后收不到积压消息 -
DeliverLastPerSubject会跳过中间事件,只推最新一条,仅适用于状态同步类场景,慎用 - 启用去重需手动开:
jetstream.DuplicateWindow(2 * time.Minute),窗口期要覆盖最大可能重发间隔 - 消费端用
QueueSubscribe()但没设nats.MaxInflight,等于并发为 1,QPS 上不去
硬编码凭证、不处理断连回调,等于裸奔上线
生产环境没认证,Authorization Violation 日志刷屏;不监听断连,故障无法感知,熔断无从谈起。
- 凭证绝不用字符串硬写:
nats.UserCredentials("nats.creds")或nats.Token("xxx")缺一不可 - 注册
nats.DisconnectedErrHandler,在回调里触发熔断器状态切换或告警,而不是静默忽略 - 所有
js.Publish()和js.GetMsg()必须绑定context.Context,防 goroutine 泄漏 - JetStream 只保证 at-least-once,消费者必须自己做幂等——比如用
order_id去重 + 数据库唯一索引拦截重复写入
真正难的不是写通一条 publish,而是让每条消息在节点宕机、网络分区、服务重启后依然可追溯、可重放、不重复。这些细节不提前对齐,压测时丢消息,上线后查日志,代价远高于写代码本身。


















