Contents

nats

NATS

1、简介

NATS 是一个简单、安全、高性能的开源消息系统,专为云原生应用、IoT 消息和微服务架构设计。它提供轻量级的发布/订阅、请求/响应和队列组等核心消息模式,并通过 JetStream 提供持久化与流处理能力。

NATS 是一个开源、高性能、轻量级的分布式消息系统。它的核心设计哲学围绕着几个关键原则:

  1. 极致的简单性: API 和协议设计都非常简洁明了,易于理解和使用。这使得开发、部署和维护的成本较低。
  2. 极致的高性能: 核心 NATS (通常称为 “Core NATS” 或 “NATS Server”) 被设计为极其快速和低延迟,尤其是在发布/订阅场景下。它通常能达到每秒处理数百万条消息的吞吐量。
  3. 极致的轻量级: NATS Server 本身非常小巧(单个二进制文件,内存占用少),并且其客户端库也非常轻量,对资源消耗要求低。
  4. 云原生: NATS 天生就是为现代分布式系统和云环境设计的,支持弹性伸缩、高可用性,非常适合容器化和微服务架构。
  5. 基于主题的发布/订阅: 这是 NATS 最核心和最强大的通信模式。
  6. “At Most Once” 交付 (核心模式): 默认情况下,核心 NATS 提供尽力而为的交付保证。这意味着消息要么被成功传递一次,要么在失败时丢失(不重试)。这种设计是其实现高性能和低延迟的关键。

2、核心架构组件与概念

NATS Server: 消息传递的核心枢纽。负责接收、路由和分发消息。可以以单机模式运行,但更常见的是组成集群模式以实现高可用性和水平扩展。

NATS Client: 连接到 NATS Server 的应用程序。使用各种编程语言(如 Go, Java, JavaScript/Typescript, Python, Ruby, C#, C, Rust 等)的官方或社区维护的客户端库。

Subject: 消息传递的核心抽象。它是一个由点分隔的字符串,用于标识消息的目的地或主题。

  • 例子: orders.new, sensors.temperature.room1, user.profile.updated

  • 通配符:

    NATS 提供了强大的主题通配符机制,使得订阅者可以灵活地订阅感兴趣的消息集合。

    • * (单级通配符):匹配一个层级的任意部分。例如:orders.* 匹配 orders.new 和 orders.cancel,但不匹配 orders.digital.new。
    • > (多级通配符):匹配零个或多个层级的任意部分。例如:sensors.> 匹配 sensors.temperature, sensors.temperature.room1, sensors.humidity 等,多级通配符只能在最后一级使用。

消息: 在 NATS 中传递的数据单元。包含:

  • Subject: 标识消息应被路由到哪里。
  • Payload (可选): 要传输的实际数据(通常是字节数组,可以是 JSON, Protobuf, 文本等任何格式)。
  • Reply Subject (可选): 用于请求/回复模式。
  • Headers (可选): 键值对形式的元数据(较新的特性)。

发布者: 创建消息并将其发送到特定主题的客户端。它不需要知道谁(如果有的话)会接收这些消息。

订阅者: 对一个或多个主题(可以使用通配符)表达兴趣的客户端。它会接收发布到这些主题上的消息。一个主题可以有多个订阅者(广播),也可以没有订阅者(消息被丢弃)。

队列组: 一种负载均衡机制。

  • 多个订阅者使用相同的队列组名称订阅同一个主题。
  • 当消息发布到该主题时,NATS Server 会随机选择队列组中的一个订阅者来接收该消息。
  • 这允许你将工作负载分布到多个消费者实例上,实现水平扩展和处理能力提升。

3、核心通信模式

Nats为subject提供了两种通配符,订阅者可通过通配符订阅多个主题的消息(subject用法会在代码中展示)。

time.us
time.us.east
time.us.east.newyork
time.cn
time.cn.beijing

1)* 会匹配单个字符串
a、可在同一个subject中多次出现
b、如time.*.east可匹配time.us.east或time.eu.east但不可匹配time.us.east.newyork
2) > 会匹配多个字符串
a、只能在同一个subject中出现一次
b、如time.us.>可匹配time.us.east和time.us.east.newyork等

1、发布/订阅(publish-subscribe):最基本和最核心的模式

  • 发布者将消息发送到一个主题。
  • 所有订阅了该主题(或匹配通配符模式)的订阅者都会收到该消息的副本。
  • 特点:一对多广播。
	nc, _ := nats.Connect(nats.DefaultURL)
	// 发布者 "foo"即为主题
	nc.Publish("foo", []byte("Hello World"))
	// 异步订阅者
	nc.Subscribe("foo", func(m *nats.Msg) {
            fmt.Printf("Received a message: %s\n", string(m.Data))
        }

2、请求/回复(request-reply):基于发布/订阅构建的常用模式

  • 请求者发布一条消息到一个主题(通常是服务名称),并在消息中包含一个 Reply-to 主题(通常是临时生成的唯一主题)。

  • 响应者订阅请求主题。当收到请求消息时,处理请求,并将响应发布到请求消息中指定的 Reply-to 主题。

  • 请求者订阅它自己的 Reply-to 主题以接收响应。

// 请求 等待10毫秒
msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond)
// 响应 收到消息后回复
nc.Subscribe("help", func(m *nats.Msg) {
    nc.Publish(m.Reply, []byte("I can help!"))
})

3、队列订阅(Queue Groups):此模式下nats服务器会随机的将消息分配至订阅者,而不是广播出去。

// 将消息随机分配至 "foo"和"job_workers"中不在队列组中的订阅者依然会接收到所有消息。
nc.QueueSubscribe("foo", "job_workers", func(_ *Msg) {
  received += 1;
})

4、关键特性和优势

  • 卓越的性能: 极低的延迟和极高的吞吐量,尤其在核心发布/订阅模式下。

  • 极简的协议: NATS 服务器和客户端之间的通信协议 (nats://) 是文本协议(可扩展为二进制 nats://),设计简洁高效,易于实现和调试。

  • 无代理依赖: NATS Server 是自包含的,不需要依赖外部的数据库、ZooKeeper 或 etcd 来运行(集群协调是内置的)。

  • 灵活的订阅模型: 强大的主题和通配符系统提供了极其灵活的消息路由能力。

  • 内置负载均衡: 通过队列组轻松实现消费者负载均衡。

  • 客户端自动重连和故障转移: 客户端库内置了强大的重连逻辑,在服务器故障或网络中断时能自动重连到集群中的其他可用节点。

  • 安全性: 支持 TLS/SSL 加密通信、基于 Token 或用户名/密码的认证、NKey 认证以及更复杂的基于 JWT 和配置分发的认证授权系统。

  • 多语言支持: 广泛的官方和社区支持的客户端库。

  • 云原生友好: 轻量级、易于容器化、支持动态扩展,完美契合 Kubernetes 和微服务架构。

5、NATS 2.0 与 JetStream:解决持久化需求

虽然核心 NATS 的高性能源于其 “at most once” 和无持久化设计,但许多应用场景需要更强的消息保证(至少一次、正好一次)和持久化能力。为此,NATS 2.0 引入了 JetStream。

  • JetStream 是什么? 它是内置于 NATS Server 中的持久化引擎。它不是独立的服务器,而是与核心 NATS 协同工作。

  • 解决的问题:

    • 消息持久化: 将消息存储到磁盘(或内存),防止服务器重启导致消息丢失。

    • 增强的交付语义: 支持 “At Least Once” 交付(通过确认机制确保消息被处理)和 “Exactly Once” 处理(通过消费者序列号去重)。

    • 流处理: 将消息组织成持久的流。流定义了消息的存储策略(如保留策略:基于时间、消息数、大小等;存储后端:文件或内存)。

    • 消费者:

      从流中拉取或推送消息的实体。支持多种消费模式:

      • 推模式: JetStream 主动将消息推送给消费者(类似核心订阅)。
      • 拉模式: 消费者主动从流中请求一批消息(批处理友好)。
      • 支持多种确认机制: Ack (成功), Nak (重试), Term (不再重试), AckNext (处理中并要求下一条) 等。
      • 支持消费者配置: 如重试策略、起始位置、过滤等。
  • 核心 NATS 与 JetStream 的关系:

    • 核心 NATS 负责高性能、低延迟的即时消息传递(发布/订阅、请求/回复)。
    • JetStream 负责需要持久化和更强保证的流式消息处理。
    • 它们共享相同的 NATS Server 基础设施、网络协议和客户端连接。客户端可以使用同一个连接同时进行核心 NATS 操作和 JetStream 操作。
    • 发布到配置了 JetStream 的主题的消息会被自动持久化到流中。

6、与其他消息系统的比较

vs RabbitMQ:

  • NATS 更轻量、性能更高(尤其在纯 pub/sub)。
  • RabbitMQ 功能更丰富(多种交换类型、死信队列、更复杂的路由规则、插件体系),但相对更重。
  • NATS 主题通配符比 RabbitMQ 的 Topic Exchange 更灵活。
  • 核心 NATS 的持久化/保证不如 RabbitMQ,但 JetStream 弥补了这一点。

vs Apache Kafka:

  • Kafka 是分布式流处理平台,核心是持久化、分区、有序的日志。吞吐量极高,适合大数据管道和流处理。
  • NATS (Core) 更轻量、延迟更低,更适合实时消息传递和命令控制。
  • NATS 的订阅模型比 Kafka 的 Consumer Group 更灵活(通配符订阅)。
  • Kafka 的学习曲线和运维复杂度通常高于 NATS。
  • JetStream 提供了类似 Kafka 的持久化流能力,但设计更简单,与核心 NATS 集成更紧密。

vs MQTT Brokers (如 Mosquitto, EMQX):

  • MQTT 是标准协议,专注于 IoT,有 QoS 级别。
  • NATS 原生协议 (nats://) 性能通常更高,功能更丰富(如请求/回复、队列组)。NATS 也支持 MQTT 协议网关。
  • NATS 的集群和高可用设计通常更易于管理和扩展。

docker compose

services:
  nats:
    image: nats:2.15-alpine3.22  # 使用 alpine 版便于调试,latest 版是 scratch 基础镜像
    container_name: nats
    restart: unless-stopped
    ports:
      - "4222:4222"   # 客户端
      - "8222:8222"   # HTTP 监控
    command: ["-js", "-sd", "/data", "-m", "8222"]  # -js 开启 JetStream
    volumes:
      - nats_data:/data

volumes:
  nats_data:

2、核心概念

subject 主题

Subject 是 NATS 中消息的路由标识,采用点分层级命名(如 orders.created、users.login)。发布者向 Subject 发送消息,订阅者订阅 Subject 接收消息。支持通配符:* 匹配单个 token,> 匹配一个或多个 token。

消息模式

模式 说明 适用场景
发布/订阅 一条消息分发给所有订阅者 事件广播、通知
队列组 同一队列组内的订阅者负载均衡消费 任务分发、工作队列
请求/响应 客户端发送请求并等待回复 RPC 调用、服务查询

JetStream

JetStream 是 NATS 的持久化层,提供流(Stream)和消费者(Consumer)抽象,支持消息持久化、至少一次/恰好一次投递、消息重放等能力。

开发示例

发布者

package main

import (
    "log"
    "github.com/nats-io/nats.go"
)

func main() {
    nc, err := nats.Connect(nats.DefaultURL)
    if err != nil {
        log.Fatal(err)
    }
    defer nc.Close()

    err = nc.Publish("hello", []byte("Hello NATS!"))
    if err != nil {
        log.Fatal(err)
    }
    nc.Flush()
    log.Println("Message published")
}

订阅者

package main

import (
    "log"
    "runtime"
    "github.com/nats-io/nats.go"
)

func main() {
    nc, _ := nats.Connect(nats.DefaultURL)
    defer nc.Close()

    nc.Subscribe("hello", func(msg *nats.Msg) {
        log.Printf("Received: %s", string(msg.Data))
    })

    runtime.Goexit()
}