- Published on
NATS Server 2.14 — 快速批量发布、服务器内置调度,以及 2.15 之前要做完的功课
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 — 为什么 2.12 之后是 2.14
- 快速批量发布 — async publish 的继任者
- 重复调度与采样 — cron 搬进了服务器内部
- WorkQueue·Interest 溯源不再断档
- 消费者重置 API
- 从运维视角看真正重要的三件事
- 从 API 级别看功能开关,以及三条发布主线
- 那么,什么时候该升级,什么时候可以再等等
- 结语
- 参考资料
引言 — 为什么 2.12 之后是 2.14
NATS Server 2.14.0 于 2026 年 4 月 30 日发布。上一个次要版本 2.12.0 是 2025 年 9 月 22 日,相隔约 7 个月。NATS 团队在2.12 发布文章中说明已经切换到 6 个月的发布周期,而2.14 发布文章把这次大约推迟一个月的原因写成「AI 安全审查与披露应对工作增多」。AI 产生的安全报告正在推着开源维护者的发布日程走 — 这是从发布说明里读到的 2026 年图景。
再看版本号。发布说明第一行明确写着「2.13.x 版本被跳过了(the 2.13.x version was skipped)」。原因在发布说明、博客、升级指南里都没有给出。查看 git 历史可以看到,就在 2.12.0 发布的第二天,也就是 2025 年 9 月 23 日,main 分支就直接跳到了 v2.14.0-dev,说明这不是事后改了编号,而是从周期一开始就计划好的跳过。后文会提到,可以观察到 JetStream API 级别号与服务器次要版本号又重新对齐了,但这只是我自己的推测,不是官方说明。
本文不是罗列亮点,而是直接查阅发布说明、升级指南、设计文档(ADR)与服务器源码,梳理「这到底做了什么、没做什么、运维者该盯住什么」。如果你对 NATS 本身还不熟悉,建议先读分布式消息传递 2026 深度解析。
快速批量发布 — async publish 的继任者
2.14 的代表功能。先说背景:2.12 引入的原子批量发布(atomic batch publish)会把 N 条消息暂存起来,只有全部通过一致性检查才提交整个批次,是一种 all-or-nothing 方式。这对像「在 KV 存储里一次性更新构成一个地址的 5 个键」这类问题非常合适,但由于要暂存,批次大小存在实际上限(ADR-50 提到原子批量的上限是 1,000 条)。
2.14 的快速批量目的不同。目标不是原子性,而是吞吐量和流控。
原子批量 (2.12) 快速批量 (2.14)
目的 all-or-nothing 的正确性 高速写入 + 流控
存储方式 先暂存后一次性提交 消息一到就立即存储
一致性检查 整批为单位 逐条消息内联检查
大小限制 有 (依 ADR 为 1,000 条) 事实上无限制
失败时 整批拒绝 gap:fail — 中止批次 / gap:ok — 报告缺口后继续
领导者切换 批次作废 gap:ok 模式下可存活
这套设计的核心,是批次开启期间保持的一条控制通道。客户端不需要为每条消息等待 ack,而是由服务器大约每 10 条消息发一次流控 ack(BatchFlowAck)。流速由服务器决定 — 按 ADR 所述,即便客户端请求最大流速,服务器也会从一个较低的值(比如 1)开始,有余量时大约每次翻倍,检测到负载时则减半。判断依据是服务器内部的指标,比如并发快速发布者数量、平均消息大小、以及尚未持久化的在途数据量。
缺口(丢失)处理可以选两种模式之一。fail 模式在检测到缺口的瞬间中止批次,适合需要顺序保证的场景(比如对象存储里的文件分块);ok 模式只用 BatchFlowGap 消息报告缺口然后继续执行,适合可以容忍部分丢失的高频指标发布者。ADR 把这套设计的最终目标写得很明确 — 用它取代现有客户端的 async publish。
关于性能说法,有必要诚实一点。官方博客写的是「视配置而定,吞吐量最多可比 async publish 快近 2 倍」,但这是厂商自述,测量条件(消息大小、复制级别、硬件、客户端数量)并未公开。目前没有公开的可复现基准数据。不过定性的动机说得通 — 与缺乏流控、容易在两个对象存储上传同时进行时崩掉的现有 async publish 不同,博客的说法是,因为服务器直接协调各发布者的速度,即使并发发布者多达数百个,每一个也都能获得各自的流控(这同样是厂商自述)。
客户端支持现状也要现实地看。要用这个功能,需要在流上打开 AllowBatchPublish 选项,并配合支持它的客户端;截至本文写作时,官方实现领先的是 Go 扩展库 orbit.go 的 jetstreamext v0.3.0(2026-04-21,FastPublisher)。两周后 v0.3.1 修复了 FastPublisher 的卡顿、超时和回复前缀的问题。核心库 nats.go 在 v1.52.0 里只加入了流配置字段。也就是说,这还是个年轻的功能,首个实现刚发布就经历了补丁修复。在放进生产环境的核心路径之前,最好先用自己的工作负载验证一下。
重复调度与采样 — cron 搬进了服务器内部
2.12 通过 Nats-Schedule-TTL 头引入了「稍后投递一次」的消息(delayed message)。2.14 把它扩展成重复调度。给消息附上 Nats-Schedule: @every 5m 这样的间隔、或 @hourly、或者一个 cron 表达式,服务器就会按这个调度持续向目标主题生成消息。
nats pub -J 'schedules.orders.hourly' \
-H "Nats-Schedule: @hourly" \
-H "Nats-Schedule-TTL: 5m" \
-H "Nats-Schedule-Target: orders" \
'body'
要点是,客户端不再需要常驻一个专门跑定时器的发布者。随之而来的还有两个衍生功能。主题采样通过 Nats-Schedule-Source 头,实现「调度每次触发时,读取源主题的最后一条消息并发布到目标」— 典型用例是服务器端降采样:边缘设备每秒产生的传感器数据,每 5 分钟才上传一次到云端(如果源主题没有消息,就把调度消息自身的正文作为兜底发布)。调度汇总(rollup)通过 Nats-Schedule-Rollup 头,在发布时对该主题做汇总。
从 ADR-51 里确认到的、不读文档就会踩到的限制:
- 每个调度都需要一个唯一的主题。如果想让多条延迟消息都发往同一个目标,就要像
orders.schedule.UUID这样让调度消息的主题唯一,再用Nats-Schedule-Target汇总。 @every的最小间隔是 1 秒,更短会被拒绝。- cron 使用包含秒字段的 6 字段格式,默认时区是 UTC。
Nats-Schedule-Time-Zone头可以指定 IANA 时区(比如Asia/Seoul),但由于服务器要用宿主机的 tzdata 解析时区名,这就带来一个运维要求 — 所有要评估 cron 调度的服务器都必须安装且更新 tzdata。固定偏移量(+02:00)不被接受。 - ADR 不建议使用会落在 DST 切换时刻的 cron — 它明确写道,时钟往前跳时那一次可能被跳过,往后调时则可能执行两次。
- 如果服务器长时间宕机后恢复,那些已经过去的调度会立即触发。如果不希望一个月前预约的消息在恢复后马上送达,ADR 给出的防御办法是在调度消息上加
Nats-TTL让它过期。 - ADR 明确写道,使用调度的流不支持 Discard New 策略,2.14.2 还加强了对计数器流(counter stream)和调度的配置约束校验。
什么时候不该用 — 这不是分布式 cron 调度器的替代品。执行历史追踪、失败重试策略、防止重叠这些任务调度器该负的责任,仍然要落在消费者一侧,服务器能做的只是「在那个时间点生成消息并放进流里」。反过来说,用它来去掉「只是保持连接、跑个定时器的 sidecar」倒是正合适。
WorkQueue·Interest 溯源不再断档
名字朴素,但其实是一处实质性的一致性修复。在 2.14 之前,流的镜像·溯源(source)内部是靠临时(ephemeral)消费者做异步复制的。问题出在溯源对象是 WorkQueue 或 Interest 保留策略的流时 — 比如一个叶子节点(leaf node)在溯源一个 Interest 流的过程中宕机一周,这期间「兴趣(interest)」会被当作已经消失处理,数据被自动删除,即便叶子节点恢复了,那段区间也不会被复制回来。
2.14 在溯源·镜像 WorkQueue、Interest 流时,会自动切换为持久(durable)消费者。新的 ack 策略 AckFlowControl 基于流控在持久化之后再确认,这个消费者会出现在消费者列表里,带着来源域·账户·流的元数据,即便连接断开也会保留状态。在安全性敏感的场景里,还支持「自带消费者(bring your own consumer)」— 预先创建好消费者,这样对方就碰不到消费者配置。
升级过程中要留意一点 — 在混合版本的集群里,已升级的服务器可能会尝试创建新式的溯源消费者,而旧版本服务器会在日志里留下 json: unknown field "sourcing" 的警告。按指南的说法,它会自动回退重试旧方式,所以这只是整体升级完成前会出现的临时日志。降级的痛苦更大一些 — 降回 2.12 后,AckFlowControl 消费者会被标记为离线,直到升回 2.14 之前都用不了。
消费者重置 API
运维中总会遇到「想让这个消费者从序号 N 重新开始跑」的时刻。以前唯一的办法是删除消费者、指定起始序号重新创建,这个过程会连带牵动名称、订阅者、ACL 相关的一堆东西。2.14 新增了 $JS.API.CONSUMER.RESET.stream.consumer API,可以不删除消费者,直接把投递状态重置到 ack floor 或任意序号。指南把重置后的状态定义为「等同于在那个序号上先删除再重建」。
从运维视角看真正重要的三件事
比起功能列表,这一节对升级决策更重要。三件事都是升级指南里明确写出的行为变化。
第一,filestore I/O 错误现在会冻结流。 以前的版本对部分文件系统 I/O 错误处理不当,流和服务器会在错误发生的情况下继续运行。从 2.14 起,错误会被表面化 — 受影响的流会冻结(freeze),不再继续处理,日志里会留下错误,健康检查会报告 unhealthy。同一台服务器上的其他流不受影响,如果是复制流,其他副本会透明地接管。不过冻结的服务器本身需要重启才能恢复。这显然比悄悄腐坏要好,但也意味着多了一个需要监控的新运营状况 — 把 healthz 失败信息里的 write error 加入监控项。
第二,Raft 过载保护。 以前,如果 Raft 日志里的条目堆积速度超过提交·应用的速度,内存和磁盘可能会无限增长。2.14 能识别这种状态,限制资源使用,并让落后的领导者主动让位给更健康的对等节点(peer)。指南也把局限写得很清楚 — 如果大多数对等节点都同样过载,系统仍会停留在性能下降的状态;这个保护机制只是应对临时性过载的安全网,不能替代足够的容量。这句话能出现在发布文档里,本身就是这个项目文档的一个优点。
第三,2.15 之前要做完的功课 — ACK 主题格式迁移。 2.14 开始同时支持消费者确认应答·流控回复主题的 v2 格式(包含域·账户哈希)。
v1: $JS.ACK.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<ts>.<pending>
v2: $JS.ACK.<domain>.<acct hash>.<stream>.<consumer>.<delivered>.<sseq>.<cseq>.<ts>.<pending>
2.14 的默认值仍是 v1,但 2.15 会把默认值改成 v2。指南明确写道,如果你在账户 import/export 或主题权限里连流名称都写死进了 ACK·FC 主题,就得在 2.15 之前更新 ACL(如果用的是万能通配符,或者只在单一账户里用 JetStream,则不受影响)。为了方便提前测试,2.14 在服务器配置里加入了 feature_flags 字段,可以在这里打开 js_ack_fc_v2。但要注意,如果带着这个字段设置降级到 2.12,旧版本无法识别该字段,所以降级前得先清掉。
除此之外,还有一些不显眼但有用的改动:info、list 这类只读查询请求现在会进入与创建·更新·删除请求分开的队列,优先级更低了(缓解了仪表盘轮询挤占运维操作的情况);复制流的状态快照现在会异步写入,不再暂停流处理 — 据称对内部删除(interior delete)较多的流的尾延迟特别有效,但没有公开具体数字。
从 API 级别看功能开关,以及三条发布主线
从 2.11 开始,NATS 按 API 级别来开关 JetStream 功能。直接从服务器源码(server/jetstream_versioning.go)里确认到的数值是:2.11 是级别 1,2.12.0 是级别 2,2.14.0 是级别 4。那级别 3 去哪儿了 — 有意思的是,维护主线 2.12.5(2026 年 3 月)用的正是级别 3。像流备份的 window_size 参数这种小的 API 新增被塞进了补丁主线,消耗掉了一个级别。结果是,跳过 2.13 反而让「次要版本号 − 10 = API 级别」在 2.14 上又重新对上了 — 但还是要重申,没有官方说明这就是跳过的原因。
这个级别在实务中的意义是降级安全性。使用更高级别功能的流·消费者,在旧版本服务器上不会被破坏,而是被标记为离线(2.12 的 offline assets),客户端也可以用 Nats-Required-Api-Level 头明确要求的级别。
发布主线的运维方式同样值得留意。翻看 GitHub 的发布历史,2.14.0 发布之后,2.12.x 依然在持续打补丁,而且常常是同一天并排发布 — 6 月 2 日一起发布了 2.12.10 和 2.14.2,6 月 29 日一起发布了 2.12.12 和 2.14.3。与此同时,2.11.x 在 2.14.0 发布前夕的 4 月 27 日发布 2.11.17 之后就停止了。也就是说,观察到的模式是「当前 + 上一个」两条主线同时维护,但没有找到官方的支持期限政策文档,所以这只是从历史里读出的模式,不是承诺。
那么,什么时候该升级,什么时候可以再等等
有理由立即升级的情况 — 在叶子节点上溯源 WorkQueue·Interest 流的拓扑(一致性修复是实质性的)、需要消费者重置的运维模式、欢迎 filestore I/O 错误提前表面化的环境(存储不太可靠的边缘)、以及服务器内置的调度·降采样能省掉一个 sidecar 的 IoT·边缘部署。
没必要着急的情况 — 如果目的是快速批量发布,考虑到客户端生态目前还是以 orbit.go(Go)为中心,而且首个实现刚经历过补丁修复,最好不要在没有自己工作负载基准测试的情况下就放进核心路径。既然 2.12.x 主线仍在同一天获得补丁,如果不需要新功能,留在 2.12.12 目前也是一个说得过去的选择。不过无论选哪一边,如果是精细使用 ACK 主题 ACL 的多租户环境,都应该从现在开始准备 js_ack_fc_v2 的迁移,赶在 2.15 默认值改变之前。
另外补充一点,同一时期消息生态的另一个方向上,Kafka 正在用 KIP-1150 无盘主题(diskless topics) 重新设计存储层本身。Kafka 是把 broker 磁盘往对象存储的方向推,NATS 则是让服务器直接协调发布者的流速 — 把这两种解决吞吐量与成本问题的路径并排看,挺有意思。两个系统在定位上的差异,在消息系统综合指南里讨论过。
结语
总结一下,2.14 与其说是一次华丽的发布,不如说是把 JetStream 往「大批量写入与无人值守运维」的方向又推了一格。快速批量用服务器主导的协调,解决了 async publish 缺乏流控这个结构性弱点;调度把定时器 sidecar 吸收进了服务器;WorkQueue·Interest 溯源悄悄补上了曾经会悄悄丢数据的漏洞。作为代价,运维者多了一项要盯的新状况(流冻结)和一项迁移功课(ACK v2 的 ACL)。
性能数字目前还只是厂商自述,所以是否引入,应该以自己工作负载的实测为准,而不是文档里的说法。而这次发布的文档 — 明确写出自身局限的升级指南、承认 DST 时可能执行两次的 ADR — 倒是开始这场实测相当不错的起点。