xframe/component/queue/asynq/asynq_define.go

208 lines
5.1 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}