123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- package service
- import (
- "context"
- "encoding/json"
- "fmt"
- "sync"
- "time"
- tagrpc "go-common/app/interface/main/tag/rpc/client"
- artmdl "go-common/app/interface/openplatform/article/model"
- artrpc "go-common/app/interface/openplatform/article/rpc/client"
- "go-common/app/job/openplatform/article/conf"
- "go-common/app/job/openplatform/article/dao"
- "go-common/app/job/openplatform/article/model"
- thumdl "go-common/app/service/main/thumbup/model"
- "go-common/library/conf/env"
- "go-common/library/log"
- "go-common/library/log/infoc"
- "go-common/library/queue/databus"
- "go-common/library/stat/prom"
- )
- const (
- _sharding = 100 // goroutines for dealing the stat
- _chanSize = 10240
- _articleTable = "filtered_articles" // article table name
- _authorTable = "article_authors" // 作者表
- )
- type lastTimeStat struct {
- time int64
- stat *artmdl.StatMsg
- }
- // Service .
- type Service struct {
- c *conf.Config
- dao *dao.Dao
- waiter *sync.WaitGroup
- closed bool
- // monitor *monitor.Service
- articleSub *databus.Databus
- articleStatSub *databus.Databus
- likeStatSub *databus.Databus
- replyStatSub *databus.Databus
- favoriteStatSub *databus.Databus
- coinStatSub *databus.Databus
- articleRPC *artrpc.Service
- tagRPC *tagrpc.Service
- statCh [_sharding]chan *artmdl.StatMsg
- statLastTime [_sharding]map[int64]*lastTimeStat
- categoriesMap map[int64]*artmdl.Category
- categoriesReverseMap map[int64][]*artmdl.Category
- sitemapMap map[int64]struct{}
- urlListHead *urlNode
- urlListTail *urlNode
- lastURLNode *urlNode
- sitemapXML string
- likeCh chan *thumdl.StatMsg
- updateDbInterval int64
- updateSortInterval time.Duration
- cheatInfoc *infoc.Infoc
- // *Cnt means sum of consumed messages.
- statCnt, binCnt int64
- cheatArts map[int64]int
- sortLimitTime int64
- setting *model.Setting
- // infoc
- logCh chan interface{}
- }
- // New creates a Service instance.
- func New(c *conf.Config) (s *Service) {
- s = &Service{
- c: c,
- dao: dao.New(c),
- waiter: new(sync.WaitGroup),
- // monitor: monitor.New(),
- articleSub: databus.New(c.ArticleSub),
- articleStatSub: databus.New(c.ArticleStatSub),
- replyStatSub: databus.New(c.ReplyStatSub),
- favoriteStatSub: databus.New(c.FavoriteStatSub),
- coinStatSub: databus.New(c.CoinStatSub),
- likeStatSub: databus.New(c.LikeStatSub),
- articleRPC: artrpc.New(c.ArticleRPC),
- tagRPC: tagrpc.New2(c.TagRPC),
- updateDbInterval: int64(time.Duration(c.Job.UpdateDbInterval) / time.Second),
- updateSortInterval: time.Duration(c.Job.UpdateSortInterval),
- likeCh: make(chan *thumdl.StatMsg, 1e4),
- cheatInfoc: infoc.New(c.CheatInfoc),
- categoriesMap: make(map[int64]*artmdl.Category),
- categoriesReverseMap: make(map[int64][]*artmdl.Category),
- sitemapMap: make(map[int64]struct{}),
- sortLimitTime: int64(time.Duration(c.Job.SortLimitTime) / time.Second),
- logCh: make(chan interface{}, 1024),
- }
- // s.monitor.SetConfig(c.HTTPClient)
- for i := int64(0); i < _sharding; i++ {
- i := i
- // for stat
- s.statCh[i] = make(chan *artmdl.StatMsg, _chanSize)
- s.statLastTime[i] = make(map[int64]*lastTimeStat)
- s.waiter.Add(1)
- go s.statproc(i)
- }
- s.loadCategories()
- s.loadSettings()
- // 10 go routines with WaitGroup
- s.waiter.Add(10)
- go s.consumeStat()
- go s.consumeCanal()
- go s.checkConsumer()
- go s.retryStat()
- go s.retryReply()
- go s.retryGame()
- go s.retryFlow()
- go s.retryDynamic()
- go s.retryCDN()
- go s.retryCache()
- // other go routines
- go s.updateSortproc()
- go s.activityLikeproc()
- go s.updateReadCountproc()
- go s.updateHotspotsproc()
- go s.updateCheatArtsproc()
- go s.loadCategoriesproc()
- go s.loadSettingsproc()
- go s.recommendAuthorproc()
- go s.checkReadStatusProc()
- go s.infocproc()
- go s.sitemapproc()
- return
- }
- // consumeStat consumes article's stat.
- func (s *Service) consumeStat() {
- defer s.waiter.Done()
- for {
- if s.closed {
- for i := 0; i < _sharding; i++ {
- close(s.statCh[i])
- }
- log.Info("databus: article-job stat consumer exit!")
- return
- }
- L:
- select {
- case msg, ok := <-s.articleStatSub.Messages():
- if !ok {
- break L
- }
- s.statCnt++
- msg.Commit()
- sm := &artmdl.StatMsg{}
- if err := json.Unmarshal(msg.Value, sm); err != nil {
- log.Error("json.Unmarshal(%s) error(%+v)", msg.Value, err)
- dao.PromError("service:解析计数databus消息")
- break L
- }
- if sm.Aid <= 0 {
- log.Warn("aid(%d) <=0 message(%s)", sm.Aid, msg.Value)
- break L
- }
- key := sm.Aid % _sharding
- s.statCh[key] <- sm
- prom.BusinessInfoCount.State(fmt.Sprintf("statChan-%v", key), int64(len(s.statCh[key])))
- log.Info("consumeStat key:%s partition:%d offset:%d msg: %v)", msg.Key, msg.Partition, msg.Offset, sm.String())
- case msg, ok := <-s.likeStatSub.Messages():
- if !ok {
- break L
- }
- msg.Commit()
- like := &thumdl.StatMsg{}
- if err := json.Unmarshal(msg.Value, like); err != nil {
- log.Error("json.Unmarshal(%s) error(%+v)", msg.Value, err)
- dao.PromError("service:解析like计数databus消息")
- break L
- }
- if like.Type != "article" {
- continue
- }
- select {
- case s.likeCh <- like:
- default:
- }
- key := like.ID % _sharding
- s.statCh[key] <- &artmdl.StatMsg{Like: &like.Count, Dislike: &like.DislikeCount, Aid: like.ID}
- prom.BusinessInfoCount.State(fmt.Sprintf("statChan-%v", key), int64(len(s.statCh[key])))
- log.Info("consumeLikeStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, like)
- case msg, ok := <-s.replyStatSub.Messages():
- if !ok {
- break L
- }
- msg.Commit()
- m := &thumdl.StatMsg{}
- if err := json.Unmarshal(msg.Value, m); err != nil {
- log.Error("reply json.Unmarshal(%s) error(%+v)", msg.Value, err)
- dao.PromError("service:解析reply计数databus消息")
- break L
- }
- if m.Type != "article" {
- continue
- }
- key := m.ID % _sharding
- s.statCh[key] <- &artmdl.StatMsg{Reply: &m.Count, Aid: m.ID}
- prom.BusinessInfoCount.State(fmt.Sprintf("statChan-%v", key), int64(len(s.statCh[key])))
- log.Info("consumeReplyStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, m)
- case msg, ok := <-s.favoriteStatSub.Messages():
- if !ok {
- break L
- }
- msg.Commit()
- m := &thumdl.StatMsg{}
- if err := json.Unmarshal(msg.Value, m); err != nil {
- log.Error("favorite json.Unmarshal(%s) error(%+v)", msg.Value, err)
- dao.PromError("service:解析favorite计数databus消息")
- break L
- }
- if m.Type != "article" {
- continue
- }
- key := m.ID % _sharding
- s.statCh[key] <- &artmdl.StatMsg{Favorite: &m.Count, Aid: m.ID}
- prom.BusinessInfoCount.State(fmt.Sprintf("statChan-%v", key), int64(len(s.statCh[key])))
- log.Info("consumeFavoriteStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, m)
- case msg, ok := <-s.coinStatSub.Messages():
- if !ok {
- break L
- }
- msg.Commit()
- m := &thumdl.StatMsg{}
- if err := json.Unmarshal(msg.Value, m); err != nil {
- log.Error("coin json.Unmarshal(%s) error(%+v)", msg.Value, err)
- dao.PromError("service:解析coin计数databus消息")
- break L
- }
- if m.Type != "article" {
- continue
- }
- key := m.ID % _sharding
- s.statCh[key] <- &artmdl.StatMsg{Coin: &m.Count, Aid: m.ID}
- prom.BusinessInfoCount.State(fmt.Sprintf("statChan-%v", key), int64(len(s.statCh[key])))
- log.Info("consumeCoinStat key:%s partition:%d offset:%d msg: %+v)", msg.Key, msg.Partition, msg.Offset, m)
- }
- }
- }
- // consumeCanal consumes article's binlog databus.
- func (s *Service) consumeCanal() {
- defer s.waiter.Done()
- var c = context.TODO()
- for {
- msg, ok := <-s.articleSub.Messages()
- if !ok {
- log.Info("databus: article-job binlog consumer exit!")
- return
- }
- s.binCnt++
- msg.Commit()
- m := &model.Message{}
- if err := json.Unmarshal(msg.Value, m); err != nil {
- log.Error("json.Unmarshal(%s) error(%+v)", msg.Value, err)
- continue
- }
- switch m.Table {
- case _articleTable:
- s.upArticles(c, m.Action, m.New, m.Old)
- case _authorTable:
- s.upAuthors(c, m.Action, m.New, m.Old)
- }
- log.Info("consumeCanal key:%s partition:%d offset:%d", msg.Key, msg.Partition, msg.Offset)
- }
- }
- func (s *Service) retryStat() {
- defer s.waiter.Done()
- var (
- err error
- bs []byte
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- bs, err = s.dao.PopStat(c)
- if err != nil || bs == nil {
- time.Sleep(time.Second)
- continue
- }
- msg := &dao.StatRetry{}
- if err = json.Unmarshal(bs, msg); err != nil {
- log.Error("json.Unmarshal(%s) error(%+v)", bs, err)
- dao.PromError("service:解析计数重试消息")
- continue
- }
- if msg.Count > dao.RetryStatCount {
- continue
- }
- log.Info("retry: %s", bs)
- switch msg.Action {
- case dao.RetryUpdateStatCache:
- if err = s.updateCache(c, msg.Data, msg.Count+1); err != nil {
- time.Sleep(100 * time.Millisecond)
- }
- dao.PromInfo("service:重试计数更新缓存")
- case dao.RetryUpdateStatDB:
- if err = s.updateDB(c, msg.Data, msg.Count+1); err != nil {
- time.Sleep(100 * time.Millisecond)
- }
- dao.PromInfo("service:重试计数更新DB")
- }
- }
- }
- func (s *Service) retryReply() {
- defer s.waiter.Done()
- var (
- err error
- aid, mid int64
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- aid, mid, err = s.dao.PopReply(c)
- if err != nil || aid == 0 || mid == 0 {
- time.Sleep(time.Second)
- continue
- }
- log.Info("retry reply: aid(%d) mid(%d)", aid, mid)
- if err = s.openReply(c, aid, mid); err != nil {
- log.Error("s.openReply(%d,%d) error(%+v)", aid, mid, err)
- dao.PromInfo("service:重试打开评论区")
- time.Sleep(100 * time.Millisecond)
- continue
- }
- log.Info("s.openReply(%d,%d) retry success", aid, mid)
- }
- }
- func (s *Service) retryCDN() {
- defer s.waiter.Done()
- var (
- err error
- file string
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- file, err = s.dao.PopCDN(c)
- if err != nil || file == "" {
- time.Sleep(time.Second)
- continue
- }
- log.Info("retry CDN: file(%s)", file)
- if err = s.dao.PurgeCDN(c, file); err != nil {
- log.Error("s.dao.PurgeCDN(%s) error(%+v)", file, err)
- dao.PromInfo("service:刷新CDN重试")
- time.Sleep(100 * time.Millisecond)
- continue
- }
- log.Info("s.dao.PurgeCDN(%s) retry success.", file)
- }
- }
- func (s *Service) retryCache() {
- defer s.waiter.Done()
- var (
- err error
- bs []byte
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- bs, err = s.dao.PopArtCache(c)
- if err != nil || bs == nil {
- time.Sleep(time.Second)
- continue
- }
- msg := &dao.CacheRetry{}
- if err = json.Unmarshal(bs, msg); err != nil {
- log.Error("json.Unmarshal(%s) error(%+v)", bs, err)
- dao.PromError("service:解析文章缓存重试消息")
- continue
- }
- log.Info("retry cache: %s", bs)
- switch msg.Action {
- case dao.RetryAddArtCache:
- if err = s.addArtCache(c, msg.Aid); err != nil {
- time.Sleep(100 * time.Millisecond)
- }
- dao.PromInfo("service:重试添加文章缓存")
- case dao.RetryUpdateArtCache:
- if err = s.updateArtCache(c, msg.Aid, msg.Cid); err != nil {
- time.Sleep(100 * time.Millisecond)
- }
- dao.PromInfo("service:重试更新文章缓存")
- case dao.RetryDeleteArtCache:
- if err = s.deleteArtCache(c, msg.Aid, msg.Mid); err != nil {
- time.Sleep(100 * time.Millisecond)
- }
- dao.PromInfo("service:重试删除文章缓存")
- case dao.RetryDeleteArtRecCache:
- if err = s.deleteArtRecommendCache(c, msg.Aid, msg.Cid); err != nil {
- time.Sleep(100 * time.Millisecond)
- }
- dao.PromInfo("service:重试删除文章推荐缓存")
- }
- }
- }
- // checkConsumer checks consumer state.
- func (s *Service) checkConsumer() {
- defer s.waiter.Done()
- if env.DeployEnv != env.DeployEnvProd {
- return
- }
- var (
- // ctx = context.TODO()
- // c* means sum of consumed messages.
- c1, c2 int64
- )
- for {
- time.Sleep(5 * time.Hour)
- if s.statCnt-c1 == 0 {
- // msg := "databus: article-job stat did not consume within a minute"
- // s.monitor.Sms(ctx, s.c.SMS.Phone, s.c.SMS.Token, msg)
- // log.Warn(msg)
- log.Warn("databus: article-job stat did not consume within a minute")
- }
- c1 = s.statCnt
- if s.binCnt-c2 == 0 {
- // msg := "databus: article-job binlog did not consume within a minute"
- // s.monitor.Sms(ctx, s.c.SMS.Phone, s.c.SMS.Token, msg)
- // log.Warn(msg)
- log.Warn("databus: article-job binlog did not consume within a minute")
- }
- c2 = s.binCnt
- }
- }
- // Ping reports the heath of services.
- func (s *Service) Ping(c context.Context) (err error) {
- return s.dao.Ping(c)
- }
- // Close releases resources which owned by the Service instance.
- func (s *Service) Close() (err error) {
- defer s.waiter.Wait()
- s.articleSub.Close()
- s.articleStatSub.Close()
- s.closed = true
- log.Info("article-job has been closed.")
- return
- }
- func (s *Service) retryGame() {
- defer s.waiter.Done()
- var (
- err error
- info *model.GameCacheRetry
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- info, err = s.dao.PopGameCache(c)
- if (err != nil) || (info == nil) {
- time.Sleep(time.Second)
- continue
- }
- for {
- log.Info("retry_game: %+v", info)
- dao.PromInfo("service:重试同步游戏")
- if err = s.dao.GameSync(c, info.Action, info.Aid); err != nil {
- time.Sleep(time.Millisecond * 100)
- continue
- }
- break
- }
- log.Info("s.GameSync(%s, aid: %d) retry success", info.Action, info.Aid)
- }
- }
- func (s *Service) activityLikeproc() {
- var c = context.TODO()
- for {
- msg, ok := <-s.likeCh
- if !ok {
- log.Info("activityLikeproc: exit!")
- return
- }
- s.dao.LikeSync(c, msg.ID, msg.Count)
- }
- }
- func (s *Service) retryFlow() {
- defer s.waiter.Done()
- var (
- err error
- info *model.FlowCacheRetry
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- info, err = s.dao.PopFlowCache(c)
- if (err != nil) || (info == nil) {
- time.Sleep(time.Second)
- continue
- }
- for {
- log.Info("retry_flow: %+v", info)
- dao.PromInfo("service:重试同步flow")
- if err = s.dao.FlowSync(c, info.Mid, info.Aid); err != nil {
- time.Sleep(time.Second * 1)
- continue
- }
- break
- }
- log.Info("s.FlowSync(mid:%v, aid: %d) retry success", info.Mid, info.Aid)
- }
- }
- func (s *Service) retryDynamic() {
- defer s.waiter.Done()
- var (
- err error
- info *model.DynamicCacheRetry
- c = context.TODO()
- )
- for {
- if s.closed {
- return
- }
- info, err = s.dao.PopDynamicCache(c)
- if (err != nil) || (info == nil) {
- time.Sleep(time.Second)
- continue
- }
- for {
- log.Info("retry_dynamic: %+v", info)
- dao.PromInfo("service:重试同步dynamic")
- if err = s.dao.PubDynamic(c, info.Mid, info.Aid, info.Show, info.Comment, info.Ts, info.DynamicIntro); err != nil {
- time.Sleep(time.Second * 1)
- continue
- }
- break
- }
- log.Info("s.PubDynamic(mid:%v, aid: %d) retry success %+v", info.Mid, info.Aid, info)
- }
- }
- func (s *Service) updateReadCountproc() {
- var c = context.TODO()
- for {
- err := s.articleRPC.RebuildAllListReadCount(c)
- if err != nil {
- log.Error("s.updateReadCountproc() err: %+v", err)
- time.Sleep(time.Second * 5)
- continue
- }
- log.Info("s.updateReadCountproc() success")
- dao.PromError("更新文集阅读数")
- time.Sleep(time.Duration(s.c.Job.ListReadCountInterval))
- }
- }
- func (s *Service) updateHotspotsproc() {
- var c = context.TODO()
- var lastUpdate int64
- for {
- var force bool
- duration := int64(time.Duration(s.c.Job.HotspotForceInterval) / time.Second)
- if (time.Now().Unix() - lastUpdate) > duration {
- force = true
- }
- err := s.articleRPC.UpdateHotspots(c, &artmdl.ArgForce{Force: force})
- if err != nil {
- log.Error("s.UpdateHotspots() err: %+v", err)
- dao.PromError("更新热点运营文章")
- time.Sleep(time.Second * 5)
- continue
- }
- if force {
- lastUpdate = time.Now().Unix()
- }
- log.Info("s.UpdateHotspots() success force:%v", force)
- time.Sleep(time.Duration(s.c.Job.HotspotInterval))
- }
- }
- func (s *Service) updateCheatArtsproc() {
- var c = context.TODO()
- for {
- arts, err := s.dao.CheatArts(c)
- if err != nil {
- log.Error("s.updateCheatArtsproc() err: %+v", err)
- dao.PromError("更新反作弊文章列表")
- time.Sleep(time.Second * 5)
- continue
- }
- log.Info("s.updateCheatArtsproc() success, len: %v", len(arts))
- s.cheatArts = arts
- time.Sleep(time.Minute)
- }
- }
|