登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go JetStream 的 Ack 与 DoubleAck 有什么区别

来源:17golang原创

时间:2026-10-30 19:27:55 361浏览 收藏

Ack() 发送消费成功确认,但不等待服务端对这次确认的回复;DoubleAck(ctx) 则发送确认并等待服务端回复。两者的主要区别,是调用方能否取得确认已被服务端接收的反馈,而不是业务处理执行一次还是两次。

本文使用 github.com/nats-io/nats.go/jetstream 中的 jetstream.Msg,讨论显式确认的消费场景。不要把它与旧接口的类型或发布消息时的 PubAck 混在一起。

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

两种方法的返回值说明了什么

官方 Msg 接口说明 DoubleAck 会等待服务端确认回复,因此需要 context,并会增加等待开销。仓库的 message.go 实现中,Ack 使用连接的 Publish,而 DoubleAck 使用 RequestWithContext;两者都提交成功确认语义,区别在于是否采用请求回复方式等待结果。

比较项AckDoubleAck
调用形式msg.Ack()msg.DoubleAck(ctx)
等待服务端回复不等待等待
nil 的理解本次发送操作未返回错误本次确认请求取得成功回复
调用成本没有额外等待该回复增加一次确认回复的等待
业务数据库事务不负责提交同样不负责提交
消息接口的Ack与DoubleAck分别依赖Publish和RequestWithContext
静态结构图:Ack 与 DoubleAck 使用不同的底层确认方式;连线表达依赖,不表示执行顺序。

实际编排时,可以把结果拆成“业务是否已完成”和“消息确认是否已知成功”。例如订单变更已经写入数据库,但确认等待超时,页面或管理端应展示待核实的确认状态,而不是直接把订单业务改回未处理。

在业务落盘后选择一种确认方法

下面是原创处理函数片段,接收已取得的消息和业务持久化函数。调用者负责创建连接、流与消费者,使用适合该任务的显式确认配置。示例不包含这些外围初始化,也未声称已在本机运行。

package ackchoice

import (
    "context"
    "fmt"
    "time"

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

// Process 的 persist 必须使用稳定业务键实现幂等,并在返回 nil 前提交业务结果。
func Process(ctx context.Context, msg jetstream.Msg,
    persist func(context.Context, []byte) error, waitReply bool) error {
    if err := persist(ctx, msg.Data()); err != nil {
        // 业务未成功时不发送成功确认;重投策略由调用方和消费者配置决定。
        return fmt.Errorf("业务处理失败: %w", err)
    }

    if !waitReply {
        // 只选一种确认方式;nil 不表示已经收到服务端确认回复。
        if err := msg.Ack(); err != nil {
            return fmt.Errorf("业务已提交,Ack 失败: %w", err)
        }
        return nil
    }

    // 给确认等待单独设置上限;此值是示例预算,不是推荐的统一配置。
    ackCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()
    if err := msg.DoubleAck(ackCtx); err != nil {
        // 保留已提交的业务状态,不能因确认超时便再次执行非幂等操作。
        return fmt.Errorf("业务已提交,确认回复未成功取得: %w", err)
    }
    return nil
}

waitReply 是应用自己的选择参数,不是 JetStream 配置项。业务持久化完成后只进入一个确认分支。不要先调用 Ack,再用 DoubleAck“补一次保障”:当前实现对已确认的消息对象记录状态,再次确认可能得到 ErrMsgAlreadyAckd。

这段函数把错误返回给调用方,不在确认错误后自动重新执行 persist。生产代码可以把业务错误和确认错误拆成不同错误类型,便于监控与界面分别显示;这里用错误前缀保持示例紧凑。

DoubleAck 超时后怎样保留业务状态

从网络请求的性质可以推得:等待失败只能说明调用方没有成功取得回复,不能单凭超时断言服务端从未处理确认。故障可能出现在请求发送前,也可能出现在回复返回途中。因此,不宜把“确认超时”直接翻译成“业务操作失败”。

建议在业务数据库中用稳定的事件编号或订单操作编号作为业务幂等键,并把幂等记录与业务变更放在同一事务里。重投消息到达时先查该业务键:若对应变更已经提交,就复用已完成结果,再为本次收到的消息执行确认,避免重复扣减、重复通知或重复创建记录。

业务处理器幂等数据库与JetStream消息确认之间的独立边界
静态边界说明图:业务持久化与消息确认是两项独立工作,确认错误不能直接推翻已提交的业务结果。

业务幂等键的作用域必须由业务定义。一个订单可能包含多次有效变更,不能简单拿订单编号去重所有事件;反过来,进程启动时随机生成的标识也无法识别同一事件的重投。确认日志建议记录业务键、消费者及错误类别,避免把消息正文中的敏感数据整段写入日志。

怎样按响应体验选择确认方式

如果工作线程只需要提交确认,能够接受确认状态不确定时由重投与业务幂等兜底,Ack 通常更符合低等待的处理方式。若管理任务需要明确显示“服务端已确认”,并愿意支付额外等待成本,可以选择 DoubleAck,并为超时设置独立状态。

界面层建议区分三个提示:“处理中”“业务已完成,确认待核实”“业务与确认均完成”。提示应同时提供文字,不能只用红绿颜色表达。这个建议是应用层体验设计,并不是 NATS 自带界面状态。

不要把示例中的两秒作为统一参数。确认等待预算应结合部署网络、任务时限和服务端运行情况决定;业务执行时间较长时,还需要另外考虑消费者的 AckWait 或进度通知机制。DoubleAck 的 context 限制本次确认等待,不能代替业务执行期间的超时与重投管理。

相关问题

DoubleAck 是不是把业务处理两次

不是。“Double”指确认具有服务端回复这一层反馈;业务处理函数依然由应用调用。示例中 persist 只在确认分支之前调用一次。

用了 DoubleAck 就不需要幂等了吗

仍然需要。外部数据库提交与消息确认之间没有因这个方法而自动形成同一事务,进程退出或网络故障仍可能留下需要恢复的中间状态。

可以把它当成发布成功的回执吗

不可以。这里操作的是消费端收到的 jetstream.Msg。发布回执回答消息是否已被流接收,与消费者完成处理后的确认是不同事项。

只想让失败消息重新投递,应该调用哪一个

Ack 与 DoubleAck 都表达成功处理,不应拿来表示业务失败。否定确认及延迟重投应另行研究 Nak、NakWithDelay 和消费者重投配置,不能仅靠切换这两个成功确认方法实现。

参考:官方 Msg 接口;官方 message.go 实现。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>