208 lines
5.1 KiB
Go
208 lines
5.1 KiB
Go
|
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)
|
|||
|
}
|