123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- package service
- import (
- "bufio"
- "context"
- "crypto/md5"
- "errors"
- "fmt"
- "io"
- "os"
- "strconv"
- "strings"
- "sync"
- "time"
- "go-common/app/job/main/push/dao"
- pb "go-common/app/service/main/push/api/grpc/v1"
- pushmdl "go-common/app/service/main/push/model"
- xsql "go-common/library/database/sql"
- "go-common/library/log"
- "go-common/library/sync/errgroup"
- )
- const (
- _delTaskLimit = 5000
- )
- var (
- errEmptyLine = errors.New("empty line")
- errInvalidMid = errors.New("invalid mid format")
- errInvalidToken = errors.New("invalid token format")
- )
- func (s *Service) addTaskproc() {
- defer s.addTaskWg.Done()
- var err error
- for {
- task, ok := <-s.addTaskCh
- if !ok {
- log.Info("add task channel exit")
- return
- }
- if task == nil {
- continue
- }
- task.Status = pushmdl.TaskStatusPrepared
- for i := 0; i < _retry; i++ {
- if err = s.dao.AddTask(context.Background(), task); err == nil {
- break
- }
- }
- if err != nil {
- log.Error("add task(%+v) error(%v)", task, err)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("add task(%d)", task.Job))
- })
- continue
- }
- dao.PromInfo("add task")
- time.Sleep(time.Millisecond)
- }
- }
- func (s *Service) delTasksproc() {
- for {
- now := time.Now()
- // 每天2点时删除一个月前的task数据
- if now.Hour() != 2 {
- time.Sleep(time.Minute)
- continue
- }
- var (
- err error
- deleted int64
- b = now.Add(time.Duration(-s.c.Job.DelTaskInterval*24) * time.Hour)
- loc, _ = time.LoadLocation("Local")
- t = time.Date(b.Year(), b.Month(), b.Day(), 23, 59, 59, 0, loc)
- )
- for {
- if deleted, err = s.dao.DelTasks(context.TODO(), t, _delTaskLimit); err != nil {
- log.Error("s.delTasks(%v) error(%v)", t, err)
- s.dao.SendWechat("DB操作失败:push-job删除task数据错误")
- time.Sleep(time.Second)
- continue
- }
- if deleted < _delTaskLimit {
- break
- }
- time.Sleep(time.Second)
- }
- time.Sleep(time.Hour)
- }
- }
- func (s *Service) pretreatTaskproc() {
- defer s.waiter.Done()
- for {
- if s.closed {
- return
- }
- task, err := s.pickPretreatmentTask()
- if err != nil {
- time.Sleep(5 * time.Second)
- continue
- }
- if task != nil {
- log.Info("pretreat task job(%d) id(%s)", task.Job, task.ID)
- if err = s.pretreatTask(task); err != nil {
- log.Error("pretreat task(%+v) error(%v)", task, err)
- s.cache.Save(func() { s.dao.SendWechat(fmt.Sprintf("pretreat task(%s) error", task.ID)) })
- }
- }
- time.Sleep(time.Duration(s.c.Job.LoadTaskInteval))
- }
- }
- func (s *Service) pickPretreatmentTask() (t *pushmdl.Task, err error) {
- c := context.Background()
- var tx *xsql.Tx
- if tx, err = s.dao.BeginTx(c); err != nil {
- log.Error("tx.BeginTx() error(%v)", err)
- return
- }
- if t, err = s.dao.TxTaskByStatus(tx, pushmdl.TaskStatusPretreatmentPrepared); err != nil {
- if e := tx.Rollback(); e != nil {
- dao.PromError("task:获取新任务")
- log.Error("tx.Rollback() error(%v)", e)
- }
- return
- }
- if t == nil {
- if e := tx.Rollback(); e != nil {
- dao.PromError("task:获取新任务")
- log.Error("tx.Rollback() error(%v)", e)
- }
- return
- }
- if err = s.dao.TxUpdateTaskStatus(tx, t.ID, pushmdl.TaskStatusPretreatmentDoing); err != nil {
- if e := tx.Rollback(); e != nil {
- dao.PromError("task:更新任务状态")
- log.Error("tx.Rollback() error(%v)", e)
- }
- return
- }
- if err = tx.Commit(); err != nil {
- dao.PromError("task:获取新任务commit")
- log.Error("tx.Commit() error(%v)", err)
- }
- return
- }
- func (s *Service) pretreatTask(t *pushmdl.Task) (err error) {
- id, _ := strconv.ParseInt(t.ID, 10, 64)
- switch t.Type {
- case pushmdl.TaskTypeAll:
- err = s.pretreatTaskAll(t)
- case pushmdl.TaskTypeMngToken, pushmdl.TaskTypeDataPlatformToken, pushmdl.TaskTypeDataPlatformMid:
- err = s.pretreatTaskToken(t)
- case pushmdl.TaskTypeStrategyMid, pushmdl.TaskTypeMngMid:
- err = s.pretreatTaskMid(t)
- default:
- log.Error("invalid task type, (%+v)", t)
- }
- if err != nil {
- err = s.dao.UpdateTaskStatus(context.Background(), id, pushmdl.TaskStatusPretreatmentFailed)
- return
- }
- err = s.dao.UpdateTaskStatus(context.Background(), id, pushmdl.TaskStatusPretreatmentDone)
- return
- }
- func (s *Service) pretreatTaskAll(t *pushmdl.Task) (err error) {
- log.Info("AddTaskAll start, task(%+v)", t)
- var (
- maxID int64
- group = errgroup.Group{}
- )
- maxID, err = s.dao.ReportLastID(context.Background())
- if err != nil || maxID <= 0 {
- log.Error("s.pretreatTaskAll() error(%v)", err)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskAll(%v) ReportLastID(%d) error", t.ID, maxID))
- })
- return
- }
- log.Info("AddTaskAll get last report ID(%d)", maxID)
- buildCount := len(t.Build)
- batch := maxID / int64(s.c.Job.TaskGoroutines)
- for j := 0; j < s.c.Job.TaskGoroutines; j++ {
- begin := int64(j) * batch
- end := begin + batch
- group.Go(func() (e error) {
- var (
- path string
- rows *xsql.Rows
- tokens = make(map[int][]string)
- )
- for {
- if begin >= end {
- break
- }
- l := begin + int64(_dbBatch)
- if l >= end {
- l = end
- }
- log.Info("AddTaskAll load reports start(%d) end(%d)", begin, l)
- if rows, e = s.dao.ReportsTaskAll(context.Background(), begin, l, t.APPID); e != nil {
- log.Error("s.dao.ReportsTaskAll(%d,%d,%d) error(%v)", begin, l, t.APPID)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskAll(%v) ReportsTaskAll(%d,%d,%d) error", t.ID, begin, l, t.APPID))
- })
- return
- }
- for rows.Next() {
- var (
- platformID int
- build int
- token string
- )
- if e = rows.Scan(&platformID, &token, &build); e != nil {
- log.Error("AddTaskAll rows.Scan() error(%v)", e)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskAll(%v) ReportsTaskAll(%d,%d,%d) error", t.ID, begin, l, t.APPID))
- })
- return
- }
- if buildCount > 0 && !pushmdl.ValidateBuild(platformID, build, t.Build) {
- continue
- }
- tokens[platformID] = append(tokens[platformID], token)
- if len(tokens[platformID]) >= s.c.Job.LimitPerTask {
- if path, e = s.saveFile(tokens[platformID]); e != nil {
- log.Error("AddTaskAll s.saveTokens error(%v)", e)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskAll(%v) saveTokens error(%v)", t.ID, e))
- })
- return
- }
- tokens[platformID] = []string{}
- task := *t
- task.MidFile = path
- task.PlatformID = platformID
- s.addTaskCh <- &task
- }
- }
- begin = l
- }
- for p, v := range tokens {
- if len(v) == 0 {
- continue
- }
- if path, e = s.saveFile(v); e == nil {
- task := *t
- task.MidFile = path
- task.PlatformID = p
- s.addTaskCh <- &task
- }
- }
- return
- })
- }
- if err = group.Wait(); err != nil {
- log.Error("add task all, task(%+v) error(%v)", t, err)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskAll(%v) error(%v)", t.ID, err))
- })
- return
- }
- log.Info("AddTaskAll end, task(%+v)", t)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("add task all success, job(%d)", t.Job))
- })
- return
- }
- func (s *Service) pretreatTaskMid(t *pushmdl.Task) (err error) {
- f, err := os.Open(t.MidFile)
- if err != nil {
- log.Error("pretreatTaskMid(%+v) open file error(%v)", t, err)
- return
- }
- defer f.Close()
- var (
- exit bool
- line string
- path string
- mid int64
- counter int
- midTotal int64
- midValid int64
- mu sync.Mutex
- mids []int64
- tokens = make(map[int][]string)
- group = errgroup.Group{}
- reader = bufio.NewReader(f)
- )
- for {
- if exit {
- break
- }
- if line, err = reader.ReadString('\n'); err != nil {
- if err == io.EOF {
- exit = true
- } else {
- log.Error("read file error(%v)", err)
- continue
- }
- }
- if mid, err = parseMidLine(line); err != nil {
- log.Error("parse mid line(%s) error(%v)", line, err)
- continue
- }
- midTotal++
- mids = append(mids, mid)
- if len(mids) >= s.c.Job.PushPartSize {
- midsCp := make([]int64, len(mids))
- copy(midsCp, mids)
- mids = []int64{}
- group.Go(func() (e error) {
- ts, valid, e := s.tokensByMids(t, midsCp)
- if e != nil {
- log.Error("s.tokensByMids(%v) error(%v)", t.ID, e)
- return
- }
- tcopy := make(map[int][]string)
- mu.Lock()
- midValid += valid
- for p, v := range ts {
- tokens[p] = append(tokens[p], v...)
- if len(tokens[p]) >= s.c.Job.LimitPerTask {
- tcopy[p] = append(tcopy[p], tokens[p]...)
- tokens[p] = []string{}
- }
- }
- mu.Unlock()
- for p, v := range tcopy {
- if path, err = s.saveFile(v); err != nil {
- log.Error("pretreatTaskMid s.saveFild error(%v)", err)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskMid(%v) saveTokens error(%v)", t.ID, err))
- })
- return
- }
- task := *t
- task.MidFile = path
- task.PlatformID = p
- s.addTaskCh <- &task
- }
- return
- })
- counter++
- if counter == s.c.Job.PushPartChanSize {
- group.Wait()
- counter = 0
- }
- }
- }
- if counter > 0 {
- group.Wait()
- }
- if len(mids) > 0 {
- var (
- valid int64
- ts map[int][]string
- )
- if ts, valid, err = s.tokensByMids(t, mids); err == nil {
- midValid += valid
- for p, v := range ts {
- tokens[p] = append(tokens[p], v...)
- }
- } else {
- log.Error("s.tokensByMids(%+v) error(%v)", t, err)
- }
- }
- s.cache.Save(func() {
- arg := &pb.AddMidProgressRequest{Task: t.ID, MidTotal: midTotal, MidValid: midValid}
- if _, e := s.pushRPC.AddMidProgress(context.Background(), arg); e != nil {
- log.Error("s.pushRPC.AddMidProgress(%+v) error(%v)", arg, e)
- }
- })
- for p, v := range tokens {
- if len(v) == 0 {
- continue
- }
- if path, err = s.saveFile(v); err != nil {
- log.Error("pretreatTaskMid s.saveFild error(%v)", err)
- return
- }
- task := *t
- task.MidFile = path
- task.PlatformID = p
- s.addTaskCh <- &task
- }
- log.Info("pretreatTaskMid task(%+v)", t)
- return
- }
- func (s *Service) pretreatTaskToken(t *pushmdl.Task) (err error) {
- f, err := os.Open(t.MidFile)
- if err != nil {
- log.Error("pretreatTaskToken(%+v) open file error(%v)", t, err)
- return
- }
- defer f.Close()
- var (
- exit bool
- plat int
- line string
- token string
- path string
- tokens = make(map[int][]string)
- reader = bufio.NewReader(f)
- )
- for {
- if exit {
- break
- }
- if line, err = reader.ReadString('\n'); err != nil {
- if err == io.EOF {
- exit = true // no 'continue', solve the last line whitout '\n'
- } else {
- log.Error("read file error(%v)", err)
- continue
- }
- }
- if plat, token, err = parseTokenLine(line); err != nil {
- log.Error("parse token line(%s) error(%v)", line, err)
- continue
- }
- tokens[plat] = append(tokens[plat], token)
- if len(tokens[plat]) >= s.c.Job.LimitPerTask {
- if path, err = s.saveFile(tokens[plat]); err != nil {
- log.Error("pretreatTaskToken s.saveFile error(%v)", err)
- s.cache.Save(func() {
- s.dao.SendWechat(fmt.Sprintf("pretreatTaskToken(%v) saveTokens error(%v)", t.ID, err))
- })
- return
- }
- tokens[plat] = []string{}
- task := *t
- task.MidFile = path
- task.PlatformID = plat
- s.addTaskCh <- &task
- }
- }
- for p, v := range tokens {
- if len(v) == 0 {
- continue
- }
- if path, err = s.saveFile(v); err == nil {
- task := *t
- task.MidFile = path
- task.PlatformID = p
- s.addTaskCh <- &task
- }
- }
- log.Info("pretreatTaskToken task(%+v)", t)
- return
- }
- func parseTokenLine(line string) (plat int, token string, err error) {
- line = strings.Trim(line, " \r\n")
- if line == "" {
- err = errEmptyLine
- return
- }
- res := strings.Split(line, "\t")
- if len(res) != 2 {
- err = errInvalidToken
- return
- }
- if res[0] == "" || res[1] == "" {
- err = errInvalidToken
- return
- }
- if plat, err = strconv.Atoi(res[0]); err != nil || plat <= 0 {
- err = errInvalidToken
- return
- }
- token = res[1]
- return
- }
- func parseMidLine(line string) (mid int64, err error) {
- line = strings.Trim(line, " \r\t\n")
- if line == "" {
- err = errEmptyLine
- return
- }
- if mid, err = strconv.ParseInt(line, 10, 64); err != nil || mid <= 0 {
- err = errInvalidMid
- }
- return
- }
- func (s *Service) saveFile(tokens []string) (path string, err error) {
- name := strconv.FormatInt(time.Now().UnixNano(), 10) + tokens[0]
- data := []byte(strings.Join(tokens, "\n"))
- for i := 0; i < _retry; i++ {
- if path, err = s.saveNASFile(name, data); err == nil {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- return
- }
- // saveNASFile writes data into NAS.
- func (s *Service) saveNASFile(name string, data []byte) (path string, err error) {
- name = fmt.Sprintf("%x", md5.Sum([]byte(name)))
- dir := fmt.Sprintf("%s/%s/%s", strings.TrimSuffix(s.c.Job.MountDir, "/"), time.Now().Format("20060102"), name[:2])
- if _, err = os.Stat(dir); err != nil {
- if !os.IsNotExist(err) {
- log.Error("os.IsNotExist(%s) error(%v)", dir, err)
- return
- }
- if err = os.MkdirAll(dir, 0777); err != nil {
- log.Error("os.MkdirAll(%s) error(%v)", dir, err)
- return
- }
- }
- path = fmt.Sprintf("%s/%s", dir, name)
- f, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
- if err != nil {
- log.Error("s.saveNASFile(%s) OpenFile() error(%v)", path, err)
- return
- }
- defer f.Close()
- if _, err = f.Write(data); err != nil {
- log.Error("s.saveNASFile(%s) f.Write() error(%v)", err)
- }
- return
- }
|