A production-oriented Asynq example for Go: Docker Redis, client and worker implementation, weighted and strict priority queues, retries, delayed jobs, Asynqmon dashboard, and multi-replica Kubernetes guidance.
这是一个从零搭建的 Go 异步任务队列示例,适合学习和验证 Asynq、Redis、Docker Compose、后台任务、延迟任务、失败重试、队列优先级、多 Worker 副本与 Asynqmon 可视化监控。
这是一个可直接运行的 Asynq 实验项目:
- Redis 运行在 Docker 中,并开启 AOF 持久化。
- Go client 负责创建普通、延迟、重试和不同优先级的任务。
- Go server 负责消费
critical、default、low三个队列。 - Asynqmon 提供队列、任务、重试、归档和服务端状态的可视化。
- 真实 Redis 集成测试验证严格优先级,不只验证代码能编译。
扩缩容和多实例消费详见 多副本 Worker 部署与消费语义。
项目固定使用 Asynq v0.26.0。Asynqmon 官方发布版较旧,本项目固定到官方仓库已升级 go-redis/v9 的 commit d1b889456d,并在同一个 Go module 中重新编译,使 Inspector 与 worker 最终统一使用 Asynq v0.26.0。
前置条件:Docker、Docker Compose、Go 1.25 或更高版本,以及 curl、jq(完整验证使用)。go.mod 要求使用 Go 1.26.5 工具链,以包含 GO-2026-5856 的标准库修复;较旧 Go 在默认 GOTOOLCHAIN=auto 模式下会自动下载它。
# 1. 启动 Redis
make redis-up
# 2. 终端 A:启动 worker
make server
# 3. 终端 B:启动只读 Web UI
make monitor
# 4. 终端 C:投递完整演示任务
make demo打开 http://127.0.0.1:8081。本机 8080 已被其他 Docker 服务占用,所以本项目使用 8081。
本地只读 Asynqmon 队列概览:
停止 Redis:
make redis-downdocker compose down 会保留 redis-data volume。如需删除实验数据,明确执行 docker compose down -v。
# 一次投递完整场景:9 个优先级任务、1 个失败后重试任务、1 个 30 秒延迟任务
go run ./cmd/client -scenario demo
# 只投递优先级任务;故意按照 low -> default -> critical 的顺序入队
go run ./cmd/client -scenario priority
# 自定义一个任务
go run ./cmd/client \
-scenario single \
-queue critical \
-name payment-001 \
-duration 2s \
-max-retry 5 \
-timeout 10s \
-retention 2h
# 创建延迟任务
go run ./cmd/client -scenario single -name delayed -process-in 30s
# 前两次执行失败,之后成功
go run ./cmd/client -scenario single -name retry-demo -fail-until 2 -max-retry 3默认配置:
ASYNQ_QUEUES=critical=6,default=3,low=1
ASYNQ_STRICT_PRIORITY=false
ASYNQ_STRICT_PRIORITY=false 时,权重总和为 10。在三个队列都持续非空时,worker 理论上约 60% 从 critical、30% 从 default、10% 从 low 取任务。它不是单个任务级别的绝对顺序,而是避免低优先级队列永久饥饿的概率分配。
ASYNQ_CONCURRENCY=1 ASYNQ_STRICT_PRIORITY=true make server严格模式永远先清空最高权重队列,再处理下一队列。高优先级持续有任务时,低优先级可能永久饥饿。make integration 会向真实 Redis 先后写入 low、default、critical,再启动单 worker,并断言实际处理顺序为:
critical, critical, default, default, low, low
权重必须是正整数;空名称、重复名称、0/负数权重会在启动前报错。client 也会拒绝向 ASYNQ_QUEUES 未声明的队列投递,防止任务进入无人消费的队列。
cmd/client 暴露的参数:
| CLI 参数 | 默认值 | 对应 Asynq Option | 说明 |
|---|---|---|---|
-queue |
default |
Queue |
目标队列,必须存在于 ASYNQ_QUEUES |
-max-retry |
3 |
MaxRetry |
首次执行失败后的最大重试次数;总尝试次数最多为 1 + max-retry |
-timeout |
15s |
Timeout |
单次 handler 最长运行时间 |
-deadline-in |
0 |
Deadline |
相对当前时间生成绝对截止时间;0 不设置 |
-process-in |
0 |
ProcessIn |
相对延迟;0 立即进入 pending |
-process-at |
空 | ProcessAt |
RFC3339 绝对执行时间;设置后覆盖 process-in |
-retention |
1h |
Retention |
成功任务在 completed 状态保留时间;0 不保留 |
-unique-ttl |
0 |
Unique |
在 TTL 内按任务类型、payload、queue 去重;0 关闭 |
-task-id |
空 | TaskID |
显式任务 ID;只允许 count=1 |
-count |
1 |
- | 批量创建同类任务的数量 |
-duration |
500ms |
payload | 模拟工作耗时,最大 10 分钟 |
-fail-until |
0 |
payload | 前 N 次 handler 人为失败,用于观察 retry |
Asynq 将 timeout、deadline、调度时间、retention 和 unique TTL 按整秒存储。client 会拒绝这些字段中的亚秒或非整秒值,避免 500ms 变成 0、1500ms 变成 1 秒后悄悄改变语义;模拟工作耗时 -duration 仍支持毫秒。
Asynq v0.26.0 的全部任务 Option:
| Option | 本项目状态 | 关键语义 |
|---|---|---|
Queue |
已暴露 | 不设置时为 default |
MaxRetry |
已暴露 | Asynq 原生默认 25;本项目显式设为 3 |
Timeout |
已暴露 | Asynq 原生默认 30 分钟;与 Deadline 同时存在时取更早者 |
Deadline |
已暴露 | 绝对截止时间 |
ProcessIn |
已暴露 | 相对调度时间;和 ProcessAt 冲突时最后一个 Option 生效 |
ProcessAt |
已暴露 | 绝对调度时间 |
Unique |
已暴露 | best-effort 去重,不等于业务幂等 |
TaskID |
已暴露 | ID 冲突时入队失败 |
Retention |
已暴露 | 只有设置后,成功任务才保留在 completed |
Group |
未暴露 | 需要 server 配置 GroupAggregator;本实验明确禁用聚合,避免任务停在 aggregating |
Asynq 提供的是 at-least-once 执行语义。生产 handler 必须按业务键实现幂等,不能只依赖 Unique。
cmd/server 显式构造完整的 asynq.Config。可配置项:
| 环境变量 | 默认值 | Config 字段 | 说明 |
|---|---|---|---|
ASYNQ_CONCURRENCY |
4 |
Concurrency |
同时执行的 handler 数量 |
ASYNQ_QUEUES |
critical=6,default=3,low=1 |
Queues |
队列及正整数权重 |
ASYNQ_STRICT_PRIORITY |
false |
StrictPriority |
false 加权随机;true 严格优先级 |
ASYNQ_TASK_CHECK_INTERVAL |
1s |
TaskCheckInterval |
所有队列为空时的轮询间隔;过低会增加 Redis 压力 |
ASYNQ_RETRY_DELAY |
0s |
RetryDelayFunc |
0 使用官方指数退避;大于等于 1 秒的整秒值使用固定间隔 |
ASYNQ_SHUTDOWN_TIMEOUT |
15s |
ShutdownTimeout |
优雅停机等待执行中任务的时间 |
ASYNQ_HEALTH_CHECK_INTERVAL |
10s |
HealthCheckInterval |
Redis ping 周期 |
ASYNQ_DELAYED_TASK_CHECK_INTERVAL |
2s |
DelayedTaskCheckInterval |
scheduled/retry 转 pending 的检查周期 |
ASYNQ_GROUP_GRACE_PERIOD |
1m |
GroupGracePeriod |
聚合等待窗口,Asynq 要求至少 1 秒 |
ASYNQ_GROUP_MAX_DELAY |
0s |
GroupMaxDelay |
聚合最大等待;0 表示无限制 |
ASYNQ_GROUP_MAX_SIZE |
0 |
GroupMaxSize |
单组触发聚合的最大任务数;0 表示无限制 |
ASYNQ_JANITOR_INTERVAL |
8s |
JanitorInterval |
清理过期 completed 任务的平均周期 |
ASYNQ_JANITOR_BATCH_SIZE |
100 |
JanitorBatchSize |
每批清理数量,过大可能产生长 Lua 操作 |
ASYNQ_LOG_LEVEL |
info |
LogLevel |
debug/info/warn/error/fatal |
其余回调/对象字段也全部明确设置:
| Config 字段 | 本项目设置 | 说明 |
|---|---|---|
BaseContext |
context.Background |
每个 handler context 的根 |
RetryDelayFunc |
默认 asynq.DefaultRetryDelayFunc |
ASYNQ_RETRY_DELAY>0 时改为固定间隔 |
IsFailure |
err != nil |
非 nil error 计为失败;SkipRetry 仍为失败但直接归档 |
ErrorHandler |
自定义日志函数 | 记录 task ID、queue、type、retry 次数和错误 |
Logger |
nil |
使用 Asynq 默认 logger |
HealthCheckFunc |
自定义日志函数 | Redis ping 失败时记录错误 |
GroupAggregator |
nil |
明确禁用任务聚合;Group 相关时长不会生效 |
| 环境变量 | 默认值 | 说明 |
|---|---|---|
REDIS_ADDR |
127.0.0.1:6379 |
Redis 地址 |
REDIS_USERNAME |
空 | Redis ACL 用户名 |
REDIS_PASSWORD |
空 | Redis 密码 |
REDIS_DB |
0 |
Redis logical DB |
REDIS_DIAL_TIMEOUT |
5s |
建连超时 |
REDIS_READ_TIMEOUT |
3s |
读超时;-1 表示不超时,0 使用客户端默认 |
REDIS_WRITE_TIMEOUT |
3s |
写超时;-1 表示不超时,0 使用客户端默认 |
REDIS_POOL_SIZE |
20 |
最大连接数 |
本地 Redis 只绑定 127.0.0.1,不对局域网暴露。镜像固定为 Redis 7.4.9-alpine 及其 digest,保证实验可复现。AOF 使用 appendfsync everysec,最多可能丢失约 1 秒尚未刷盘的数据;这是本地实验的性能/可靠性折中,不是生产高可用方案。
如需修改配置:
cp .env.example .env
# 修改 .env 后导入当前 shell
set -a; source .env; set +a| 环境变量 | 默认值 | 说明 |
|---|---|---|
MONITOR_ADDR |
127.0.0.1:8081 |
Web UI 监听地址 |
MONITOR_READ_ONLY |
true |
默认禁止 UI 修改任务/队列;本地实验需要管理操作时显式设为 false |
Asynqmon 没有内置认证。本项目强制 MONITOR_ADDR 使用显式 loopback IP,拒绝 0.0.0.0、空 host 和 hostname;HTTP 层还会校验 Host、Origin 和 Sec-Fetch-Site,降低恶意网页攻击本机管理 API 的风险。默认只读;如确需本机管理功能,运行 MONITOR_READ_ONLY=false make monitor。生产共享部署应单独实现带认证、CSRF 防护和 TLS 的管理服务,而不是直接复用这个实验入口。
# 单元测试和编译
make test
# 扫描标准库和 Go 依赖的已知漏洞
make vuln
# 真实 Redis 严格优先级测试
make integration
# 完整验证:Redis + 单测 + 集成测试 + server + client + UI/API
make verify验证脚本不会删除 Redis volume。集成测试使用 DB 15,完整端到端验证强制连接 127.0.0.1:6379 的 DB 14,并覆盖所有 Redis/worker 环境变量,避免继承 .env 后误操作外部 Redis;两者都在测试前后执行 FLUSHDB,不要在这两个本地 DB 中放重要数据。端到端验证会编译真实二进制,使用固定 task ID,断言 completed/result/retry/scheduled/server 配置、只读与跨站保护,并通过 SIGTERM 验证优雅停机后 active 数量为 0。
cmd/client/ 任务生产者与演示场景
cmd/server/ worker 和完整 Server Config
cmd/monitor/ 内嵌 Asynqmon Web UI
internal/config/ 环境变量解析与严格校验
internal/tasks/ payload、任务构造器和 handler
integration/ 真实 Redis 优先级测试
scripts/verify.sh 端到端验证
docker-compose.yml Redis 容器
- 单 Redis 容器不是高可用架构;生产环境应评估 Redis Sentinel/Cluster、备份和持久化策略。
- Asynqmon 前端上游较旧,但本项目通过统一 Go dependency、编译测试,以及 queues/servers/completed/scheduled/result/read-only API 契约验证当前实验兼容性。
- 严格优先级可能饿死低优先级队列;生产默认更推荐加权模式,或为关键队列使用独立 worker 池。
- 队列权重只影响取任务的顺序/概率,不限制单队列并发,也不提供 rate limit。
- handler 超时依赖业务代码响应
context.Done();阻塞且不响应 context 的外部调用可能超过期望时间。
