xframe/component/queue/asynq/asynq_define.go

208 lines
5.1 KiB
Go
Raw Normal View History

2024-10-12 12:55:20 +08:00
package asynq
import (
"context"
"net/http"
"sync"
"time"
"git.wishpal.cn/wishpal_ironfan/xframe/component/queue/asynq/internal/errhandler"
"git.wishpal.cn/wishpal_ironfan/xframe/component/queue/asynq/internal/handlers"
"git.wishpal.cn/wishpal_ironfan/xframe/component/queue/asynq/internal/loggerx"
"git.wishpal.cn/wishpal_ironfan/xframe/component/queue/asynq/internal/maker"
"git.wishpal.cn/wishpal_ironfan/xframe/component/queue/asynq/internal/rate"
"git.wishpal.cn/wishpal_ironfan/xframe/component/queue/asynq/internal/utils"
"git.wishpal.cn/wishpal_ironfan/xframe/component/storage/redis"
"github.com/hibiken/asynq"
"github.com/hibiken/asynqmon"
"github.com/pkg/errors"
)
const (
queueDefault = "default"
queueSlow = "slow"
DefaultQueueQps = 500
SlowQueueQps = 50
)
type XcAsynq struct {
cfg *Config
server *asynq.Server
client *asynq.Client
rdb *redis.Redis
rateCtrl *rate.RateCtrl
queuePrefix string
initLock sync.Mutex
startLock sync.Mutex
inited bool
started bool
}
func (q *XcAsynq) Init(ctx context.Context, cfg *Config) (err error) {
// 上锁防止重复init
q.initLock.Lock()
defer q.initLock.Unlock()
if q.inited {
return
}
defer func() {
if err == nil {
q.inited = true
}
}()
q.cfg = cfg
if cfg.DefaultQueueQps <= 0 {
cfg.DefaultQueueQps = DefaultQueueQps
}
if cfg.SlowQueueQps <= 0 {
cfg.SlowQueueQps = SlowQueueQps
}
if cfg.DefaultQueueQps < cfg.SlowQueueQps {
err = errors.Errorf("default queue qps (%d) must larget than slow queue qps (%d)", cfg.DefaultQueueQps,
cfg.SlowQueueQps)
return
}
// 连redis
rdb, err := redis.NewRedis(cfg.RedisConf)
if err != nil {
err = errors.Wrap(err, "new redis")
return
}
// 检查集群版redis的lua兼容性
// 阿里的集群版做了特殊限制要求keys必须显式传入导致asynq的lua脚本报错
//reclaimStateAggregationSetsCmd、listLeaseExpiredCmd里面的key是动态获取的
testLuaCmd := redis.NewScript(`return redis.call("TYPE", "test_cmd_key1")`)
err = testLuaCmd.Run(ctx, rdb, []string{"fake"}).Err()
if err != nil {
err = errors.Wrap(err, "lua incompatible")
return
}
q.rdb = rdb
q.rateCtrl = rate.NewRateCtrl(rdb)
q.queuePrefix = utils.GenDefaultQueuePrefix()
q.server = asynq.NewServer(maker.NewMaker(rdb), asynq.Config{
BaseContext: utils.BaseContext,
Queues: map[string]int{
q.queuePrefix + queueDefault: 5,
q.queuePrefix + queueSlow: 1,
},
IsFailure: rate.IsFailure,
RetryDelayFunc: rate.RetryDelayFunc,
DelayedTaskCheckInterval: time.Second / 2, // 超过1秒会导致超出qps的部分被delay过久实际qps低于预期qps60时想限制为50结果实际跑出来10qps
ErrorHandler: errhandler.ErrorHandler{},
Logger: loggerx.Logger{},
})
q.client = asynq.NewClient(maker.NewMaker(rdb))
return
}
func (q *XcAsynq) Start() (err error) {
q.startLock.Lock()
defer q.startLock.Unlock()
if !q.inited {
return errors.New("not inited")
}
if q.started {
return
}
defer func() {
if err == nil {
q.started = true
}
}()
slowPattern, err := utils.ExtendUnifiedPattern(queueSlow, "")
if err != nil {
return
}
defaultPattern, err := utils.ExtendUnifiedPattern(queueDefault, "")
if err != nil {
return
}
mux := asynq.NewServeMux()
mux.HandleFunc(slowPattern, handlers.GenHandlerFunc(q.rateCtrl, "slow", q.cfg.SlowQueueQps))
mux.HandleFunc(defaultPattern, handlers.GenHandlerFunc(q.rateCtrl, "default", q.cfg.DefaultQueueQps))
err = q.server.Start(mux)
if err != nil {
return
}
q.startWebUI()
return
}
func (q *XcAsynq) Stop() {
q.server.Stop()
q.server.Shutdown()
}
// assignQueue 指定使用哪个队列,并给队列名加特定的前缀
func (q *XcAsynq) assignQueue(queue string, opts []asynq.Option) (result []asynq.Option, err error) {
// opts里不能有对queue的配置要报错
for _, v := range opts {
if v.Type() == asynq.QueueOpt {
err = errors.Errorf("can not assign queue(%v) manually", v.Value())
return
}
}
result = append(opts, asynq.Queue(q.queuePrefix+queue))
return
}
// assignDefaultMaxRetry 如果没有配置重试次数,补上默认的
func (q *XcAsynq) assignDefaultMaxRetry(opts []asynq.Option) []asynq.Option {
for _, v := range opts {
if v.Type() == asynq.MaxRetryOpt {
return opts
}
}
return append(opts, asynq.MaxRetry(3))
}
func (q *XcAsynq) addTask(ctx context.Context, queue, typ string, payload []byte, opts ...asynq.Option) (
info *asynq.TaskInfo, err error) {
opts, err = q.assignQueue(queue, opts)
if err != nil {
err = errors.Wrap(err, "assignQueue")
return
}
opts = q.assignDefaultMaxRetry(opts)
typ, err = utils.ExtendUnifiedPattern(queue, typ)
if err != nil {
err = errors.Wrap(err, "ExtendUnifiedPattern")
return
}
task := asynq.NewTask(typ, payload, opts...)
info, err = q.client.EnqueueContext(ctx, task)
if err != nil {
err = errors.Wrap(err, "EnqueueContext")
return
}
return
}
func (q *XcAsynq) startWebUI() {
h := asynqmon.New(asynqmon.Options{
RedisConnOpt: maker.NewMaker(q.rdb),
})
http.Handle(h.RootPath()+"/", h)
go http.ListenAndServe(":8889", nil)
}