package queue import ( "time" "github.com/break/junhong_cmp_fiber/pkg/config" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/hibiken/asynq" "github.com/redis/go-redis/v9" "go.uber.org/zap" ) // Server Asynq Worker 服务器 type Server struct { server *asynq.Server logger *zap.Logger } // RateLimitedRetry 定义任务因运营商账户网关限流失败时的重试语义:不计为失败、按固定短延迟重试。 // Match 判断错误是否为限流拒绝;Delay 返回当前配置的固定延迟;OnDeferred 在每次因限流推迟时回调(用于累计推迟告警)。 type RateLimitedRetry struct { Match func(error) bool Delay func() time.Duration OnDeferred func(task *asynq.Task) } // ServerOption 是 Asynq 服务器的可选配置。 type ServerOption func(*serverOptions) type serverOptions struct { rateLimited *RateLimitedRetry } // WithRateLimitedRetry 让限流错误不计入任务重试次数并按固定短延迟重试,其他错误行为不变。 func WithRateLimitedRetry(retry RateLimitedRetry) ServerOption { return func(o *serverOptions) { if retry.Match != nil { o.rateLimited = &retry } } } // NewServer 创建新的 Asynq 服务器 func NewServer(redisClient *redis.Client, queueCfg *config.QueueConfig, logger *zap.Logger, options ...ServerOption) *Server { var serverOpts serverOptions for _, option := range options { option(&serverOpts) } // 从 Redis 客户端获取配置 opts := redisClient.Options() // 解析队列优先级配置 queues := ParseQueueConfig(queueCfg) // 设置并发数 concurrency := queueCfg.Concurrency if concurrency <= 0 { concurrency = constants.DefaultConcurrency } asynqConfig := asynq.Config{ // 并发数 Concurrency: concurrency, // 队列优先级配置 Queues: queues, // 重试延迟函数(指数退避) RetryDelayFunc: asynq.DefaultRetryDelayFunc, // 是否记录详细日志 LogLevel: asynq.WarnLevel, } if retry := serverOpts.rateLimited; retry != nil { // 限流错误不是任务本身的失败:IsFailure 返回 false 时 Asynq 不增加重试计数,任务不会因限流被归档; // 重试间隔固定为短延迟,避免按指数退避把"流量用完停机"等推迟数小时。 asynqConfig.IsFailure = func(err error) bool { return !retry.Match(err) } asynqConfig.RetryDelayFunc = func(n int, err error, task *asynq.Task) time.Duration { if retry.Match(err) { if retry.OnDeferred != nil { retry.OnDeferred(task) } if retry.Delay != nil { if delay := retry.Delay(); delay > 0 { return delay } } return 5 * time.Second } return asynq.DefaultRetryDelayFunc(n, err, task) } } // 创建 Asynq 服务器配置 asynqServer := asynq.NewServer( asynq.RedisClientOpt{ Addr: opts.Addr, Password: opts.Password, DB: opts.DB, }, asynqConfig, ) return &Server{ server: asynqServer, logger: logger, } } // Start 启动 Worker 服务器 func (s *Server) Start(mux *asynq.ServeMux) error { s.logger.Info("Worker 服务器启动中...") if err := s.server.Start(mux); err != nil { s.logger.Error("Worker 服务器启动失败", zap.Error(err)) return err } s.logger.Info("Worker 服务器启动成功") return nil } // Shutdown 优雅关闭服务器 func (s *Server) Shutdown() { s.logger.Info("Worker 服务器关闭中...") s.server.Shutdown() s.logger.Info("Worker 服务器已关闭") } // Run 启动并阻塞运行(用于主函数) func (s *Server) Run(mux *asynq.ServeMux) error { s.logger.Info("Worker 服务器启动中...") if err := s.server.Run(mux); err != nil { s.logger.Error("Worker 服务器运行失败", zap.Error(err)) return err } return nil }