go-common/app/job/main/aegis/service/weight.go
2019-04-22 18:49:16 +08:00

363 lines
9.2 KiB
Go
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 service
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/job/main/aegis/model"
"go-common/library/log"
)
// WeightManager weight manager
type WeightManager struct {
s *Service
businessID, flowID int64
toplen, batchlen int64
minute int64
// cache
topweightList []*model.WeightItem
// channel
redisWeightList chan *model.WeightItem
dbWeightList chan *model.WeightItem
asignList chan *model.Task
//dbstartSig, dbstopSig chan struct{}
redisFinish chan struct{}
//closeChan chan struct{}
close bool
}
var _defaultopt = &model.WeightOPT{
TopListLen: 1000,
BatchListLen: 1000,
RedisListLen: 10000,
DbListLen: 2000,
AssignLen: 100,
Minute: 3,
}
// NewWeightManager new
func NewWeightManager(s *Service, opt *model.WeightOPT, key string) (wm *WeightManager) {
if opt == nil {
opt = _defaultopt
} else {
if opt.TopListLen <= 0 {
opt.TopListLen = _defaultopt.TopListLen
}
if opt.BatchListLen <= 0 {
opt.BatchListLen = _defaultopt.BatchListLen
}
if opt.RedisListLen <= 0 {
opt.RedisListLen = _defaultopt.RedisListLen
}
if opt.DbListLen <= 0 {
opt.DbListLen = _defaultopt.DbListLen
}
if opt.AssignLen <= 0 {
opt.AssignLen = _defaultopt.AssignLen
}
if opt.Minute <= 0 {
opt.Minute = _defaultopt.Minute
}
}
if len(key) > 0 {
bizid, flowid := parseKey(key)
opt.BusinessID = int64(bizid)
opt.FlowID = int64(flowid)
}
wm = &WeightManager{
s: s,
businessID: opt.BusinessID,
flowID: opt.FlowID,
toplen: opt.TopListLen,
batchlen: opt.BatchListLen,
minute: opt.Minute,
redisWeightList: make(chan *model.WeightItem, opt.RedisListLen),
dbWeightList: make(chan *model.WeightItem, opt.DbListLen),
asignList: make(chan *model.Task, opt.AssignLen),
redisFinish: make(chan struct{}),
}
go wm.weightProc()
go wm.weightWatcher()
log.Info("启动权重计算器 bizid(%d) flowid(%d) opt(%+v)", wm.businessID, wm.flowID, opt)
return
}
func parseKey(key string) (bizid, flowid int) {
pos := strings.Index(key, "-")
bizids := key[:pos]
flowids := key[pos+1:]
bizid, _ = strconv.Atoi(bizids)
flowid, _ = strconv.Atoi(flowids)
return
}
func (s *Service) startWeightManager() {
// 1.当前的所有业务线,需要计算权重的先枚举出来
s.wmHash = make(map[string]*WeightManager)
for key := range s.newactiveBizFlow {
bizid, _ := parseKey(key)
s.wmHash[key] = NewWeightManager(s, s.getWeightOpt(bizid), key)
}
}
func (w *WeightManager) weightProc() {
for !w.close {
if err := w.weightRedisProcess(); err != nil {
w.weightDBProcess()
}
time.Sleep(time.Duration(w.minute) * time.Minute)
}
}
func (w *WeightManager) weightWatcher() {
for !w.close {
select {
case <-w.redisFinish: //取出权重最大的一批,更新到数据库
log.Info("redisFinish(%d-%d:%d)", w.businessID, w.flowID, w.toplen)
w.handleRedisFinish(context.Background())
case wi := <-w.redisWeightList:
w.handleRedisWeightList(context.Background(), wi)
case wi := <-w.dbWeightList:
w.handleDBWeightList(context.Background(), wi)
case task := <-w.asignList:
w.handleAssign(context.Background(), task)
}
}
}
func (w *WeightManager) weightRedisProcess() (err error) {
var c = context.Background()
if err = w.s.dao.CreateUnionSet(c, w.businessID, w.flowID); err != nil {
return
}
var (
start = int64(0)
stop = w.batchlen
)
for {
wis, err := w.s.dao.RangeUinonSet(c, w.businessID, w.flowID, start, stop)
if err != nil {
return err
}
log.Info("weightRedisProcess length(%d) start(%d) stop(%d)", len(wis), start, stop)
start += w.batchlen
stop += w.batchlen
if len(wis) == 0 {
break
}
for _, wi := range wis {
if w.caculateWeight(c, wi) {
log.Warn("weightRedisProcess 任务未找到 wi(%+v)", wi)
continue
}
w.s.dao.SetWeight(c, w.businessID, w.flowID, wi.ID, wi.Weight)
}
time.Sleep(time.Second)
}
w.redisFinish <- struct{}{}
w.s.dao.DeleteUinonSet(c, w.businessID, w.flowID)
return nil
}
func (w *WeightManager) caculateWeight(c context.Context, wi *model.WeightItem) (skip bool) {
task, err := w.s.dao.GetTask(c, wi.ID)
if err != nil {
return true
}
w.reAssign(c, task)
wm := int64(time.Since(task.Ctime.Time()).Minutes())
wl := &model.WeightLog{
UPtime: time.Now().Format("2006-01-02 15:04:05"),
Mid: task.MID,
Fans: task.Fans,
Group: task.Group,
WaitTime: model.WaitTime(task.Ctime.Time()),
}
var wtRange, wtEqual int64
wci, ewc := w.s.getWeightCache(c, task.BusinessID, task.FlowID)
if wci != nil {
wtRange = w.rangeCaculate(c, wci, task, wm, wl)
}
if ewc != nil {
wtEqual = w.equalCaculate(c, ewc, task, wm, wl)
}
wi.Weight = wtRange + wtEqual
wl.Weight = wi.Weight
w.s.sendWeightLog(c, task, wl)
return
}
func (w *WeightManager) rangeCaculate(c context.Context, wci map[string]*model.RangeWeightConfig, task *model.Task, wt int64, wl *model.WeightLog) (weight int64) {
var wtWeight, fanWeight, groupWeight int64
if cfg, ok := wci["waittime"]; ok {
if wtlen := len(cfg.Range); wtlen > 0 { // 等待时长,要把之前等级的权重加上去
for i := wtlen - 1; i >= 0; i-- {
if wt >= cfg.Range[i].Threshold { // 命中配置
wtWeight += cfg.Range[i].Weight * ((wt - cfg.Range[i].Threshold) / w.minute)
// 计算0 到 (i-1) 累计权重
for j := 0; j <= i-1; j++ {
wtWeight += cfg.Range[j].Weight * ((cfg.Range[j+1].Threshold - cfg.Range[j].Threshold) / w.minute)
}
break
}
}
}
}
if cfg, ok := wci["fans"]; ok {
if fanLen := len(cfg.Range); fanLen > 0 {
for i := fanLen - 1; i >= 0; i-- {
if task.Fans >= cfg.Range[i].Threshold {
fanWeight = cfg.Range[i].Weight * (wt / w.minute)
break
}
}
}
}
if cfg, ok := wci["group"]; ok {
if len(cfg.Range) > 0 {
for _, item := range cfg.Range {
if strings.Contains(","+task.Group+",", fmt.Sprintf(",%d,", item.Threshold)) {
groupWeight = item.Weight * (wt / w.minute)
}
}
}
}
weight = wtWeight + fanWeight + groupWeight
wl.WaitWeight = wtWeight
wl.FansWeight = fanWeight
wl.GroupWeight = groupWeight
return
}
func (w *WeightManager) equalCaculate(c context.Context, ewc []*model.EqualWeightConfig, task *model.Task, wt int64, wl *model.WeightLog) (weight int64) {
var midweight, taskweight int64
for _, item := range ewc {
if item.Name == "mid" {
if strings.Contains(","+item.IDs+",", fmt.Sprintf(",%d,", task.MID)) {
if item.Type == model.WeightTypeCycle {
midweight += item.Weight * (wt / w.minute)
} else {
midweight += item.Weight
}
log.Info("equalCaculate task(%+v) hit (%+v)", task, item)
wl.ConfigItems = append(wl.ConfigItems, &model.ConfigItem{
Name: item.Name,
Desc: item.Description,
Uname: item.Uname,
})
}
}
if item.Name == "taskid" || item.Name == "task_id" {
if strings.Contains(","+item.IDs+",", fmt.Sprintf(",%d,", task.ID)) {
if item.Type == model.WeightTypeCycle {
taskweight += item.Weight * (wt / w.minute)
} else {
taskweight += item.Weight
}
log.Info("equalCaculate task(%+v) hit (%+v)", task, item)
wl.ConfigItems = append(wl.ConfigItems, &model.ConfigItem{
Name: item.Name,
Desc: item.Description,
Uname: item.Uname,
})
}
}
}
weight = midweight + taskweight
wl.EqualWeight = weight
return
}
func (w *WeightManager) reAssign(c context.Context, task *model.Task) {
if task.UID == 0 {
select {
case w.asignList <- task:
log.Info("指派判断 reAssign%+v", task)
case <-time.NewTimer(10 * time.Millisecond).C:
log.Warn("chan asignList full,len:%d", len(w.dbWeightList))
}
}
}
func (w *WeightManager) weightDBProcess() (err error) {
// TODO 只用db更新权重的策略
return nil
}
func (w *WeightManager) handleAssign(c context.Context, task *model.Task) (err error) {
if w.s.setAssign(c, task) {
if rows, err := w.s.dao.AssignTask(c, task); err == nil && rows == 1 {
w.s.dao.SetTask(c, task)
}
}
return
}
func (w *WeightManager) handleRedisWeightList(c context.Context, wi *model.WeightItem) (err error) {
return w.s.dao.SetWeight(c, w.businessID, w.flowID, wi.ID, wi.Weight)
}
func (w *WeightManager) handleDBWeightList(c context.Context, wi *model.WeightItem) (rows int64, err error) {
return w.s.dao.SetWeightDB(c, wi.ID, wi.Weight)
}
func (w *WeightManager) handleRedisFinish(c context.Context) (err error) {
log.Info("handleRedisFinish")
wis, err := w.s.dao.TopWeights(c, w.businessID, w.flowID, w.toplen)
if err != nil {
return
}
tempMap := make(map[int64]struct{})
for _, wi := range wis {
log.Info("handleRedisFinish:(%+v)", wi)
w.addToDBList(wi)
tempMap[wi.ID] = struct{}{}
}
for _, wi := range w.topweightList {
if _, ok := tempMap[wi.ID]; !ok {
weight, err := w.s.dao.GetWeight(c, w.businessID, w.flowID, wi.ID)
if err != nil {
continue
}
wi.Weight = weight
w.addToDBList(wi)
}
}
w.topweightList = wis
log.Info("handleRedisFinish:topweightList(%d)", len(wis))
return
}
func (w *WeightManager) addToDBList(wi *model.WeightItem) {
select {
case w.dbWeightList <- wi:
log.Info("addToDBList (%+v)", wi)
case <-time.NewTimer(10 * time.Millisecond).C:
log.Warn("chan dbWeightList full,len:%d", len(w.dbWeightList))
}
}