更新日志
Snake
所有版本变更记录。格式基于 Keep a Changelog。
[0.0.5] - 2026-08-26
新增
- 自动 WorkerID 推导在启动日志中打印推导结果与来源 IP,便于撞槽排查
变更
- 跨毫秒时序列号由重置为 0 改为随机偏移起始:两个副本自动推导的 WorkerID 撞车时,同毫秒内不再产出重复 ID
修复
Generator将(timestamp, sequence)打包进单个 64 位原子字整体 CAS,消除旧版两步原子操作中 timestamp 已推进、sequence 尚未重置导致的进程内重复 ID 竞态Validate()拒绝(timestamp << SequenceBits)打包状态会溢出 63 位原子字的SequenceBits配置(配合新版Generator的打包 CAS)
[0.0.4] - 2026-06-19
新增
Validate()新增位宽校验:WorkerIDBits + SequenceBits超过 63 时返回错误,避免时间戳字段溢出- 新增完整单元测试,覆盖率达 97.8%(含并发唯一性、时钟回拨、序列号溢出、位宽边界等场景)
- 新增 CI 工作流(
.github/workflows/ci-snake.yml),集成 Codecov 覆盖率上报
优化
- 移除
CalculateWorkerID中两处死代码(fnv.Write永不返回错误;取模运算保证 workerID 始终在有效范围内)
[0.0.3] - 2026-06-04
依赖升级
github.com/zeromicro/go-zerov1.10.0 → v1.10.2go.opentelemetry.io/otel*v1.24.0 → v1.40.0(indirect,主动不升 v1.44+ 避免强制要求 go 1.25)- 同步
go mod tidy清理无用 indirect 依赖 godirective 保持 1.24.0
[0.0.2] - 2026-03-20
- 升级 Go 版本至 1.24.0
- 升级 go-zero 至 v1.10.0
- 更新依赖版本
[0.0.1] - 2026-01-26
- 项目初版发布
Cron
所有版本变更记录。格式基于 Keep a Changelog。
[0.1.2] - 2026-08-06
变更
- 定时任务去重从固定
TaskID改为asynq.Unique:原方案以 task type 作为固定TaskID,而 asynq 判定 ID 冲突的唯一依据是 task key 是否存在,这个 key 的寿命由上一次执行的结局决定(成功即删、带Retention时按保留时长、归档默认 90 天、还在跑时按执行时长),没有一种情况等于定时周期。现在改注入asynq.Unique(TTL),其底层是一把 RedisSET NX EX锁,键由队列名 + task type 决定,TTL 是相对时长,天然与周期对齐。- TTL 由 spec 与实际超时共同推导:
TTL = max(最短触发间隔 − 1s, 实际超时, 1s),无需调用方指定 TTL ≥ 实际超时保证「上一次还在跑就投不进第二份」;TTL < 最短触发间隔(超时小于间隔时)保证一次失败最多堵不足一个周期- 实际超时从
opts中的asynq.Timeout反查,未显式传入时按 asynq 的 30 分钟默认值计算 - 行为变化:重叠执行的保护窗口从「上一次任务在世期间」(可长达 90 天)变为「一个 TTL」。超时不小于触发间隔时锁会活过下一个 tick,该 tick 被跳过,这是「任务可能跑得比周期还久」应有的语义
- 显式传入的
asynq.Unique会被覆盖
- TTL 由 spec 与实际超时共同推导:
修复
- 一次执行失败不再导致定时任务永久停摆:归档任务不再参与去重判定,去重锁自带有界过期(asynq 归档任务时不清理该锁),失败后最多堵住一个 TTL,下一个 tick 即恢复
- 到点入队失败不再静默:
SchedulerOpts.PostEnqueueFunc原先只统计入队成功的情况,且未设置EnqueueErrorHandler,入队失败时 asynq 直接返回,既无日志也无指标。现在失败会同时打日志和记指标 CronAdd不再写进调用方opts的空余容量:原先直接在入参opts上append队列与去重选项,调用方传opts...且该 slice 有空余容量时,会踩进它的底层数组。现在先复制再追加。变参直接调用(包括cztctl生成的workers.go)cap == len,不受影响
新增
- 指标
cron_server_scheduler_enqueue_error_total{task_type, reason}:定时任务到点入队失败次数。reason=duplicate是多副本部署下的正常结果(每个 tick 有副本数 −1 次),只记 debug 日志;reason=other(如 Redis 不可用)响亮报错,是发现该类故障的唯一入口
依赖
github.com/zeromicro/go-zerov1.10.2 → v1.10.3github.com/redis/go-redis/v9v9.20.0 → v9.21.0(随 go-zero v1.10.3 的最低要求上浮)github.com/robfig/cron/v3v3.0.1 从 indirect 提为 direct:用于解析 spec 推导最短触发间隔,解析器与 asynq Scheduler 内部一致github.com/alicebob/miniredis/v2v2.38.0 从 indirect 提为 direct:单元测试直接依赖go mod tidy清理 22 项不再需要的 indirect 依赖(grpc、grpc-gateway、otel exporters、pyroscope、zipkin 等)- 有意保持
go.opentelemetry.io/otel*在 v1.40.0(v1.44+ 强制 go 1.25,为兼容性不升级) go指令保持 1.24.0
[0.1.1] - 2026-06-04
依赖升级
github.com/zeromicro/go-zerov1.10.0 → v1.10.2github.com/redis/go-redis/v9v9.17.3 → v9.20.0- 同步
go mod tidy清理无用 indirect 依赖 - 主动保留
go.opentelemetry.io/otel*v1.40.0(v1.44+ 强制要求 go 1.25,为兼容使用方不升) godirective 保持 1.24.0
[0.1.0] - 2026-06-04
破坏性变更 (Breaking)
CronAdd签名调整:新增必传参数handler HandlerFunc,一步完成调度注册与 handler 注册,避免同一 task type 在CronAdd/Add两处重复书写- 旧用法:
CronAdd(spec, pattern, opts...)+Add(pattern, handler) - 新用法:
CronAdd(spec, pattern, handler, opts...)
- 旧用法:
- 定时任务超时改由 go-zero
RestConf.Timeout接管:v0.1.0 之前定时任务未注入超时,行为回落到 asynq 默认 30 分钟硬编码常量;v0.1.0 起推荐在workers.go将serverCtx.Config.Timeout(go-zerorest.RestConf.Timeout)通过asynq.Timeout(d)注入CronAdd,使 handler 收到的ctx自带Deadline,与 go-zero HTTP 超时管控统一- cztctl 模板将在后续版本同步联动,业务方升级时需手动调整
workers.go,详见迁移指引
- cztctl 模板将在后续版本同步联动,业务方升级时需手动调整
迁移指引
API 签名迁移
- serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB")
- serverCtx.CronServer.Add("GDemoB", demoA.GDemoBHandler(serverCtx))
+ serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB", demoA.GDemoBHandler(serverCtx))接入 go-zero 超时管控
+ import (
+ "time"
+ "github.com/hibiken/asynq"
+ )
+
func RegisterHandlers(server *service.ServiceGroup, serverCtx *svc.ServiceContext) {
+ var taskOpts []asynq.Option
+ taskOpts = append(taskOpts,
+ asynq.Timeout(time.Duration(serverCtx.Config.Timeout)*time.Millisecond),
+ asynq.MaxRetry(0),
+ )
- serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB", demoA.GDemoBHandler(serverCtx))
+ serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB",
+ demoA.GDemoBHandler(serverCtx), taskOpts...)
server.Add(serverCtx.CronServer)
}
rest.RestConf.Timeout默认3000ms,对 IO 密集型定时任务偏短,建议在 yaml 中显式调大(如Timeout: 30000)。
新增
CronAdd/Client.Push*可通过asynq.Timeout(d)opt 为单个任务设置任意时长超时(不受 asynq 默认 30 分钟限制),例:CronAdd("*/1 * * * *", "X", h, asynq.Timeout(time.Hour))- README 进阶指南新增「任务超时控制」章节,详述定时任务与投递任务的超时注入机制
修复
ExponentialRetryDelay公式修正为2^(n+1) - 1:asynq 传入的n为上次已重试次数(0-indexed),原公式2^n - 1在第 1 次重试时返回 0s,导致首次重试无退避;修正后重试序列稳定为1s, 3s, 7s, 15s, 31s, ...
[0.0.9] - 2026-06-04
新增
- 新增
SetBaseContext(ctx context.Context)方法,支持注入基础上下文,所有任务 handler 的 ctx 将以此为父级
变更
asynq.Server延迟到Start()时创建,支持在构造后、启动前动态配置上下文
[0.0.8] - 2026-06-04
新增
- 新增
GetTaskID、GetRetryCount、GetMaxRetry、GetQueueName包装函数,Handler 层无需直接依赖 asynq 即可获取任务元信息
变更
- README.md 全面重构:完整示例按 go-zero / 独立脚本 + Server / Client 四象限组织
- 进阶指南新增任务分组聚合、放弃重试用法示例
- 补充
asynq.SkipRetry包装注意事项(pkg/errors不兼容) - 监控指标章节标题层级修正
[0.0.7] - 2026-06-04
新增
- 新增
WithGroupAggregatorServerOption,支持注入任务分组聚合器,使 Group 分组功能可用 - 新增
WithRetryDelayFuncServerOption,支持自定义重试延迟策略 - 新增
ExponentialRetryDelay内置重试策略(2^n - 1秒:1s, 3s, 7s, 15s, 31s...) CronAdd自动以realPattern作为 TaskID,多实例部署时定时任务不会重复投递
修复
- 移除
ServerConfig.Validate()中GroupGracePeriod的死代码校验(int64 不可能 >0 且 <1)
变更
GroupMaxDelay默认值从 0(无限制)调整为 300 秒(5分钟),防止任务持续到来时永不聚合- Server 默认重试策略从 asynq 内置的
n⁴指数退避改为ExponentialRetryDelay(2^n - 1秒) Add日志统一为[CRON] Worker registered: %s
[0.0.6] - 2026-05-20
变更
metrics.go中log.Printf替换为logx.Errorf,统一使用 go-zero 日志规范- 移除
LoggingMiddleware,避免与 asynq 内部错误日志重复 - 移除
RecoveryMiddleware中冗余的logc.Errorf,asynqperform已内置 panic recovery 日志
[0.0.5] - 2026-05-15
新增
- 新增完整监控指标体系
- Server 端:
cron_server_consume_total、cron_server_consume_duration_ms、cron_server_consume_bytes、cron_server_active_workers、cron_server_retry_total、cron_server_skip_retry_total、cron_server_panic_total、queue_groups、tasks_aggregating_total、cron_scheduler_trigger_total、cron_scheduler_registered、cron_tasks_enqueued_total、cron_queue_size、cron_queue_latency_seconds - Client 端:
cron_client_push_total、cron_client_push_duration_ms、cron_client_push_bytes、cron_client_cancel_total
- Server 端:
- 新增队列白名单过滤,解决多服务共用 Redis 时指标混杂问题
- Client 默认不重试(MaxRetry=0),用户可通过 opts 覆盖
变更
- 监控指标命名空间从
asynq_改为cron_ - Server 端指标添加
serversubsystem
[0.0.4] - 2026-04-10
变更
- 升级 Go 版本至 1.24.0
- 升级 go-zero 至 v1.10.0
- 更新依赖版本
ConfigCenter (Consul)
所有版本变更记录。格式基于 Keep a Changelog。
[0.1.2] - 2026-06-04
依赖升级
github.com/zeromicro/go-zerov1.10.0 → v1.10.2- 同步
go mod tidy清理无用 indirect 依赖 - 主动锁定
github.com/hashicorp/consul/apiv1.25.1(v1.33+ 强制要求 go 1.25,为兼容 go 1.24 不升) godirective 保持 1.24.0
[0.1.1] - 2026-03-20
- 升级 Go 版本至 1.24.0
- 升级 go-zero 至 v1.10.0
- 更新依赖版本
[0.1.0] - 2025-11-26
- 项目初版发布
RegisterCenter (Consul)
所有版本变更记录。格式基于 Keep a Changelog。
[0.1.7] - 2026-06-04
依赖升级
github.com/zeromicro/go-zerov1.10.0 → v1.10.2google.golang.org/grpcv1.65.0 → v1.80.0go.opentelemetry.io/otel/tracev1.24.0 → v1.40.0(indirect)- 同步
go mod tidy清理无用 indirect 依赖 - 主动锁定
github.com/hashicorp/consul/apiv1.25.1(v1.33+ 强制要求 go 1.25,为兼容 go 1.24 不升) godirective 保持 1.24.0
[0.1.6] - 2026-03-20
- 升级 Go 版本至 1.24.0
- 升级 go-zero 至 v1.10.0
- 更新依赖版本
[0.1.5] - 2025-12-01
- 优化版本
[0.1.3] - 2025-12-01
- 优化版本
[0.1.2] - 2025-12-01
- 优化版本
[0.1.1] - 2025-12-01
- 优化版本
[0.1.0] - 2025-12-01
- 项目初版发布
RabbitMQ
所有版本变更记录。格式基于 Keep a Changelog。
[0.2.0] - 2026-08-03
整库重新设计。0.1.6 未发版,其内容已并入本版本。
所有新增配置字段均有默认值,配置中心里的现有配置无需任何改动即可升级。
破坏性变更
1. 拦截器抽象统一为中间件
删除 Interceptor / SenderInterceptor / SenderFunc / SenderChain,改为一套 Meta / Handler / Middleware / Chain:
-type Interceptor func(ctx context.Context, queueName string, message []byte, next func(context.Context, []byte) error) error
-type SenderInterceptor func(ctx context.Context, exchange, routeKey string, msg []byte, next SenderFunc) error
+type Handler func(ctx context.Context, meta Meta, body []byte) error
+type Middleware func(next Handler) Handler说明:原 Chain 与 SenderChain 逻辑完全一致,仅因签名不同而复制。Meta 统一描述消息坐标(消费侧填 Queue,投递侧填 Exchange 与 RouteKey)后,两组同构类型合为一套。当前无第三方使用者,破坏面仅限自定义拦截器。
新增中间件注入点 WithConsumeMiddlewares / WithPublishMiddlewares:拦截器链此前硬编码在构造函数内,调用方无法追加。
2. Send 阻塞等待 confirm,错误细分为五类哨兵
err := sender.Send(ctx, exchange, routeKey, body)
switch {
case errors.Is(err, rabbitmq.ErrPublishUnroutable): // routeKey 或绑定配置错误
case errors.Is(err, rabbitmq.ErrPublishNacked): // broker 拒绝,框架内已重试
case errors.Is(err, rabbitmq.ErrPublishTimeout): // 无法判断是否已达 broker
case errors.Is(err, rabbitmq.ErrPublishUnknown): // 确认到达前 channel 已关闭,同属结果未知
case errors.Is(err, rabbitmq.ErrSenderClosed): // Sender 已关闭
}说明:原实现下 PublishWithContext 返回 nil 仅代表写入 socket,[RABBITMQ_SEND_OK] 日志并不成立。现在返回 nil 即代表 broker 已确认收下。代价是延迟上升、吞吐下降,由 PoolSize 补偿。
哨兵的划分只按一条界线:投递结果是否确定。ErrPublishNacked 与 ErrPublishUnroutable 均已收到 broker 的完整回应,框架因此能自行决断——前者退避重试,后者不重试因为结果必然相同。ErrPublishTimeout 与 ErrPublishUnknown 都没等到确认,消息可能已落地也可能没有,二者均不重试:重投会在调用方无感的情况下造成重复投递,能否容忍只有业务知道。ErrPublishUnknown 虽与 ErrPublishTimeout 同命,仍单独成类,因为二者的排查方向完全相反:超时指向 PublishTimeout 配置与 broker 负载,channel 断开指向网络与连接稳定性。合为一类会让 timeout 指标上涨时把人引向错误的修法。
3. 投递默认持久化 + mandatory
说明:原实现未设 DeliveryMode,而 QueueConf.Durable 默认为 true——典型的半持久化陷阱:broker 重启后 durable 队列尚存,消息全丢。mandatory: false 则让 routeKey 打错时被 broker 静默丢弃且 confirm 仍返回 ack。现在 routeKey 无匹配队列会返回 ErrPublishUnroutable 而非静默成功。
4. 删除 ConsumerConf.NoLocal、ChannelQosConf.Global 与 RabbitListenerConf.ContentType
ChannelQos:
PrefetchCount: 10
- Global: false
ListenerQueues:
- Name: demoA
- NoLocal: true说明:RabbitMQ 从未实现 no-local,服务端对该标志静默忽略——保留反而更危险:业务以为「已禁止消费自己推送的消息」,实际仍会收到。ContentType 在消费端配置中为死字段。Global 同样是个假旋钮:该标志只在同一 channel 内区分预取额度的归属(每 consumer 各一份,还是整个 channel 共享一份),从不跨 channel;而本库每队列独占 channel、且其上恰好一个 consumer,两种取值下额度归属完全相同——看上去能调流控,实际调不了。Qos 现固定传 false。上述字段删除后,多余的 yaml 键被忽略,存量配置不受影响。
5. Admin 新增 NewAdmin 与 Close
-admin := rabbitmq.MustNewAdmin(conf)
+admin, err := rabbitmq.NewAdmin(conf)
+if err != nil {
+ return err
+}
+defer admin.Close()说明:原实现无 Close,每次调用泄漏一条 TCP 连接;且打开 channel 失败时 log.Fatalf,连接不会被关闭。MustNewAdmin 保留。
6. MustNewListener 返回自有 Listener 接口
-func MustNewListener(c RabbitListenerConf, handler ConsumeHandler) queue.MessageQueue
+func MustNewListener(c RabbitListenerConf, handler ConsumeHandler, opts ...ListenerOption) Listener说明:Listener 的方法集(Start() / Stop())与 go-zero 的 queue.MessageQueue 完全一致,因此仍可直接交给 service.ServiceGroup 托管,listeners.go 与 ServiceContext 无需改动。改动只为去掉公开签名对 go-zero 类型的依赖:本模块并未使用 queue 包的其他任何能力,暴露该类型会让调用方误以为存在 go-zero 的队列语义。同时新增 NewListener,与 NewSender 口径一致——原实现只有 Must 版本,配置错误只能靠 panic 暴露。
7. 链路上下文从消息信封改走 AMQP header
RabbitMsgBody 删除,Meta 新增 Headers:
-type RabbitMsgBody struct {
- Carrier *propagation.HeaderCarrier // OpenTelemetry 链路头
- Msg []byte // 业务消息内容
-}
+type Meta struct {
+ Queue string
+ Exchange string
+ RouteKey string
+ Headers map[string]any // 链路上下文由此透传
+}生产端将链路上下文注入 Meta.Headers,最终落到 amqp.Publishing.Headers;消费端从 amqp.Delivery.Headers 提取。两端都不再触碰消息体,因此 rabbitmq_listener_parse_error_total 随信封一同删除——已不存在会解析失败的东西。
说明:把链路上下文塞进消息体,正是本模块只能与自己互通的根源。改由 header table 携带——OpenTelemetry 的消息语义约定对 RabbitMQ 正是如此规定——一次解决四件事:他方系统投递的消息可以消费了(见下文对应的修复项),消息体不再为多一层 JSON 与 encoding/json 对 []byte 字段的 33% base64 膨胀付费,链路真正能跨语言延续,管理界面里的消息体也可读了。
这是一次传输格式变更。 0.2.0 的消费端不会拆 0.1.x 的信封,而是把信封 JSON 原样交给 handler。因此共用同一队列的生产端与消费端需一同升级,且升级前应先把队列里信封形态的消息消完。
8. 连接类指标新增 endpoint 与 vhost label
rabbitmq_sender_reconnect_total、rabbitmq_sender_disconnect_total、rabbitmq_listener_reconnect_total、rabbitmq_listener_disconnect_total 原本完全没有 label,现统一携带 endpoint(host:port)与 vhost。本版本新增的两项池指标(rabbitmq_sender_pool_wait_duration_ms、rabbitmq_sender_pool_in_use)采用同一对维度。
理由:同一进程内可以存在多个 Sender 与 Listener,连的可能是不同集群,无维度的计数器会把它们全部堆到一条曲线上,报警时无从分辨是哪个 broker 在抖。vhost 未配时归一为 AMQP 默认值 /,避免同一个 vhost 因写法不同(省略或显式写 /)分裂成两条曲线。两个 label 均不含凭据:指标会被拉取到监控系统并长期留存。
这会改变既有曲线的基数。 查询上述四项指标的看板与报警规则需补上聚合(sum without (endpoint, vhost) (...) 或等价写法),否则升级前的单条曲线会变成按 broker 拆分的多条。
新增
- 单队列消费并发:
ConsumerConf.Concurrency(默认 1,上限 256)。等于 1 为严格顺序模式,大于 1 启动对应数量的 worker 并行处理。须满足Concurrency <= ChannelQos.PrefetchCount - 处理超时:
ConsumerConf.Timeout(默认 0 不限)与WithConsumeTimeout(d)option。原实现的中间件链根为裸context.Background(),全程无 deadline。被 go-zero 托管时在 handler 中显式注入宿主的RestConf.Timeout - TLS:
RabbitConf.TLS,支持单向与双向认证。原实现硬编码Scheme: "amqp" - 心跳:
RabbitConf.Heartbeat(默认 30 秒),用于探测死链触发重连 - 投递调优:
RabbitSenderConf新增PoolSize(10) /PublishTimeout(5000) /MaxRetry(3) - 优雅停机上限:
RabbitListenerConf.GracefulTimeout(默认 10000 毫秒) - 投递侧优雅关闭上限:
RabbitSenderConf.GracefulTimeout(默认 10000 毫秒),与消费侧字段口径对称——Close等待在途投递收到 broker 确认的上限 - 中间件注入:消费侧
WithConsumeMiddlewares(...)、投递侧WithPublishMiddlewares(...),追加位置均在内置链之后,受超时约束。投递侧位于重试编排之外,因此一次Send只执行一次,不随重试重复触发 - 投递侧 option:
WithPublishTimeout(d),与消费侧WithConsumeTimeout(d)对称,供宿主框架统一注入超时,优先级高于RabbitSenderConf.PublishTimeout - 5 项指标:
rabbitmq_sender_confirm_duration_ms、rabbitmq_sender_pool_wait_duration_ms、rabbitmq_sender_pool_in_use、rabbitmq_sender_retry_total、rabbitmq_listener_in_flight - 测试与 CI:单元测试(Validate 全分支、
Chain组合顺序、超时中间件、状态机迁移、channel 池借还)、集成测试(//go:build integration,真 broker)、.github/workflows/ci-rabbitmq.yml。本模块此前是唯一没有 CI 的模块
修复
- 并发
Send共享单 channel:amqp091-go的Channel非线程安全,原实现下所有并发投递共用一条 channel,协议帧交错会导致 channel 被 broker 关闭。改为 channel 池承载,池内每条 channel 同时只处理一条在途消息——NotifyReturn不携带 deliveryTag,这也是正确归因不可路由退回的前提 - Sender 的
conn/channel数据竞争:原实现下Send裸读q.channel,reconnect裸写q.channel = nil,Sender 无任何锁,可导致数据竞争与 nil dereference panic。现由读写锁保护,投递路径只读走读锁 - Sender 重连不可中断:原实现固定 10 次 × 2s 死等,与 Listener 的指数退避两套哲学,且无法被停机信号中断。现统一为指数退避 + 可中断
Admin泄漏连接:见上文破坏性变更 5- WaitGroup 跳代复用 panic 风险:
listenerWg/taskWg改为按代次分配。原实现下 reconnect 等待超时后 waiter 仍挂在Wait()上,新代对同一 wgAdd,计数器归零时可触发sync: WaitGroup misuse直接打挂进程(触发条件:handler 卡死 + 连接抖动) Stop最坏耗时翻倍:reconnect 的「等旧协程退出」改为含停机信号的多路 select。原实现用裸time.After,重连中收到停机需先等满 reconnect 的GracefulTimeout再等Stop自己的,容易超出停机宽限期- 他方投递的消息被静默丢弃:
consumerTrace无条件将每条消息体按信封反序列化,解析失败则返回 error,而「已执行的一律确认」规则又会把它变成 ack。于是 Java 服务投递的、或从管理界面手工投递的消息,handler 根本没被调用就被丢掉。现在消息体完全不再被解析 - 中间件链自身 panic 使队列静默停摆:
handle的 panic 兜底位于 ack 决策之外,panic 后确认语句整体被跳过,该消息既不 Ack 也不 Reject,永久占住一个 prefetch 名额——累计到PrefetchCount条后该队列彻底停止收消息,而日志里只有 panic、没有任何停摆信号,直到重连或停机才释放。触发面不窄:WithConsumeMiddlewares注入的业务中间件位于consumerRecovery之外,任一用户中间件 panic 都走这条路。现将兜底下沉一层(invoke)就地转为 error,ack 必然执行,与「已执行的一律确认」口径一致 ErrPublishUnknown不说明原因:确认通道被关闭时只报一句「结果未知」,不带任何归因。而最常见的配置错误——exchange 名字打错——恰恰走这条路径,broker 给出的404 NOT_FOUND只出现在 channel 关闭通知这一路上。现pubChannel注册NotifyClose,把 broker 给出的 code 与 reason 拼在错误末尾,最该可诊断的场景不再最不可诊断- 停机把在途投递变成
ErrPublishUnknown:Close不做等待直接拆连接,已把帧写出的投递其确认通道随之被关闭——而停机钩子是NewSender自注册的,等于每次进程退出都在成批制造「结果未知」。现Close先在GracefulTimeout内等在途投递收到确认,再销毁 channel;窗口耗尽仍有未确认的,记一行[RABBITMQ_STOP] Close之后的Send干等到投递超时:借用 channel 只在「空闲槽位」与「调用方 ctx」两路上等待,池关闭后槽位已被收走且不会再回填,这条投递只能干等到PublishTimeout,最后报一个误导的「超时」。现借用同时在池的停机信号上等待,立即返回ErrSenderClosed
变更
Validate统一为三条规则,每个字段只按一条处理:必填缺失或取值不可能正确 → 返回 error;存在安全默认值的非法值 → 回退默认并记录一行日志;超出协议或资源上限 → 返回 error。原实现混用「报错」与「静默回退」两种口径- 首次连接只尝试一次:配置错误应在启动时立刻暴露,运行期的连接故障才由重连负责自愈。Sender 与 Listener 口径一致
- 超时配置只在调用方未给出时限时兜底:
timeoutMiddleware先判断传入ctx是否已带 deadline,已带则原样透传,不再无条件context.WithTimeout包一层。后者取「父 deadline 与配置值中较早者」,会把调用方显式要求的更长时限悄悄截断,使配置从兜底变成强制上限。消费侧的消息 ctx 由 Listener 从context.Background()派生,本身无 deadline,行为不受影响;受影响的是Send——调用方 ctx 若已带 deadline(如在 go-zero rest handler 中),PublishTimeout不再介入 - 文件按职责拆分:
conf.go/errors.go/const.go/message.go/middleware.go/metrics.go/conn.go/consumer.go/listener.go/chanpool.go/sender.go/admin.go。原RabbitListener单结构体承担连接管理、channel 生命周期、代次编排、消息分发、Ack 决策 5 类职责,16 个字段中含 4 个同步原语 + 3 个 atomic - Listener 生命周期改为显式状态机:
stateNew → stateRunning → stateStopping → stateStopped,代次由分层 context 表达。原实现的状态判断散落于 7 处closed.Load()与 4 处epoch.Load()
依赖升级
github.com/rabbitmq/amqp091-gov1.11.0 → v1.13.0go.opentelemetry.io/otel/sdk由 indirect 提为直接依赖:链路传播往返测试需驱动真实的TracerProvidergodirective 保持 1.24.0example/独立为自有模块(example/go.mod,以replace指回../):示例依赖配置中心与注册中心,此前与库共处一个模块,在库目录下执行go mod tidy会把 consul 与 etcd 的依赖树写进本模块的 go.mod。独立后库目录下的go mod tidy与go build ./...均不再涉及示例- go.mod 与 go.sum 收敛:indirect 由 42 条降至 20 条,go.sum 由 139 行降至 85 行。清掉的是 0.1.x 遗留、当前已无人 import 的条目(gRPC、Zipkin 与 OTLP exporter、genproto、pyroscope、gonum、k8s.io/utils 等)。实际参与编译的传递依赖数不变(281 个包)——这些残留只存在于 go.mod 的声明里,不影响构建,但会让依赖审计与 SBOM 把一批与本库无关的包算进依赖面
文档
- README 与 readme-cn 按新模型重写,补齐投递语义、消费语义、超时体系、中间件、指标清单与升级指南
- 明确
PrefetchCount不是并发度:它是流控参数(由消息大小与网络 RTT 决定),并发度由Concurrency决定(由下游承载能力决定) - 明确 at-least-once 的代价:重试必然带来重复投递,消费侧必须幂等
- 明确 deadline 是协作式的:handler 必须把 ctx 透传给下游阻塞调用,否则超时不生效
- 明确消费侧与投递侧 ctx 来源不同导致的超时生效差异
与设计文档的偏离
实现过程中有六处与最初的设计方案不同的决定:
- recovery 位于中间件链最内层,设计文档将其列在最外层。置于最外层时 panic 会穿透 metrics 与 logging,导致 panic 的消息不计入
consume_total、也不产生一行日志——而 panic 恰是最需要被观测到的情形 - 未实现
rabbitmq_listener_worker_in_use:该值可由rabbitmq_listener_in_flight除以Concurrency得出,独立 gauge 只会在每条消息的收尾路径上多两次原子写,且两个同源指标一旦漂移会互相矛盾 - 未引入独立的
inflightWg:按代次分配的taskWg已表达「等待在途消息处理完」的语义,再加一个 waitgroup 会让「停机在等谁」变得含糊,而这正是原实现最难读的地方 - 中间件注入点拆为对称的两个:设计文档只给了
WithMiddlewares(仅消费侧)。投递侧同样需要注入点,而单一名字无法表达它作用于哪一侧;两侧的执行语义也不同(投递侧位于重试编排之外,一次Send只执行一次),共用一个名字会掩盖这个差异 - 未改动 cztctl 模板(设计文档 §8 「cztctl 联动」):cztctl 的 handler 模板由远程仓库提供,仓库内的
.tpl并非生成时实际使用的那份,因此在本仓改它不会生效。宿主超时注入改为在文档中给出写法,由调用方在 handler 里显式添加 - 链路上下文由 AMQP header 携带,而非消息信封:设计文档保留了信封(
message.go的职责、以及两个 trace 中间件的职责,均按包裹与拆包描述)。信封使每条被消费的消息都多一次 JSON 解析,而不是信封形态的消息会在没触及 handler 的情况下被确认掉。其所谓的代价——「信封 JSON 解析不受 deadline 约束,但解析为纯 CPU 且有界,可接受」——在已不存在解析的前提下自然失效
迁移
- 无需改动:配置中心的现有配置、
listeners.go(生成文件)、ServiceContext - 需要改动:若曾自定义拦截器;
Send调用方若需区分错误类型;MustNewAdmin的调用点需补defer admin.Close();共用同一队列的生产端与消费端需一同升级,因为链路上下文已移出消息体(破坏性变更 7);查询重连 / 断链指标的看板与报警规则需补聚合(破坏性变更 8) - 建议改动:为存量 handler 补上宿主超时注入,即在
MustNewListener调用处手工添加rabbitmq.WithConsumeTimeout(...)一行(imports 补"time")。cztctl 的 handler 模板由远程仓库提供,本模块不介入;且 handler 文件在生成时已存在则跳过,存量服务无论如何都需手工补 - 注意:
rest.RestConf.Timeout默认仅 3000ms。一旦注入,消息处理就会被隐式限在 3 秒内,消费场景通常需要在 yaml 中显式放大Timeout
[0.1.5] - 2026-06-04
依赖升级
github.com/rabbitmq/amqp091-gov1.10.0 → v1.11.0github.com/zeromicro/go-zerov1.10.0 → v1.10.2go.opentelemetry.io/otel*v1.24.0 → v1.40.0(主动不升 v1.44+,避免强制要求 go 1.25)- 同步
go mod tidy清理无用 indirect 依赖 godirective 保持 1.24.0
[0.1.4] - 2026-03-20
破坏性变更
1. MustNewSender 去掉 ctx 参数
- rabbitmq.MustNewSender(ctx, conf)
+ rabbitmq.MustNewSender(conf)说明: 与 MustNewListener 保持一致,初始化时不需要 ctx
[0.1.3] - 2026-03-20
新增功能
1. 连接可靠性增强
- NotifyClose 监听: Sender 和 Listener 同时监听 Connection 和 Channel 的关闭事件
- 自动重连: 连接断开后自动尝试重连(最多 10 次)
- 线程安全: 使用
atomic.Bool保证closed标志线程安全 - 双重检查: 避免重复重连(加锁前检查 + 加锁后检查)
- 优雅停机: 收到 go-zero proc 停止信号后不再重连
2. Sender 优雅停机
- 自动注册
proc.AddShutdownListener关闭钩子 - 停机时标记
closed,不再重连
3. 监控指标大幅扩展
Sender 指标(5个):
| 指标名 | 类型 | Labels | 说明 |
|---|---|---|---|
rabbitmq_sender_send_total | Counter | exchange, route_key, status | 发送总数 |
rabbitmq_sender_send_duration_ms | Histogram | exchange, route_key | 发送耗时 |
rabbitmq_sender_send_size_bytes | Histogram | exchange, route_key | 消息大小 |
rabbitmq_sender_reconnect_total | Counter | - | 重连次数 |
rabbitmq_sender_disconnect_total | Counter | - | 掉线次数 |
Listener 指标(9个):
| 指标名 | 类型 | Labels | 说明 |
|---|---|---|---|
rabbitmq_listener_consume_total | Counter | queue, status | 消费总数 |
rabbitmq_listener_consume_duration_ms | Histogram | queue | 消费耗时 |
rabbitmq_listener_consume_size_bytes | Histogram | queue | 消息大小 |
rabbitmq_listener_in_flight | Gauge | queue | 当前处理中消息数 |
rabbitmq_listener_parse_error_total | Counter | queue | 解析失败数 |
rabbitmq_listener_panic_total | Counter | queue | Panic 次数 |
rabbitmq_listener_ack_total | Counter | queue, type | ACK/Reject 计数 |
rabbitmq_listener_reconnect_total | Counter | - | 重连次数 |
rabbitmq_listener_disconnect_total | Counter | - | 掉线次数 |
4. 拦截器优化
- 调整顺序为:
recovery → prometheus → logging → trace - 确保所有拦截器都能执行(即使 trace 解析失败)
5. 其他优化
StartConsumerSpan增加 carrier nil 防护- 消息解析移至 traceInterceptor,避免重复解析
- ACK/Reject 逻辑统一放到 processMessage 外层
删除的功能
- 删除
client.go文件 - 删除
parseMessage方法(消息解析移至 traceInterceptor) - 删除
requeueMessage重复消费逻辑
1. 指标名称变更
- mq_consume_total
+ rabbitmq_listener_consume_total
- mq_consume_duration_ms
+ rabbitmq_listener_consume_duration_ms影响: 需要更新 Prometheus 查询和 Grafana 仪表盘
2. client.go 已删除
- client, err := rabbitmq.NewClient(ctx, conf)
+ sender, err := rabbitmq.NewSender(conf)
// 或
+ sender := rabbitmq.MustNewSender(conf)3. Sender 自动注册 shutdown hook
NewSender和MustNewSender会自动注册proc.AddShutdownListener- 在 go-zero 环境下不需要手动调用
Close() - 非 go-zero 环境仍需手动调用
Close()
4. MustNewListener 去掉 ctx 参数
- rabbitmq.MustNewListener(ctx, conf, handler)
+ rabbitmq.MustNewListener(conf, handler)说明: 消费者内部直接使用 context.Background() 派生,外部传入的 ctx 无实际作用
Aliyun Gateway
所有版本变更记录。格式基于 Keep a Changelog。
[0.0.3] - 2026-06-14
修复
MultipartBuilder.File忽略CreateFormFile错误可能导致 nil panic,改为错误积累模式MultipartBuilder.Build()签名变更为(contentType string, body []byte, err error),统一返回构建过程中的错误
优化
uuid.New().String()替换为uuid.NewString(),减少中间 UUID 结构体分配
[0.0.2] - 2026-06-14
优化
sortedQuery改用strings.IndexByte零分配遍历,消除strings.Split的[]string分配sortedQuery消除冗余result切片,改用strings.Builder直接输出;map/slice 预分配容量signOption对 form/multipart body 利用GetBody跳过全量缓冲,避免大文件上传时的无谓内存拷贝[]byte(AppSecret)在NewClient时闭包外预算一次,不再每次签名重复转换strings.Join(signHeaders)提升为包级signHeadersValue,只算一次
修复
signRequest中 Date 和 Timestamp 现共享同一次time.Now(),消除跨毫秒边界导致两者不一致的隐患
[0.0.1] - 2026-06-13
新增
- 阿里云 API 网关 Go 客户端,基于 go-zero httpc 封装,自动完成 HMAC-SHA256 v1 签名
Do方法:结构化请求,支持path/form/json/headertag 自动映射DoRaw方法:原始字节请求,支持文件上传、XML、纯文本等自定义 bodyWithClientOption:注入自定义*http.Client(TLS、连接池等)Parse函数:封装 go-zerohttpc.Parse,接收Do/DoRaw返回的(resp, err),自动解析 JSON 响应并关闭 BodyNewMultipart()/MultipartBuilder:链式构造 multipart/form-data 请求体Conf.Validate()自动去除 Host 尾部多余/- 底层集成 go-zero 熔断器,同一 Host 自动共享
Kong HMAC Auth
所有版本变更记录。格式基于 Keep a Changelog。
[0.0.1] - 2026-06-18
新增
- Kong HMAC Auth Go 客户端,基于 go-zero httpc 封装,自动完成 HMAC 签名,遵循 Kong HMAC Auth 插件 官方规范
Do方法:结构化请求,支持path/form/json/headertag 自动映射DoRaw方法:原始字节请求,支持文件上传、XML、纯文本等自定义 bodyWithClientOption:注入自定义*http.Client(TLS、连接池等)Parse函数:封装 go-zerohttpc.Parse,接收Do/DoRaw返回的(resp, err),自动解析 JSON 响应并关闭 Body- 支持 5 种 HMAC 算法:
hmac-sha1、hmac-sha224(Kong 3.14+)、hmac-sha256、hmac-sha384、hmac-sha512 @request-target伪头支持,值为method /path?query(method 小写)Conf.Headers可自定义参与签名的 header 列表,默认["date", "@request-target"]Digest头自动计算(SHA-256=base64(sha256(body))),支持空 body 零长度摘要,form / multipart 跳过Date头自动注入(GMT 格式),用于 Kong clock skew 校验User-Agent头自动注入(Go-Kong-HmacAuth-Client),可被调用方覆盖Host头注入与签名取值统一使用r.Host,确保签名值与实际发送一致Conf.Algorithm和Conf.Headers支持 go-zerooptional/defaulttag,YAML 配置可省略- 底层集成 go-zero 熔断器、tracing、metrics,同一 Host 自动共享
cztctl
所有版本变更记录。格式基于 Keep a Changelog。
[1.10.11] - 2026-08-03
变更
- 破坏性变更
cztctl api rabbitmq生成的 handler 与 logic 改为面向czt-contrib/mq/rabbitmqv0.2.0+,本版本生成的代码在更早的库版本上编译不过- handler 闭包与 logic 构造函数均新增
meta rabbitmq.Meta参数,业务通过l.meta.Queue判断消息来自哪个队列,一个 handler 挂多队列才得以据此分流 - handler 无条件注入
rabbitmq.WithConsumeTimeout(time.Duration(svcCtx.Config.Timeout)*time.Millisecond),使 MQ 消费与 HTTP 请求共用RestConf.Timeout这一个时限。注意此 option 优先级高于配置文件中的 per-queueListenerQueues[].Timeout,后者在生成的服务里不再生效 - DSL 声明了消息体类型时(如
order.created(GOrderOrderCreatedReq)),由 handler 执行json.Unmarshal,logic 业务方法收*types.XxxReq;未声明类型的 route(如payment.success)生成的业务方法不带任何参数,签名由func(req []byte) error变为func() error,原先直接读字节流的业务代码需改为在 DSL 中声明类型
- handler 闭包与 logic 构造函数均新增
github.com/zeromicro/go-zero升至 v1.10.3,github.com/zeromicro/go-zero/tools/goctl升至 v1.10.2api/swagger依赖 goctl 的api/spec与pkg/parser/api/parser,本次spec.Type与parser.Parse均无签名变化,调用处无需适配- README 中
--version与cztctl env的示例输出已同步刷新(1.10.11、go-zero v1.10.3)
修复
- 修复 DSL 声明消息体类型时生成的代码编译失败:
internal/types在 handler 与 logic 中均被 import 却从未被引用,触发 Go 的未使用 import 检查- 现仅在 route 确实声明了请求类型时才引入
internal/types
- 现仅在 route 确实声明了请求类型时才引入
- 修复报错文本含
%时被扭曲:拼接后的文本被当作格式串传给fmt.Errorf,使Foo%sBar变成Foo%!s(MISSING)Bar;现改走errors.New,原样输出(errorManager.error、Parser.CheckErrors)- 同时消除 Go 1.24 vet 报为错误的
non-constant format string in call to fmt.Errorf,此前它使go vet ./...与go test ./...持红
- 同时消除 Go 1.24 vet 报为错误的
[1.10.10] - 2026-06-18
修复
- 修复
cztctl rpc sdk生成.kong.proto时,proto 文件为 CRLF(Windows)换行导致 rpc 行分号未被去除、注解错位的问题- 根因:
strings.Split(data, "\n")切行后行尾残留\r,使TrimSuffix(";")末尾匹配到\r而非;,分号保留、{错位 - 修复:读取时统一将
\r\n/\r规范化为\n,从源头消除\r对后缀判断的干扰
- 根因:
变更
rpcMethodRe正则改用returns关键字识别 rpc 方法声明,比原\(括号匹配更语义化(有 returns 才是真正的 rpc 方法)
[1.10.9] - 2026-06-18
修复
- 修复
cztctl rpc sdk生成.kong.proto时,rpc 行使用{}空块写法导致注解错位的问题- 原判断
strings.Contains(line, "{")将{}空块误判为已有开括号,导致option (google.api.http)注解游离到块外并多出闭合括号 - 改为先剥除
{}的闭合括号,再用HasSuffix("{")判断,覆盖分号 / 开括号 / 空块三种 rpc 行风格
- 原判断
[1.10.8] - 2026-06-16
新增
cztctl rpc sdk自动生成.kong.proto文件(Kong gRPC-gateway HTTP 注解)- 执行
cztctl rpc sdk时自动在_sdk/目录中生成与 proto 同名的.kong.proto变体 - 自动添加
import "google/api/annotations.proto" - 为每个 rpc 方法生成
option (google.api.http)注解 - 路径规则:
/{ServiceName}/{RpcMethodName} - HTTP 方法统一 POST +
body: "*" - 若 proto 已包含 annotations import 则跳过插入
- 执行
[1.10.7] - 2026-06-04
变更
cztctl api cron生成器适配 cron v0.1.0 新 APICronAdd一体化签名:调用时直接传入 handler,内部完成注册 + 调度,不再需要额外Add- 定时任务默认注入
asynq.Timeout(time.Duration(serverCtx.Config.Timeout) * time.Millisecond),超时由 go-zeroRestConf.Timeout接管 - 多个定时任务时抽取
timeoutOpt局部变量,避免重复书写超时表达式 MaxRetry仍按 per-taskCronRetry配置- 非定时任务(外部投递消费端)保持仅注册 handler,不变
依赖升级
github.com/zeromicro/go-zerov1.10.1 → v1.10.2github.com/zeromicro/go-zero/tools/goctl保持 v1.10.1(上游未跟版发 v1.10.2)
[1.10.6] - 2026-05-27
新增
cztctl rpc sdk— 将 RPC 客户端代码自动生成为独立 Go 模块并发布到 Git 仓库- 自动克隆/初始化 SDK 仓库,支持 HTTPS + Token 认证
- 递归解析 proto import 依赖,自动复制所有相关 proto 文件
- 调用 goctl 生成客户端代码,清理服务端代码仅保留 client/
- 自动执行
go mod tidy整理依赖 - SemVer 版本号管理:自动递增 patch,patch 到 99 自动进位 minor
- Git 自动提交、打标签、推送(分支 + 标签)
- 支持
--remote/--branchgoctl 远程模板透传 - 支持
--multiple多服务模式 - 支持
--goproxy自定义代理 - 支持
--repo-branch指定 SDK 仓库分支(默认 main) - goctl 版本前置检查(未安装为硬错误,版本低为软警告)
[1.10.5] - 2026-04-09
修复
- 修复
cztctl api cron生成 workers.go 时,CronAdd错误包含 handler 参数的问题- 定时任务(
@cron)现在正确生成两行:先Add(pattern, handler)注册处理函数,再CronAdd(cronExpr, pattern, opts...)注册定时调度 - 纯异步任务(无
@cron)仍只生成Add(pattern, handler)
- 定时任务(
- 修复 go.sum 中 goctl 校验和不匹配导致
go install失败的问题
变更
- 升级 goctl 依赖 v1.10.0 → v1.10.1
- 升级 go-zero 依赖 v1.10.0 → v1.10.1
[1.10.3] - 2026-04-07
新增
- .cron 路由名支持
-(横杠)和:(冒号)分隔符,如sync-order、email:send - .rabbitmq 路由名支持
-(横杠)分隔符,如payment-refund、order.pay-callback - 路由名分隔符按文件类型语义隔离:.cron 仅允许
-:,.rabbitmq 仅允许.-
[1.10.2] - 2026-03-22
新增
cztctl api swagger— 从 .api 文件生成 Swagger 2.0 文档- 支持 info 块全量属性映射(title / description / version / host / basePath / schemes 等)
- 支持 @server 注解(tags / summary / prefix / group / deprecated / operationId / authType)
- 支持
validatetag 自动生成参数约束注释 - 支持字段头部多行注释解析
- 支持
useDefinitions模式($ref引用) - 支持
wrapCodeMsg响应包装 - 支持 JSON / YAML 双格式输出
cztctl api cron— 从 .cron 文件生成分布式定时任务服务- 基于 czt-contrib/cron 框架
- 支持内部定时任务(
@cron表达式 +@cronRetry重试) - 支持外部触发任务(无
@cron,通过 asynq.Client.Enqueue 触发) - 生成完整目录结构:etc / config / handler / logic / svc / types / worker / main
- 支持
@doc字符串与 KV 两种写法 - 支持
@server分组(group / tags / summary / middleware) - 支持
--remote/--branch远程模板 - 支持
--style文件命名风格(gozero / go_zero / goZero)
cztctl api rabbitmq— 从 .rabbitmq 文件生成 RabbitMQ 消费者服务- 基于 czt-contrib/mq/rabbitmq 框架
- 支持点分隔队列名称(如
order.created、payment.refund.success) - 支持可选消息参数类型
- 生成完整目录结构:etc / config / handler / logic / svc / types / listener / main
- 支持
--remote/--branch远程模板 - 支持
--style文件命名风格
cztctl env— 环境变量管理- 查看当前环境变量(CZTCTL_OS / CZTCTL_ARCH / CZTCTL_HOME / CZTCTL_CACHE / CZTCTL_VERSION)
- 编辑环境变量(
cztctl env -w KEY=VALUE) CZTCTL_EXPERIMENTAL开关:off使用 ANTLR4 解析器,on使用手写递归下降解析器
- DSL 语法解析
- .cron 和 .rabbitmq 共享基础语法:syntax / info / import / type / @server
- ANTLR4 解析器(默认)+ 手写递归下降解析器(实验性)
- 支持完整类型系统:基本类型、切片、map、指针、嵌套结构体、struct tag
- 支持 import 跨文件类型引用
- 版本号规则:
v<go-zero主版本>.<微版本>(当前基于 go-zero v1.10.0)