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