Skip to content
Go
任务处理与中间件

任务处理与中间件

1 Handler 接口

type Handler interface {
    ProcessTask(ctx context.Context, t *Task) error
}

任何实现了 ProcessTask 方法的类型都可以作为 Handler。

1.1 HandlerFunc 适配器

type HandlerFunc func(context.Context, *Task) error

func (f HandlerFunc) ProcessTask(ctx context.Context, task *Task) error {
    return f(ctx, task)
}

使用方式:

// 直接传入函数
mux.HandleFunc("email:send", func(ctx context.Context, t *asynq.Task) error {
    // 处理逻辑
    return nil
})

// 或传入预定义的函数
mux.HandleFunc("email:send", tasks.HandleEmailTask)

1.2 自定义 Handler 类型

当需要为 Handler 注入依赖(如 DB 连接、配置)时,使用结构体实现接口:

type EmailHandler struct {
    mailer   *mail.Client
    db       *sql.DB
    template *template.Engine
}

func (h *EmailHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
    var p EmailPayload
    if err := json.Unmarshal(t.Payload(), &p); err != nil {
        return fmt.Errorf("unmarshal: %w", asynq.SkipRetry)
    }

    tmpl, err := h.template.Load(p.TemplateID)
    if err != nil {
        return fmt.Errorf("template load: %w", err) // 可重试
    }

    return h.mailer.Send(ctx, p.To, tmpl.Subject, tmpl.Body)
}

// 注册
handler := &EmailHandler{mailer: mailer, db: db, template: tmpl}
mux.Handle(tasks.TypeEmailSend, handler)

2 ServeMux 路由

mux := asynq.NewServeMux()

// HandleFunc:按 Task Type 字符串路由
mux.HandleFunc("email:send", handleEmailSend)
mux.HandleFunc("order:cancel", handleOrderCancel)
mux.HandleFunc("report:export", handleReportExport)

// Handle:注册 Handler 接口的实现
mux.Handle("image:process", &ImageProcessHandler{processor})

2.1 路由匹配规则

ServeMux 使用精确匹配——Task Type 字符串必须完全一致。不同于 http.ServeMux,不支持前缀匹配或模式匹配。

"email:send"   → handleEmailSend
"email:send.v2"→ 不匹配 "email:send"(完全不同的类型)

💡 需要版本化任务类型时,使用显式版本号:

const (
    TypeEmailSendV1 = "email:send:v1"
    TypeEmailSendV2 = "email:send:v2"
)

2.2 未匹配的处理

如果 Task Type 没有注册任何 Handler,ServeMux 返回 SkipRetry 错误,任务直接进入 archived。

3 中间件

type MiddlewareFunc func(Handler) Handler

中间件在注册的 Handler 外层形成洋葱模型:

                    ┌─────────────┐
                    │  Middleware1 │
                    │  ┌─────────┐ │
                    │  │  Mid2   │ │
                    │  │ ┌─────┐ │ │
                    │  │ │Handler│ │ │
                    │  │ └─────┘ │ │
                    │  └─────────┘ │
                    └─────────────┘

3.1 内置常用中间件模式

日志中间件

func LoggingMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        taskID, _ := asynq.GetTaskID(ctx)
        queueName, _ := asynq.GetQueueName(ctx)

        log.Printf("[START] type=%s id=%s queue=%s", t.Type(), taskID, queueName)

        err := next.ProcessTask(ctx, t)

        duration := time.Since(start)
        if err != nil {
            log.Printf("[FAIL]  type=%s id=%s duration=%s err=%v", t.Type(), taskID, duration, err)
        } else {
            log.Printf("[DONE]  type=%s id=%s duration=%s", t.Type(), taskID, duration)
        }
        return err
    })
}

Panic 恢复中间件

func RecoveryMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) (err error) {
        defer func() {
            if r := recover(); r != nil {
                buf := make([]byte, 4096)
                n := runtime.Stack(buf, false)
                err = fmt.Errorf("panic: %v\n%s", r, buf[:n])
            }
        }()
        return next.ProcessTask(ctx, t)
    })
}

链路追踪中间件

func TracingMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        // 从 Headers 中提取 TraceID 并注入 context
        traceID := t.Headers["X-Trace-ID"]
        if traceID != "" {
            ctx = context.WithValue(ctx, traceKey, traceID)
        }

        // 如果没有,生成新的
        if traceID == "" {
            traceID = uuid.New().String()
            ctx = context.WithValue(ctx, traceKey, traceID)
        }

        span, ctx := opentracing.StartSpanFromContext(ctx, t.Type())
        defer span.Finish()

        return next.ProcessTask(ctx, t)
    })
}

指标采集中间件

func MetricsMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        taskType := t.Type()

        taskCounter.WithLabelValues(taskType, "received").Inc()
        timer := prometheus.NewTimer(taskDuration.WithLabelValues(taskType))

        err := next.ProcessTask(ctx, t)

        timer.ObserveDuration()
        if err != nil {
            taskCounter.WithLabelValues(taskType, "failed").Inc()
        } else {
            taskCounter.WithLabelValues(taskType, "completed").Inc()
        }
        return err
    })
}

3.2 注册中间件

mux := asynq.NewServeMux()

// 中间件按 Use 顺序由外向内包裹
mux.Use(RecoveryMiddleware)   // 最外层:panic 恢复
mux.Use(LoggingMiddleware)    // 第二层:日志
mux.Use(TracingMiddleware)    // 第三层:追踪
mux.Use(MetricsMiddleware)    // 最内层:指标

// 注册 Handler
mux.HandleFunc("email:send", handleEmailSend)

最终执行顺序:

Recovery → Logging → Tracing → Metrics → Handler → Metrics → Tracing → Logging → Recovery

3.3 中间件中的错误处理

func RetrySuppressMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        err := next.ProcessTask(ctx, t)

        // 对特定错误类型降级处理
        if IsDegradedError(err) {
            log.Printf("[DEGRADED] task=%s err=%v", t.Type(), err)
            return nil // 标记为成功
        }
        return err
    })
}

4 Context 辅助函数

Asynq 在 Handler 的 context.Context 中注入了以下信息:

4.1 GetTaskID

func Handler(ctx context.Context, t *asynq.Task) error {
    taskID, ok := asynq.GetTaskID(ctx)
    if ok {
        log.Printf("Processing task: %s", taskID)
    }
    // ...
}

4.2 GetQueueName

func Handler(ctx context.Context, t *asynq.Task) error {
    queueName, ok := asynq.GetQueueName(ctx)
    if ok {
        log.Printf("Task from queue: %s", queueName)
    }
    // ...
}

5 BaseContext 注入全局信息

v0.22.0+

srv := asynq.NewServer(redisOpt, asynq.Config{
    BaseContext: func() context.Context {
        // 在所有 Handler context 中注入全局 logger
        ctx := context.WithValue(context.Background(), loggerKey, appLogger)
        // 注入 tracer
        ctx = context.WithValue(ctx, tracerKey, appTracer)
        return ctx
    },
})

Handler 中获取:

func Handler(ctx context.Context, t *asynq.Task) error {
    logger := ctx.Value(loggerKey).(*slog.Logger)
    logger.Info("processing task", "type", t.Type())
    // ...
}

6 并发安全的 Handler

Handler 会被多个 goroutine 并发调用,必须保证线程安全:

type SafeHandler struct {
    mu   sync.Mutex
    cache map[string]*CachedData // 需要加锁访问
    db   *sql.DB                 // database/sql 的连接池已内置线程安全
}

func (h *SafeHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
    // ✅ database/sql 的连接池是线程安全的,无需额外加锁
    rows, err := h.db.QueryContext(ctx, "SELECT ...")

    // ✅ 访问共享 map 需要加锁
    h.mu.Lock()
    data := h.cache[t.Type()]
    h.mu.Unlock()

    // ...
}

🚨 陷阱:不要在 Handler 中直接写闭包捕获的共享变量。避免使用包级全局变量,改为通过 Handler 结构体注入。

7 完整示例:带依赖注入的 Worker

package main

import (
    "context"
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/hibiken/asynq"
)

type AppHandler struct {
    emailHandler *EmailHandler
    orderHandler *OrderHandler
    logger       *slog.Logger
}

func NewAppHandler(db *sql.DB, mailer *mail.Client) *AppHandler {
    return &AppHandler{
        emailHandler: &EmailHandler{mailer: mailer},
        orderHandler: &OrderHandler{db: db},
        logger:       slog.Default(),
    }
}

func (h *AppHandler) Register(mux *asynq.ServeMux) {
    // 注册中间件
    mux.Use(h.loggingMiddleware)
    mux.Use(h.recoveryMiddleware)

    // 注册路由
    mux.HandleFunc("email:send", h.emailHandler.ProcessTask)
    mux.HandleFunc("order:cancel", h.orderHandler.ProcessTask)
}

func (h *AppHandler) loggingMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        err := next.ProcessTask(ctx, t)
        h.logger.Info("task processed",
            "type", t.Type(),
            "duration", time.Since(start),
            "error", err,
        )
        return err
    })
}

func (h *AppHandler) recoveryMiddleware(next asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) (err error) {
        defer func() {
            if r := recover(); r != nil {
                h.logger.Error("panic recovered", "type", t.Type(), "panic", r)
                err = fmt.Errorf("panic: %v", r)
            }
        }()
        return next.ProcessTask(ctx, t)
    })
}

func main() {
    srv := asynq.NewServer(
        asynq.RedisClientOpt{Addr: "localhost:6379"},
        asynq.Config{Concurrency: 10},
    )

    h := NewAppHandler(db, mailer)
    mux := asynq.NewServeMux()
    h.Register(mux)

    if err := srv.Run(mux); err != nil {
        log.Fatal(err)
    }
}