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 }