table_queue.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. package dbQueue
  2. import (
  3. "context"
  4. "f1-game/internal/extend/maps"
  5. "f1-game/internal/pb"
  6. "runtime/debug"
  7. "sync"
  8. "sync/atomic"
  9. "time"
  10. "go.mongodb.org/mongo-driver/v2/bson"
  11. "go.mongodb.org/mongo-driver/v2/mongo"
  12. mopts "go.mongodb.org/mongo-driver/v2/mongo/options"
  13. ctime "github.com/cherry-game/cherry/extend/time"
  14. dbFacade "f1-game/internal/component/db/facade"
  15. cstring "github.com/cherry-game/cherry/extend/string"
  16. clog "github.com/cherry-game/cherry/logger"
  17. )
  18. type (
  19. TableQueue struct {
  20. workers map[string]*tableWorker // worker map
  21. db *mongo.Database // gorm db
  22. onStopScanSecond time.Duration // 停机时扫描表等待时间
  23. producerWg sync.WaitGroup // 追踪 run (生产者)
  24. isRun bool // 是否启动
  25. }
  26. tableWorker struct {
  27. options
  28. tableName string // 表名
  29. dataMaps *maps.OrderedMap[any, *mongo.UpdateOneModel] // 所有待保存的数据 key:hash, value:*Table
  30. submitCount int64 // 提交到保存队列的总数
  31. mongoUpdateOpts *mopts.FindOneAndUpdateOptionsBuilder // mongodb update options
  32. }
  33. options struct {
  34. db *mongo.Database // mongodb
  35. saveChanSize int // chan的大小
  36. saveTicker time.Duration // 默认保存频率(秒)
  37. saveCount int // 默认每次保存的数量
  38. onStopSaveTicker time.Duration // 停机时保存频率(秒)
  39. onStopSaveCount int // 停机时每次保存的数量
  40. printSaveLog bool // 是否打印保存日志
  41. }
  42. OptionFunc func(options *options)
  43. )
  44. func newOption(db *mongo.Database) options {
  45. return options{
  46. db: db,
  47. saveChanSize: 10240,
  48. saveTicker: 60 * time.Second,
  49. saveCount: 500,
  50. onStopSaveTicker: 1 * time.Second,
  51. onStopSaveCount: 1000,
  52. printSaveLog: false,
  53. }
  54. }
  55. func NewTableQueue(db *mongo.Database) *TableQueue {
  56. if db == nil {
  57. clog.Fatal("db is nil")
  58. return nil
  59. }
  60. queue := &TableQueue{
  61. db: db,
  62. workers: make(map[string]*tableWorker),
  63. onStopScanSecond: 3 * time.Second,
  64. isRun: false,
  65. }
  66. return queue
  67. }
  68. func (p *TableQueue) AddTable(table dbFacade.Table, optsFunc ...OptionFunc) {
  69. if p.isRun {
  70. clog.Warn("TableQueue already started")
  71. return
  72. }
  73. if table == nil {
  74. clog.Warn("table is nil")
  75. return
  76. }
  77. if cstring.IsBlank(table.TableName()) {
  78. clog.Warn("table name is empty.")
  79. return
  80. }
  81. if _, found := p.workers[table.TableName()]; found {
  82. clog.Warnf("duplicate table = %s name.", table.TableName())
  83. return
  84. }
  85. opts := newOption(p.db)
  86. for _, optionFunc := range optsFunc {
  87. optionFunc(&opts)
  88. }
  89. worker := &tableWorker{
  90. options: opts,
  91. tableName: table.TableName(),
  92. dataMaps: maps.NewOrderMap[any, *mongo.UpdateOneModel](opts.saveChanSize, 10),
  93. submitCount: 0,
  94. mongoUpdateOpts: mopts.FindOneAndUpdate().SetUpsert(true), // set upsert
  95. }
  96. p.workers[worker.tableName] = worker
  97. clog.Infof("add [table = %s, ticker = %d(sec), count = %d]",
  98. worker.tableName,
  99. int(worker.saveTicker.Seconds()),
  100. worker.saveCount,
  101. )
  102. }
  103. func (p *TableQueue) AddTables(tables []dbFacade.Table, optsFunc ...OptionFunc) {
  104. for _, table := range tables {
  105. p.AddTable(table, optsFunc...)
  106. }
  107. }
  108. func (p *TableQueue) Start() {
  109. if p.isRun {
  110. clog.Warn("TableQueue already started")
  111. return
  112. }
  113. p.isRun = true
  114. clog.Infof("db queue starting...")
  115. for _, table := range p.workers {
  116. p.producerWg.Add(1)
  117. go table.run(&p.producerWg)
  118. }
  119. }
  120. func (p *TableQueue) Stop() {
  121. clog.Infof("stopping db queue....")
  122. for _, tw := range p.workers {
  123. tw.saveTicker = tw.onStopSaveTicker
  124. tw.saveCount = tw.onStopSaveCount
  125. }
  126. // 阶段 1: 等待所有内存数据刷入
  127. for {
  128. if m := p.allWorkerDataCount(); m < 1 {
  129. break
  130. }
  131. // wait...
  132. clog.Debugf("saving data! waiting in %d seconds.", int(p.onStopScanSecond.Seconds()))
  133. time.Sleep(p.onStopScanSecond)
  134. }
  135. clog.Infof("stopping db queue finish!")
  136. // 阶段 2: 关闭数据源 (这会触发 run 协程退出)
  137. for _, worker := range p.workers {
  138. worker.dataMaps.Close()
  139. }
  140. // 阶段 3: 等待所有生产者 (run 协程) 退出
  141. p.producerWg.Wait()
  142. }
  143. // 对象进入队列
  144. func (p *TableQueue) InQueue(table dbFacade.Table) {
  145. worker, found := p.workers[table.TableName()]
  146. if !found {
  147. clog.Errorf("[table = %s] unregister!", table.TableName())
  148. return
  149. }
  150. // flush update time
  151. table.FlushUpdateTime(ctime.Now().ToMillisecond())
  152. table.CheckField()
  153. // marshal to bytes
  154. tableBytes, err := bson.Marshal(table)
  155. if err != nil {
  156. clog.Warnf("[table = %s] Marshal table to bytes error. [err = %v]", table.TableName(), err)
  157. return
  158. }
  159. // set update
  160. update := bson.D{{Key: "$set", Value: bson.Raw(tableBytes)}}
  161. // set filter
  162. filter := bson.M{
  163. "_id": table.PrimaryValue(), // 利用自带的_id主键进行条件过滤
  164. }
  165. // build *mongo.UpdateOneModel
  166. model := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
  167. worker.dataMaps.Set(table.PrimaryValue(), model)
  168. }
  169. func (p *TableQueue) allWorkerDataCount() int {
  170. var allDataMapCount = 0
  171. for _, worker := range p.workers {
  172. allDataMapCount += worker.dataMaps.Len()
  173. }
  174. return allDataMapCount
  175. }
  176. func (p *TableQueue) allSubmitCount() int64 {
  177. var allSubmitCount int64 = 0
  178. for _, worker := range p.workers {
  179. allSubmitCount += worker.getSubmitCount()
  180. }
  181. return allSubmitCount
  182. }
  183. func (p *TableQueue) Report() *pb.DBQueueReportList {
  184. list := &pb.DBQueueReportList{}
  185. for _, worker := range p.workers {
  186. list.List = append(list.List, &pb.DBQueueReport{
  187. TableName: worker.tableName,
  188. RemainCount: int64(worker.dataMaps.Len()),
  189. SubmitCount: worker.getSubmitCount(),
  190. })
  191. }
  192. return list
  193. }
  194. func (p *tableWorker) run(wg *sync.WaitGroup) {
  195. defer wg.Done()
  196. clog.Infof("run queue for [table = %s]", p.tableName)
  197. checkTicker := time.NewTicker(100 * time.Millisecond)
  198. defer checkTicker.Stop()
  199. lastTicker := ctime.Now()
  200. for {
  201. select {
  202. case <-p.dataMaps.GetStopChan():
  203. return
  204. case t := <-checkTicker.C:
  205. if t.Unix() >= lastTicker.Add(p.saveTicker).Unix() {
  206. lastTicker = ctime.Now()
  207. // Use PopBatch to get multiple items at once
  208. list := p.dataMaps.PopBatch(p.saveCount)
  209. listLen := len(list)
  210. if listLen > 0 {
  211. p.submitIncrease(listLen)
  212. p.bulkWrite(list)
  213. p.submitDecrease(listLen)
  214. }
  215. }
  216. }
  217. }
  218. }
  219. func (p *tableWorker) bulkWrite(list []*mongo.UpdateOneModel) {
  220. defer func() {
  221. if r := recover(); r != nil {
  222. clog.Warnf("[table = %s] recover in executor. %s", p.tableName, string(debug.Stack()))
  223. for _, model := range list {
  224. clog.Warnf("%+v", model)
  225. }
  226. }
  227. }()
  228. // 构建批量更新对象
  229. models := make([]mongo.WriteModel, 0, len(list))
  230. for _, model := range list {
  231. models = append(models, model)
  232. }
  233. bulkWriteOptions := mopts.BulkWrite().SetOrdered(false)
  234. _, err := p.db.Collection(p.tableName).BulkWrite(context.TODO(), models, bulkWriteOptions)
  235. if err != nil && err != mongo.ErrNoDocuments {
  236. // ignore error: no document
  237. clog.Warnf("[table = %s] save error. [error = %v]", p.tableName, err)
  238. for _, model := range list {
  239. clog.Warnf("%+v", model)
  240. }
  241. } else {
  242. clog.Debugf("[table = %s, size = %d] queue bulk write.", p.tableName, len(list))
  243. }
  244. }
  245. func (p *tableWorker) submitIncrease(num int) {
  246. atomic.AddInt64(&p.submitCount, int64(num))
  247. }
  248. func (p *tableWorker) submitDecrease(num int) {
  249. atomic.AddInt64(&p.submitCount, -int64(num))
  250. }
  251. func (p *tableWorker) getSubmitCount() int64 {
  252. return atomic.LoadInt64(&p.submitCount)
  253. }
  254. func SetChanSize(saveChanSize int) OptionFunc {
  255. return func(options *options) {
  256. if saveChanSize > 0 {
  257. options.saveChanSize = saveChanSize
  258. }
  259. }
  260. }
  261. func SetTicker(saveTicker time.Duration, saveCount int) OptionFunc {
  262. return func(options *options) {
  263. if saveTicker > 10*time.Millisecond {
  264. options.saveTicker = saveTicker
  265. }
  266. if saveCount > 0 {
  267. options.saveCount = saveCount
  268. }
  269. }
  270. }
  271. func SetOnStopTicker(onStopSaveTicker time.Duration, onStopSaveCount int) OptionFunc {
  272. return func(options *options) {
  273. if onStopSaveTicker > 10*time.Millisecond {
  274. options.onStopSaveTicker = onStopSaveTicker
  275. }
  276. if onStopSaveCount > 0 {
  277. options.onStopSaveCount = onStopSaveCount
  278. }
  279. }
  280. }
  281. func SetPrintSaveLog(printSaveLog bool) OptionFunc {
  282. return func(options *options) {
  283. options.printSaveLog = printSaveLog
  284. }
  285. }