当前位置:首页 > 文章列表 > Golang > Go教程 > Go JetStream Go 客户端怎样分批拉取消费消息

Go JetStream Go 客户端怎样分批拉取消费消息

来源:17golang原创 2026-10-30 19:25:39 0浏览 收藏

Go JetStream 里想“每次取一小批消息再处理”,优先使用 jetstream.Consumer.Fetch;它按消息数返回一个 MessageBatch,通道关闭后还要检查 batch.Error()。如果是常驻 worker,则用 Messages() 或 Consume(),让客户端复用拉取缓冲。这样既能控制单批工作量,也能避免把一次性 Fetch 误当成高吞吐循环。

官方地址:https://pkg.go.dev/github.com/nats-io/nats.go/jetstream

本文只讨论 nats.go 的 JetStream pull consumer:按消息数或字节数取批次、显式确认、批次错误和生命周期清理。示例是接口用法说明,不把未执行代码描述成运行结果。

先分清三种拉取方式

Fetch、Messages 和 Consume 都建立在 pull consumer 上,但控制点不同。一次性的导入任务、定时批处理适合 Fetch;长期运行的 worker 更适合 Messages 或 Consume,因为它们会复用客户端缓冲并预取后续请求。

目标接口要记住的边界
一次取最多 N 条Fetch(N)达到批量或等待时间结束就返回,连续调用没有持续消费优化。
一次控制总字节数FetchBytes 或 FetchMaxBytes要给最大消息留出空间,不能把字节上限设得小于可能出现的单条消息。
持续逐条处理Messages()可以用 PullMaxMessages 控制缓冲,结束时调用迭代器的 Stop。
持续回调处理Consume()回调不会自动确认消息,返回的消费上下文必须停止或排空。

因此,“分批拉取”不一定意味着每轮都重新调用 Fetch。如果批次只是业务处理粒度,而进程需要持续工作,可以使用 Messages 的缓冲,再由业务代码决定何时凑够一批。

先创建显式确认的 pull consumer

消费者配置决定了后面的拉取行为。下面的示例创建一个名为 batch-worker 的 durable pull consumer,并把确认策略设为 AckExplicitPolicy。显式确认的好处是:只有业务处理成功后才确认,处理失败的消息不会被代码误标记为完成。

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/nats-io/nats.go"
	"github.com/nats-io/nats.go/jetstream"
)

func createConsumer(ctx context.Context) (jetstream.Consumer, *nats.Conn, error) {
	// 连接关闭后,JetStream 客户端和消费者句柄都不应继续使用。
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		return nil, nil, fmt.Errorf("连接 NATS 失败: %w", err)
	}

	js, err := jetstream.New(nc)
	if err != nil {
		nc.Close() // 初始化 JetStream 失败时及时回收连接。
		return nil, nil, fmt.Errorf("创建 JetStream 客户端失败: %w", err)
	}

	consumer, err := js.CreateOrUpdateConsumer(ctx, "EVENTS", jetstream.ConsumerConfig{
		Durable:       "batch-worker",
		AckPolicy:     jetstream.AckExplicitPolicy,
		FilterSubject: "events.order",
	})
	if err != nil {
		nc.Close()
		return nil, nil, fmt.Errorf("创建消费者失败: %w", err)
	}
	return consumer, nc, nil
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel() // 管理请求也要有明确的结束时间。

	consumer, nc, err := createConsumer(ctx)
	if err != nil {
		panic(err)
	}
	defer nc.Close() // 所有消费工作结束后再关闭连接。
	_ = consumer
}

生产代码通常会在启动阶段创建或取得 stream,再把这个 consumer 句柄交给消费循环。FilterSubject 是可选的;如果不需要按 subject 筛选,可以省略它。不要在每次批处理循环里重复创建 consumer,durable 名称应由应用生命周期负责。

JetStream Stream、pull consumer、Fetch 批量和 MessageBatch 的静态关系结构图
图1:Fetch 批量消费结构图,展示 Stream、pull consumer、MessageBatch 与逐条确认的静态关系。

用 Fetch 按消息数处理一批

Fetch(20) 表示“最多取 20 条”,不是保证一定有 20 条。默认等待时间是 30 秒,也可以使用 FetchMaxWait 调整。读取 MessageBatch.Messages() 得到的通道后,必须继续检查 MessageBatch.Error(),否则心跳丢失等传输问题可能被当成正常的少量消息。

func consumeBatch(ctx context.Context, consumer jetstream.Consumer) error {
	// 单次最多取 20 条,避免空闲时无限等待;实际数量可能少于 20。
	batch, err := consumer.Fetch(20, jetstream.FetchMaxWait(2*time.Second))
	if err != nil {
		return fmt.Errorf("发起批量拉取失败: %w", err)
	}

	for msg := range batch.Messages() {
		// 先完成业务处理,再确认,避免失败消息被提前标记为完成。
		if err := handleOrder(msg.Data()); err != nil {
			return fmt.Errorf("处理消息失败: %w", err)
		}
		if err := msg.Ack(); err != nil {
			return fmt.Errorf("确认消息失败: %w", err)
		}
	}

	// 通道结束只代表本次投递结束,批次本身仍可能携带传输错误。
	if err := batch.Error(); err != nil {
		return fmt.Errorf("批次投递失败: %w", err)
	}
	return nil
}

func handleOrder(data []byte) error {
	// 这里替换为幂等的订单处理逻辑;不要在示例中吞掉业务错误。
	if len(data) == 0 {
		return fmt.Errorf("消息体为空")
	}
	return nil
}

批次内某条消息处理失败时,示例直接返回而不确认该消息。真实项目可以把失败消息转入重试队列、记录业务错误或等待消费者重新投递,但应先明确幂等键和重复处理策略。

消息大小差异大时按字节控量

只按条数控制并不能限制网络和内存开销:一条 JSON 可能只有几百字节,也可能接近消息大小上限。此时可以使用 FetchBytes,或者在 Fetch 上组合 FetchMaxBytes。结果仍是“达到任一限制或等待时间到期就结束”,读取方式和错误处理不变。

func consumeByBytes(consumer jetstream.Consumer) error {
	// 同时限制条数和总字节数,先达到哪个条件就结束本次批次。
	batch, err := consumer.Fetch(100,
		jetstream.FetchMaxBytes(4

字节上限应覆盖可能出现的最大单条消息,并留意官方文档对 subject、reply subject 和 headers 等数据也计入限制的说明。若把限制设得过小,可能出现拉取停滞或批次经常提前结束;这不是把批量参数越小越安全。

持续消费时改用 Messages 或 Consume

如果 worker 需要持续接收消息,不建议在无限循环里反复调用 Fetch。官方文档指出,Messages 与 Consume 会做持续消费优化;前者更适合需要自己掌握逐条处理节奏的 work queue,后者适合把处理逻辑放进回调。

func consumeContinuously(ctx context.Context, consumer jetstream.Consumer) error {
	// PullMaxMessages 控制客户端缓冲,避免预取过多消息占用内存。
	iter, err := consumer.Messages(
		jetstream.PullMaxMessages(20),
		jetstream.PullExpiry(2*time.Second),
	)
	if err != nil {
		return fmt.Errorf("创建消息迭代器失败: %w", err)
	}
	defer iter.Stop() // 退出循环时释放订阅和迭代器资源。

	for {
		select {
		case 

需要回调模型时可以使用 Consume,并保留返回的 ConsumeContext:

func startCallbackConsumer(consumer jetstream.Consumer) (jetstream.ConsumeContext, error) {
	// 回调不会自动确认,处理完成后仍要显式调用 Ack。
	return consumer.Consume(func(msg jetstream.Msg) {
		if err := handleOrder(msg.Data()); err != nil {
			return // 失败消息不确认,交给重投策略处理。
		}
		_ = msg.Ack()
	}, jetstream.PullMaxMessages(20))
}

应用关闭时调用 Stop 可以立即停止接收;希望缓冲中的消息先处理完,则根据生命周期选择 Drain。不要丢弃消费上下文,否则后台 goroutine 和订阅可能继续存活。

JetStream Messages 与 Consume 复用客户端缓冲并由 Stop 或 Drain 结束的静态关系图
图2:持续拉取结构图,展示 Messages 迭代器、Consume 回调、缓冲控制与生命周期出口。

分批消费的四个检查点

检查点推荐做法容易忽略的后果
批次数量把 Fetch(n) 当作最多 n 条,并为超时设计空批次或短批次处理。把“未满 n 条”误判成消费失败。
批次错误遍历消息通道结束后检查 batch.Error()。心跳或传输错误被吞掉,任务看起来只是少处理了几条。
确认时机业务处理成功后再 Ack,失败路径保留错误。消息提前确认后,业务失败也不会重新投递。
资源生命周期结束时调用 iter.Stop() 或 ConsumeContext.Stop/Drain。消费 goroutine、订阅或缓冲无法按预期退出。

常见问题

Fetch(20) 一定会返回 20 条吗?不会,它最多返回 20 条,等待时间先到、当前可用消息不足或发生错误时都可能提前结束。

什么时候用 FetchBytes?当单条消息大小差别很大,业务更关心单批传输量或内存上限时使用;同时要确保上限不会小于最大可能消息。

为什么持续循环不推荐一直 Fetch?Fetch 是一次请求并为每次执行建立投递路径,Messages 和 Consume 更适合持续消费,会复用缓冲和拉取优化。

Messages 和 Consume 怎么选?需要自己控制每条消息的处理节奏、并发和退出条件时选 Messages;已有回调式处理模型时选 Consume,但要保存并停止消费上下文。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
手机壁纸提示词:薄荷绿陶瓷轨道上的朱砂小球手机壁纸提示词:薄荷绿陶瓷轨道上的朱砂小球
上一篇
手机壁纸提示词:薄荷绿陶瓷轨道上的朱砂小球
Go JetStream 的 Ack 与 DoubleAck 有什么区别
下一篇
Go JetStream 的 Ack 与 DoubleAck 有什么区别
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    797次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    786次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    875次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    844次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    676次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码