真正“连上”NATS需三要素:显式重连策略(如nats.MaxReconnects(-1))、JetStream初始化并验证(js.AccountInfo())、流与消费者规范配置(如DeliverAll+Durable)。

裸调 nats.Connect() 就上线,等于把消息链路交给网络玄学——连接断了不重连、消息发了不确认、消费者重启就丢历史数据。真要跑微服务,JetStream 不是可选项,是必选项;而“可用”和“裸连”的分界线,就卡在三件事上:连接健壮性、流配置有效性、消费者行为规范。
怎么连 NATS 才算真正“连上了”
默认 nats.Connect(nats.DefaultURL) 只建 TCP 连接,不健康检查、不重试、不设超时。一次 DNS 解析慢或防火墙抖动,Connect() 就卡住不动,后续所有 Publish() 静默失败。
- 必须显式加重连策略:
nats.MaxReconnects(-1)(别用 0 或正数)、nats.ReconnectWait(2 * time.Second)、nats.ReconnectJitter(100*time.Millisecond, time.Second) - 单次连接操作必须设超时:
nats.Timeout(5 * time.Second),否则会无限 hang - 密码绝不能写在 URL 里,改用
nats.UserCredentials("user.creds");启用了 TLS 就必须用tls://前缀,且证书要可信 - 启用了 JetStream 后,连接后立刻初始化:
js, err := jetstream.New(nc),并调js.AccountInfo()做健康探活,失败直接退出
为什么消息总“丢”,以及怎么让它不丢
纯 NATS 是内存转发,消费者掉线期间发布的消息直接蒸发——这不是 bug,是设计。想持久化,只有一条路径:JetStream + 正确的 Stream 配置。
- 必须调
js.AddStream()显式创建流,光连上 JetStream 没用;流名和 subject 要对齐,比如 stream 名orders,subject 设为"orders.>" -
RetentionPolicy必须选对:jetstream.InterestPolicy(只存活跃订阅者需要的)或jetstream.WorkQueuePolicy(每条只投一次),别用默认的LimitsPolicy - 订阅时必须带
nats.DeliverPolicy(nats.DeliverAll),否则默认跳过已有消息;还要加nats.Durable("consumer-name")记住消费位置 - 没开 JetStream 就别指望
Subscribe()能回溯,那是幻觉
JetStream 发布消息时重复和乱序怎么防
JetStream 本身支持去重和有序,但得你主动开、开对、用对。它不会替你做决定。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
立即学习“go语言免费学习笔记(深入)”;
- 发消息必须带业务唯一 ID:
js.Publish("orders.created", data, nats.WithMsgID("order-123")),ID 不能是随机 uuid,得是order_id这类业务主键 - 流配置里的
Duplicates: 2*time.Minute是去重窗口,不是存储时限;若处理耗时超 2 分钟,重复仍会进 - 多 goroutine 并发调
js.Publish()到同一 subject 不保证顺序;关键路径应串行发布,或用单 goroutine + channel 缓冲 - 别依赖“流自动保序”,NATS 不保证跨 subject 或跨 publisher 的全局顺序
消费者怎么写才不算“裸奔”
没幂等、没 ACK、没错误重试的消费者,在 NATS 里等于定时炸弹。一次网络抖动,订单就扣两次库存。
- 事件结构体必须含
Type和Version字段,避免消费者无法路由或解析失败;拒绝json.RawMessage和map[string]interface{} - 处理前必须查去重表(Redis SETNX 或 DB 唯一索引),用
event.Type + event.ID当幂等键 - 必须显式调
msg.Ack();漏掉nats.ManualAck()或忘了调Ack(),消息会在AckWait超时后重投 - 失败时用
msg.NakWithDelay(10 * time.Second)+nats.MaxDeliver(3)控制重试次数,别让死信无限循环
最易被忽略的点是:JetStream 初始化和流创建是一次性动作,改配置得删了重建,不是热更新;而 Durable 名字一旦写错,消费者就相当于全新实例,历史 offset 全丢。


















