redisbroker

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package redisbroker 是 Broker 的 Redis 后端:所有"多步读写必须原子"的操作 都收进单段 Lua 脚本执行(宪法 III:终态更新与子任务唤醒同一段脚本收敛), 语义以 memorybroker 为基准,由 brokertest 的 18 条契约统一验收。

关键约定(specs/002-m2-redis/research.md):

  • 第 1 节:认领 = 单段 claim.lua 原子完成,不用 BLMOVE 两步;Dequeue 是 Go 侧轮询循环;
  • 第 2 节:脚本内禁用 TIME/math.random,时间与 ulid 全部由 Go 经 ARGV 注入(fakeclock 生效);
  • 第 3 节:传播在同一段脚本内用工作队列收敛整棵子树;
  • 第 9 节:哨兵错误只在 Lua 明确判定时返回(TGERR: 错误码),网络错误原样透传, 绝不折叠成 ErrLeaseLost/ErrTaskNotFound。

键设计见 specs/002-m2-redis/data-model.md 第 2 节;脚本内用 ARGV[1] 传前缀自行拼键, 因此本后端面向单实例/主从 Redis,不支持 Redis Cluster(键不带 hash tag)。

limiter.go 分布式限流器:实现 taskgate.QueueLimiter,多进程共享同一份配额。 两层独立(与 localLimiter 的分层一致):

  • 并发槽:zset 信号量 tg:sem:{q}(sem_acquire.lua 原子占槽,续期 goroutine 保活, 进程崩溃 → 续期停 → 槽按 TTL 过期自动回收,research 第 6 节);
  • RPS 令牌:redis_rate 的 GCRA(research 第 7 节)。注意 redis_rate 的 Lua 用 Redis 服务器时间(redis.call TIME),属"RPS 走真时钟"的既有豁免(spec FR-018 例外), 与其余脚本"时间全由 Go 注入"的铁律并不冲突:限速要的本来就是物理时间。

键归属:tg:sem:{q} 与 redis_rate 的 rate:* 键都是限流器私有,不属 Broker 数据 (data-model.md 第 2 节),丢了最多是限流短暂失准,不影响任务数据。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Broker

type Broker struct {
	// contains filtered or unexported fields
}

Broker Redis 后端。实现 taskgate.Broker。

func New

func New(opts Options) (*Broker, error)

New 按 Options 建连接并 PING 一次探活(配置错误尽早暴露)。 返回的 Broker 用之前必须先 Init(由 taskgate.New(cfg) 统一调用)。

func (*Broker) Ack

func (b *Broker) Ack(ctx context.Context, id, leaseToken string, result []byte) error

Ack 成功完结:completed + Result + FinishedAt,子任务唤醒在同一段脚本内收敛。

func (*Broker) Cancel

func (b *Broker) Cancel(ctx context.Context, id string) error

Cancel 取消:排队类状态(blocked/pending/retrying)直接 canceled 并同脚本传播; running 只打 cancel_requested 标记(终态由 FinishCanceled 落库); 终态报 ErrAlreadyFinal,不存在报 ErrTaskNotFound。Cancel 不带令牌(合同)。

func (*Broker) Close

func (b *Broker) Close() error

Close 关连接:标记 closed 并广播,让阻塞中的 Dequeue 尽快退出,再关客户端, 最后统一停掉所有已发限流器仍在续期的槽(不然续期 goroutine 会一直空转泄漏)。 顺序讲究:先关客户端再收尾限流器——归还路径的 ZREM 在关掉的连接上失败被容忍, Redis 里的槽按 TTL 自然过期回收,语义与"进程崩溃"一致(槽不许被 Close 抢先删掉, 别的进程要等 TTL 才能占,这正是崩溃自愈的既定口径)。

func (*Broker) Counts

func (b *Broker) Counts(ctx context.Context) (map[string]map[taskgate.Status]int64, error)

Counts 出现过的 Type×Status 稀疏矩阵:直接 HGETALL tg:stats(每段 Lua 流转时 顺手 HINCRBY 维护),O(矩阵大小),与逐个 Get 汇总一致(brokertest 契约 13 验证)。 流转来回抵消后可能留下 0 值字段,按"只含非零"的合同过滤掉。

func (*Broker) Dequeue

func (b *Broker) Dequeue(ctx context.Context, queues []string) (*taskgate.Task, error)

Dequeue 阻塞认领:直到某队列出现"status∈{pending,retrying} 且 run_at≤now"的任务, 或 ctx 取消(返回 ctx.Err())。循环结构与 sqlitebroker 完全同构: 试认领一次(claim.lua 原子)→ 无果则挂起等待,三个唤醒源:

  • 同进程写入踢的内部信号(Enqueue/Ack/Fail/... 后 wakeAll);
  • 注入 clock 的到点信号:等 min(100ms, 最近的 delayed 到期时刻 - now), 延迟任务到点自动醒,100ms 同时兜底跨进程写入(fakeclock 下不推时间就纯挂起,不空转);
  • ctx 取消。

func (*Broker) Enqueue

func (b *Broker) Enqueue(ctx context.Context, t *taskgate.Task) error

Enqueue 入队。查重、父校验、初始状态判定、落库全部在 enqueue.lua 一段脚本内原子完成; ID 由 Go 生成注入(脚本内禁 math.random),生成的 ID 与判定结果只在成功后回填 *t—— 报错路径不能让调用方拿到一个根本不存在的孤儿 ID(合同 Enqueue 条款)。

func (*Broker) Fail

func (b *Broker) Fail(ctx context.Context, id, leaseToken, errMsg string, kind taskgate.FailKind, retryAt time.Time) error

Fail 失败路径:按 FailKind 动对应计数,封顶或耗尽进 failed(同脚本连锁传播), 否则进 retrying、RunAt=retryAt 到点重跑。

func (*Broker) FinishCanceled

func (b *Broker) FinishCanceled(ctx context.Context, id, leaseToken string) error

FinishCanceled worker 响应取消后收尾:running → canceled 落库并同脚本传播。

func (*Broker) Get

func (b *Broker) Get(ctx context.Context, id string) (*taskgate.Task, error)

Get 取单个任务。hash 解码出来的就是全新副本,调用方改了不影响存储。

func (*Broker) Heartbeat

func (b *Broker) Heartbeat(ctx context.Context, id, leaseToken string) error

Heartbeat 续租:lease_until = now + TTL(hash 与 inflight score 一起续)。 发现取消标记时**续租已成**才返回 ErrTaskCanceled,scheduler 收到后 cancel handler ctx。

func (*Broker) Init

func (b *Broker) Init(opts taskgate.BrokerOptions) error

Init 装配运行参数,零值补默认(TTL 60s / LeaseLostMax 3 / ThrottledMax 100 / 真时钟)。

func (*Broker) List

func (b *Broker) List(ctx context.Context, f taskgate.Filter) ([]*taskgate.Task, error)

List 按 Filter 过滤,零值字段不过滤;先过滤 → 内存按 (CreatedAt, ID) 升序 → 切 [Offset, Offset+Limit)(排序分页合同见 broker-contract.md,Offset 越界返回空)。 候选 ID 从索引集合取(Status 优先、其次 Type,否则七个状态集合并集), 剩余条件读回 hash 后在 Go 侧过滤。代价 O(候选集):分页不减少取回条数, 大库存用 Filter 缩小候选集(已知限制,research 第 5 节裁决,不建 zset 二级索引)。

func (*Broker) QueueLen

func (b *Broker) QueueLen(ctx context.Context, queue string) (int, error)

QueueLen 队列积压:status∈{pending,retrying} 的数量(不看 RunAt 到没到点)。 排队中的任务要么在 pending list、要么在 delayed zset,恰好各占一处 (离开排队状态时由各脚本立刻摘除),所以 LLEN+ZCARD 就是精确值,O(1)。

func (*Broker) QueueLimiter

func (b *Broker) QueueLimiter(queue string, qc taskgate.QueueConfig) (taskgate.QueueLimiter, error)

QueueLimiter 为队列构造跨进程共享的限流器(taskgate.LimiterProvider 能力接口)。 槽 TTL 复用该队列的 LeaseTTL(任务租约和槽租约同生命周期,好理解;research 第 6 节)。

func (*Broker) QueueQuota

func (b *Broker) QueueQuota(queue string, qc taskgate.QueueConfig) (taskgate.QuotaGate, error)

QueueQuota 构造该队列的配额闸(taskgate.QuotaProvider 能力实现)。

func (*Broker) ReapExpired

func (b *Broker) ReapExpired(ctx context.Context) (int, error)

ReapExpired 回收过期租约(lease_until < now 严格小于,压线不算过期): 带 cancel_requested 的直接落 canceled(不占 LeaseLost,传播);其余 LeaseLost+1, 封顶进 failed(固定文案,传播)否则回 pending 清令牌;顺带防御修复 "blocked 但父实际全部终态"的任务。整个过程在 reap.lua 一段脚本内完成, 返回值只算租约回收条数。

func (*Broker) Replay

func (b *Broker) Replay(ctx context.Context, req taskgate.ReplayRequest) (*taskgate.Task, error)

Replay 重放一次终态执行(spec 005):定位、校验、创建全部在 replay.lua 一段脚本内 原子完成——并发同目标重放天然串行,恰好一个成功。新执行 ID 与 now 由 Go 注入 (脚本内禁 TIME/math.random,fakeclock 才有效)。目标记录对外零改写 (脚本内部的 replayed 标记是链元数据,decodeTask 不外露)。

func (*Broker) Requeue

func (b *Broker) Requeue(ctx context.Context, id, leaseToken string) error

Requeue 优雅停机时归还任务:running → pending,三计数与 RunAt 全不动, 清租约和取消标记。这不算失败,一个计数都不许占(合同要求)。

type Options

type Options struct {
	Addr     string // Redis 地址,如 "127.0.0.1:6379"
	Password string // 密码,空 = 不认证
	DB       int    // 库号
	// KeyPrefix 所有键的前缀,默认 "tg:";多应用共用一个 Redis 时用它隔离。
	// 例外:RPS 限速状态键不在本前缀命名空间内——redis_rate 自己会再加 "rate:"
	// 前缀,最终键形如 "rate:<KeyPrefix><queue>";按前缀批量清理/统计时别漏了它。
	KeyPrefix string
}

Options 连接参数。库不读环境变量,应用自己填好传进来(宪法 I)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL