github.com/TeaOSLab/EdgeNode@v1.3.8/internal/metrics/task_sqlite.go (about)

     1  // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package metrics
     4  
     5  import (
     6  	"encoding/json"
     7  	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
     9  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
    10  	"github.com/TeaOSLab/EdgeNode/internal/goman"
    11  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    12  	"github.com/TeaOSLab/EdgeNode/internal/rpc"
    13  	"github.com/TeaOSLab/EdgeNode/internal/trackers"
    14  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    15  	"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
    16  	"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
    17  	"github.com/TeaOSLab/EdgeNode/internal/zero"
    18  	"github.com/iwind/TeaGo/Tea"
    19  	"github.com/iwind/TeaGo/types"
    20  	"os"
    21  	"strconv"
    22  	"sync"
    23  	"time"
    24  )
    25  
    26  const MaxQueueSize = 256 // TODO 可以配置,可以在单个任务里配置
    27  
    28  // SQLiteTask 单个指标任务
    29  // 数据库存储:
    30  //
    31  //	data/
    32  //	   metric.$ID.db
    33  //	      stats
    34  //	         id, keys, value, time, serverId, hash
    35  //	原理:
    36  //	   添加或者有变更时 isUploaded = false
    37  //	   上传时检查 isUploaded 状态
    38  //	   只上传每个服务中排序最前面的 N 个数据
    39  type SQLiteTask struct {
    40  	BaseTask
    41  
    42  	db            *dbs.DB
    43  	statTableName string
    44  
    45  	cleanTicker  *time.Ticker
    46  	uploadTicker *utils.Ticker
    47  
    48  	cleanVersion int32
    49  
    50  	insertStatStmt          *dbs.Stmt
    51  	deleteByVersionStmt     *dbs.Stmt
    52  	deleteByExpiresTimeStmt *dbs.Stmt
    53  	selectTopStmt           *dbs.Stmt
    54  	sumStmt                 *dbs.Stmt
    55  
    56  	serverIdMap       map[int64]zero.Zero  // 所有的服务Ids
    57  	timeMap           map[string]zero.Zero // time => bool
    58  	serverIdMapLocker sync.Mutex
    59  
    60  	statsTicker *utils.Ticker
    61  }
    62  
    63  // NewSQLiteTask 获取新任务
    64  func NewSQLiteTask(item *serverconfigs.MetricItemConfig) *SQLiteTask {
    65  	return &SQLiteTask{
    66  		BaseTask: BaseTask{
    67  			itemConfig: item,
    68  			statsMap:   map[string]*Stat{},
    69  		},
    70  
    71  		serverIdMap: map[int64]zero.Zero{},
    72  		timeMap:     map[string]zero.Zero{},
    73  	}
    74  }
    75  
    76  func CheckSQLiteDB(itemId int64) bool {
    77  	var path = Tea.Root + "/data/metric." + types.String(itemId) + ".db"
    78  	_, err := os.Stat(path)
    79  	return err == nil
    80  }
    81  
    82  // Init 初始化
    83  func (this *SQLiteTask) Init() error {
    84  	this.statTableName = "stats"
    85  
    86  	// 检查目录是否存在
    87  	var dir = Tea.Root + "/data"
    88  	_, err := os.Stat(dir)
    89  	if err != nil {
    90  		err = os.MkdirAll(dir, 0777)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		remotelogs.Println("METRIC", "create data dir '"+dir+"'")
    95  	}
    96  
    97  	var path = dir + "/metric." + types.String(this.itemConfig.Id) + ".db"
    98  
    99  	db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
   100  	if err != nil {
   101  		return err
   102  	}
   103  	db.SetMaxOpenConns(1)
   104  	this.db = db
   105  
   106  	// 恢复数据库
   107  	var recoverEnv, _ = os.LookupEnv("EdgeRecover")
   108  	if len(recoverEnv) > 0 {
   109  		for _, indexName := range []string{"serverId", "hash"} {
   110  			_, _ = db.Exec(`REINDEX "` + indexName + `"`)
   111  		}
   112  	}
   113  
   114  	if teaconst.EnableDBStat {
   115  		this.db.EnableStat(true)
   116  	}
   117  
   118  	//创建统计表
   119  	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
   120    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
   121    "hash" varchar(32),
   122    "keys" varchar(1024),
   123    "value" real DEFAULT 0,
   124    "time" varchar(32),
   125    "serverId" integer DEFAULT 0,
   126    "version" integer DEFAULT 0,
   127    "isUploaded" integer DEFAULT 0
   128  );
   129  
   130  CREATE INDEX IF NOT EXISTS "serverId"
   131  ON "` + this.statTableName + `" (
   132    "serverId" ASC,
   133    "version" ASC
   134  );
   135  
   136  CREATE UNIQUE INDEX IF NOT EXISTS "hash"
   137  ON "` + this.statTableName + `" (
   138    "hash" ASC
   139  );`)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	// insert stat stmt
   145  	this.insertStatStmt, err = db.Prepare(`INSERT INTO "stats" ("serverId", "hash", "keys", "value", "time", "version", "isUploaded") VALUES (?, ?, ?, ?, ?, ?, 0) ON CONFLICT("hash") DO UPDATE SET "value"="value"+?, "isUploaded"=0`)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	// delete by version
   151  	this.deleteByVersionStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "version"<?`)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	// delete by expires time
   157  	this.deleteByExpiresTimeStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "time"<?`)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	// select topN stmt
   163  	this.selectTopStmt, err = db.Prepare(`SELECT "id", "hash", "keys", "value", "isUploaded" FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=? ORDER BY "value" DESC LIMIT 20`)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	// sum stmt
   169  	this.sumStmt, err = db.Prepare(`SELECT COUNT(*), IFNULL(SUM(value), 0) FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=?`)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	// 所有的服务IDs
   175  	err = this.loadServerIdMap()
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	this.isLoaded = true
   181  
   182  	return nil
   183  }
   184  
   185  // Start 启动任务
   186  func (this *SQLiteTask) Start() error {
   187  	// 读取数据
   188  	this.statsTicker = utils.NewTicker(1 * time.Minute)
   189  	goman.New(func() {
   190  		for this.statsTicker.Next() {
   191  			var tr = trackers.Begin("METRIC:DUMP_STATS_TO_LOCAL_DATABASE")
   192  
   193  			this.statsLocker.Lock()
   194  			var statsMap = this.statsMap
   195  			this.statsMap = map[string]*Stat{}
   196  			this.statsLocker.Unlock()
   197  
   198  			for _, stat := range statsMap {
   199  				err := this.InsertStat(stat)
   200  				if err != nil {
   201  					remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
   202  				}
   203  			}
   204  
   205  			tr.End()
   206  		}
   207  	})
   208  
   209  	// 清理
   210  	this.cleanTicker = time.NewTicker(24 * time.Hour)
   211  	goman.New(func() {
   212  		idles.RunTicker(this.cleanTicker, func() {
   213  			var tr = trackers.Begin("METRIC:CLEAN_EXPIRED")
   214  			err := this.CleanExpired()
   215  			tr.End()
   216  			if err != nil {
   217  				remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
   218  			}
   219  		})
   220  	})
   221  
   222  	// 上传
   223  	this.uploadTicker = utils.NewTicker(this.itemConfig.UploadDuration())
   224  	goman.New(func() {
   225  		for this.uploadTicker.Next() {
   226  			err := this.Upload(1 * time.Second)
   227  			if err != nil && !rpc.IsConnError(err) {
   228  				remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
   229  			}
   230  		}
   231  	})
   232  
   233  	return nil
   234  }
   235  
   236  // Stop 停止任务
   237  func (this *SQLiteTask) Stop() error {
   238  	this.isStopped = true
   239  
   240  	if this.cleanTicker != nil {
   241  		this.cleanTicker.Stop()
   242  	}
   243  	if this.uploadTicker != nil {
   244  		this.uploadTicker.Stop()
   245  	}
   246  	if this.statsTicker != nil {
   247  		this.statsTicker.Stop()
   248  	}
   249  
   250  	_ = this.insertStatStmt.Close()
   251  	_ = this.deleteByVersionStmt.Close()
   252  	_ = this.deleteByExpiresTimeStmt.Close()
   253  	_ = this.selectTopStmt.Close()
   254  	_ = this.sumStmt.Close()
   255  
   256  	if this.db != nil {
   257  		_ = this.db.Close()
   258  	}
   259  
   260  	return nil
   261  }
   262  
   263  func (this *SQLiteTask) Delete() error {
   264  	return nil
   265  }
   266  
   267  // InsertStat 写入数据
   268  func (this *SQLiteTask) InsertStat(stat *Stat) error {
   269  	if this.isStopped {
   270  		return nil
   271  	}
   272  	if stat == nil {
   273  		return nil
   274  	}
   275  
   276  	this.serverIdMapLocker.Lock()
   277  	this.serverIdMap[stat.ServerId] = zero.New()
   278  	this.timeMap[stat.Time] = zero.New()
   279  	this.serverIdMapLocker.Unlock()
   280  
   281  	keyData, err := json.Marshal(stat.Keys)
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	_, err = this.insertStatStmt.Exec(stat.ServerId, stat.Hash, keyData, stat.Value, stat.Time, this.itemConfig.Version, stat.Value)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	return nil
   291  }
   292  
   293  // CleanExpired 清理数据
   294  func (this *SQLiteTask) CleanExpired() error {
   295  	if this.isStopped {
   296  		return nil
   297  	}
   298  
   299  	// 清除低版本数据
   300  	if this.cleanVersion < this.itemConfig.Version {
   301  		_, err := this.deleteByVersionStmt.Exec(this.itemConfig.Version)
   302  		if err != nil {
   303  			return err
   304  		}
   305  		this.cleanVersion = this.itemConfig.Version
   306  	}
   307  
   308  	// 清除过期的数据
   309  	_, err := this.deleteByExpiresTimeStmt.Exec(this.itemConfig.LocalExpiresTime())
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  // Upload 上传数据
   318  func (this *SQLiteTask) Upload(pauseDuration time.Duration) error {
   319  	var uploadTr = trackers.Begin("METRIC:UPLOAD_STATS")
   320  	defer uploadTr.End()
   321  
   322  	if this.isStopped {
   323  		return nil
   324  	}
   325  
   326  	this.serverIdMapLocker.Lock()
   327  
   328  	// 服务IDs
   329  	var serverIds []int64
   330  	for serverId := range this.serverIdMap {
   331  		serverIds = append(serverIds, serverId)
   332  	}
   333  	this.serverIdMap = map[int64]zero.Zero{} // 清空数据
   334  
   335  	// 时间
   336  	var times = []string{}
   337  	for t := range this.timeMap {
   338  		times = append(times, t)
   339  	}
   340  	this.timeMap = map[string]zero.Zero{} // 清空数据
   341  
   342  	this.serverIdMapLocker.Unlock()
   343  
   344  	rpcClient, err := rpc.SharedRPC()
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	for _, serverId := range serverIds {
   350  		for _, currentTime := range times {
   351  			idStrings, err := func(serverId int64, currentTime string) (ids []string, err error) {
   352  				var t = trackers.Begin("METRIC:SELECT_TOP_STMT")
   353  				rows, err := this.selectTopStmt.Query(serverId, this.itemConfig.Version, currentTime)
   354  				t.End()
   355  				if err != nil {
   356  					return nil, err
   357  				}
   358  				var isClosed bool
   359  				defer func() {
   360  					if isClosed {
   361  						return
   362  					}
   363  					_ = rows.Close()
   364  				}()
   365  
   366  				var pbStats []*pb.UploadingMetricStat
   367  				for rows.Next() {
   368  					var pbStat = &pb.UploadingMetricStat{}
   369  					// "id", "hash", "keys", "value", "isUploaded"
   370  					var isUploaded int
   371  					var keysData []byte
   372  					err = rows.Scan(&pbStat.Id, &pbStat.Hash, &keysData, &pbStat.Value, &isUploaded)
   373  					if err != nil {
   374  						return nil, err
   375  					}
   376  
   377  					// TODO 先不判断是否已经上传,需要改造API进行配合
   378  					/**if isUploaded == 1 {
   379  						continue
   380  					}**/
   381  					if len(keysData) > 0 {
   382  						err = json.Unmarshal(keysData, &pbStat.Keys)
   383  						if err != nil {
   384  							return nil, err
   385  						}
   386  					}
   387  					pbStats = append(pbStats, pbStat)
   388  					ids = append(ids, strconv.FormatInt(pbStat.Id, 10))
   389  				}
   390  
   391  				// 提前关闭
   392  				_ = rows.Close()
   393  				isClosed = true
   394  
   395  				// 上传
   396  				if len(pbStats) > 0 {
   397  					// 计算总和
   398  					count, total, err := this.sum(serverId, currentTime)
   399  					if err != nil {
   400  						return nil, err
   401  					}
   402  
   403  					_, err = rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
   404  						MetricStats: pbStats,
   405  						Time:        currentTime,
   406  						ServerId:    serverId,
   407  						ItemId:      this.itemConfig.Id,
   408  						Version:     this.itemConfig.Version,
   409  						Count:       count,
   410  						Total:       float32(total),
   411  					})
   412  					if err != nil {
   413  						return nil, err
   414  					}
   415  				}
   416  
   417  				return
   418  			}(serverId, currentTime)
   419  			if err != nil {
   420  				return err
   421  			}
   422  
   423  			if len(idStrings) > 0 {
   424  				// 设置为已上传
   425  				// TODO 先不判断是否已经上传,需要改造API进行配合
   426  				/**_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
   427  				if err != nil {
   428  					return err
   429  				}**/
   430  			}
   431  		}
   432  
   433  		// 休息一下,防止短时间内上传数据过多
   434  		if pauseDuration > 0 {
   435  			time.Sleep(pauseDuration)
   436  			uploadTr.Add(-pauseDuration)
   437  		}
   438  	}
   439  
   440  	return nil
   441  }
   442  
   443  // 加载服务ID
   444  func (this *SQLiteTask) loadServerIdMap() error {
   445  	{
   446  		rows, err := this.db.Query(`SELECT DISTINCT "serverId" FROM `+this.statTableName+" WHERE version=?", this.itemConfig.Version)
   447  		if err != nil {
   448  			return err
   449  		}
   450  		defer func() {
   451  			_ = rows.Close()
   452  		}()
   453  
   454  		var serverId int64
   455  		for rows.Next() {
   456  			err = rows.Scan(&serverId)
   457  			if err != nil {
   458  				return err
   459  			}
   460  			this.serverIdMapLocker.Lock()
   461  			this.serverIdMap[serverId] = zero.New()
   462  			this.serverIdMapLocker.Unlock()
   463  		}
   464  	}
   465  
   466  	{
   467  		rows, err := this.db.Query(`SELECT DISTINCT "time" FROM `+this.statTableName+" WHERE version=?", this.itemConfig.Version)
   468  		if err != nil {
   469  			return err
   470  		}
   471  		defer func() {
   472  			_ = rows.Close()
   473  		}()
   474  
   475  		var timeString string
   476  		for rows.Next() {
   477  			err = rows.Scan(&timeString)
   478  			if err != nil {
   479  				return err
   480  			}
   481  			this.serverIdMapLocker.Lock()
   482  			this.timeMap[timeString] = zero.New()
   483  			this.serverIdMapLocker.Unlock()
   484  		}
   485  	}
   486  
   487  	return nil
   488  }
   489  
   490  // 计算数量和综合
   491  func (this *SQLiteTask) sum(serverId int64, time string) (count int64, total float64, err error) {
   492  	rows, err := this.sumStmt.Query(serverId, this.itemConfig.Version, time)
   493  	if err != nil {
   494  		return 0, 0, err
   495  	}
   496  	defer func() {
   497  		_ = rows.Close()
   498  	}()
   499  	if rows.Next() {
   500  		err = rows.Scan(&count, &total)
   501  		if err != nil {
   502  			return 0, 0, err
   503  		}
   504  	}
   505  	return
   506  }