接口里直接发邮件、转码、跑报表,请求要等几秒甚至超时。这些耗时操作和响应用户无关,该丢到后台。这一篇用 asynq 把任务异步化,请求秒回,worker 慢慢处理。
1. 为什么异步
发邮件 200ms、生成报表 5s,都卡在请求链路里,连接和线程被占着,用户干等。这些操作失败也不该让用户请求失败。扔给后台,请求立即返回 202。
2. asynq 基于 Redis
asynq 用 Redis 存任务队列,自带重试、调度、优先级。生产者入队,独立 worker 进程消费:
import "github.com/hibiken/asynq" client := asynq.NewClient(asynq.RedisClientOpt{Addr: "127.0.0.1:6379"}) func enqueueEmail(to, body string) error { payload, _ := json.Marshal(EmailTask{To: to, Body: body}) t := asynq.NewTask("email", payload) return client.Enqueue(t, asynq.MaxRetry(3), asynq.Queue("low")) }
handler 里调 enqueueEmail 后直接返回,不发完邮件才响应。
3. worker 消费
func main() { srv := asynq.NewServer(asynq.RedisClientOpt{Addr: "127.0.0.1:6379"}, asynq.Config{Concurrency: 10}) srv.Run(asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error { switch t.Type() { case "email": return sendEmail(t.Payload()) } return fmt.Errorf("unknown task: %s", t.Type()) })) }
worker 是独立进程,Concurrency 控制并发数。handler 和 API 服务分开部署,互不影响。
4. 失败重试
任务返回 error 就按 MaxRetry 重试,指数退避。重试耗尽进死信队列,别丢:
asynq.NewTask("email", payload, asynq.MaxRetry(5), asynq.Timeout(30*time.Second))
Timeout 防单个任务卡死。死信队列单独监控,堆积说明下游真有问题。
5. 优先级与延迟
client.Enqueue(t, asynq.Queue("critical")) // 高优队列先消费 client.Enqueue(t, asynq.ProcessIn(24*time.Hour)) // 延迟到明天
报表生成走低优队列,提醒类走高优。延迟任务做定时发券、过期清理。
6. 任务状态可见
asynq 自带 Web UI(asynqmon)看队列长度、失败数、重试中任务。比盲猜强,堆积时能及时加 worker 或查下游。
7. 上线清单
耗时且用户不等结果的操作才异步,强一致写库别异步。worker 独立部署,和 API 资源隔离。任务幂等,重试不会重复发邮件/扣款。失败任务进死信队列并监控,别静默丢。Redis 是任务存处,挂了队列也挂,重要任务考虑持久化或换更稳的 broker。
下一篇讲安全加固,把常见的 Web 攻击挡在门外。