github.com/TeaOSLab/EdgeNode@v1.3.8/internal/metrics/task_kv.go (about) 1 // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package metrics 4 5 import ( 6 "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 7 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 8 "github.com/TeaOSLab/EdgeNode/internal/goman" 9 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 10 "github.com/TeaOSLab/EdgeNode/internal/rpc" 11 "github.com/TeaOSLab/EdgeNode/internal/trackers" 12 "github.com/TeaOSLab/EdgeNode/internal/utils" 13 byteutils "github.com/TeaOSLab/EdgeNode/internal/utils/byte" 14 "github.com/TeaOSLab/EdgeNode/internal/utils/idles" 15 "github.com/TeaOSLab/EdgeNode/internal/utils/kvstore" 16 "github.com/TeaOSLab/EdgeNode/internal/zero" 17 "github.com/cockroachdb/pebble" 18 "github.com/iwind/TeaGo/Tea" 19 "github.com/iwind/TeaGo/types" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 ) 25 26 // TODO sumValues不用每次insertStat的时候都保存 27 28 // KVTask KV存储实现的任务管理 29 type KVTask struct { 30 BaseTask 31 32 itemsTable *kvstore.Table[*Stat] // hash => *Stat 33 valuesTable *kvstore.Table[[]byte] // time_version_serverId_value_hash => []byte(nil) 34 sumTable *kvstore.Table[[]byte] // time_version_serverId => [count][total] 35 36 serverTimeMap map[string]zero.Zero // 有变更的网站 serverId_time => Zero 37 serverIdMapLocker sync.Mutex 38 39 statsTicker *utils.Ticker 40 cleanTicker *time.Ticker 41 uploadTicker *utils.Ticker 42 43 valuesCacheMap map[string]int64 // hash => value 44 } 45 46 func NewKVTask(itemConfig *serverconfigs.MetricItemConfig) *KVTask { 47 return &KVTask{ 48 BaseTask: BaseTask{ 49 itemConfig: itemConfig, 50 statsMap: map[string]*Stat{}, 51 }, 52 53 serverTimeMap: map[string]zero.Zero{}, 54 valuesCacheMap: map[string]int64{}, 55 } 56 } 57 58 func (this *KVTask) Init() error { 59 store, err := kvstore.DefaultStore() 60 if err != nil { 61 return err 62 } 63 64 db, err := store.NewDB("metrics" + types.String(this.itemConfig.Id)) 65 if err != nil { 66 return err 67 } 68 69 { 70 table, tableErr := kvstore.NewTable[*Stat]("items", &ItemEncoder[*Stat]{}) 71 if tableErr != nil { 72 return tableErr 73 } 74 db.AddTable(table) 75 this.itemsTable = table 76 } 77 78 { 79 table, tableErr := kvstore.NewTable[[]byte]("values", kvstore.NewNilValueEncoder()) 80 if tableErr != nil { 81 return tableErr 82 } 83 db.AddTable(table) 84 this.valuesTable = table 85 } 86 87 { 88 table, tableErr := kvstore.NewTable[[]byte]("sum_values", kvstore.NewBytesValueEncoder()) 89 if tableErr != nil { 90 return tableErr 91 } 92 db.AddTable(table) 93 this.sumTable = table 94 } 95 96 // 所有的服务IDs 97 err = this.loadServerIdMap() 98 if err != nil { 99 return err 100 } 101 102 this.isLoaded = true 103 104 return nil 105 } 106 107 func (this *KVTask) InsertStat(stat *Stat) error { 108 if this.isStopped { 109 return nil 110 } 111 if stat == nil { 112 return nil 113 } 114 115 var version = this.itemConfig.Version 116 117 this.serverIdMapLocker.Lock() 118 this.serverTimeMap[types.String(stat.ServerId)+"_"+stat.Time] = zero.New() 119 this.serverIdMapLocker.Unlock() 120 121 if len(stat.Hash) == 0 { 122 stat.Hash = stat.UniqueKey(version, this.itemConfig.Id) 123 } 124 125 var isNew bool 126 var newValue = stat.Value 127 128 // insert or update 129 { 130 var statKey = stat.FullKey(version, this.itemConfig.Id) 131 oldStat, err := this.itemsTable.Get(statKey) 132 var oldValue int64 133 if err != nil { 134 if !kvstore.IsNotFound(err) { 135 return err 136 } 137 isNew = true 138 } else { 139 oldValue = oldStat.Value 140 141 // delete old value from valuesTable 142 err = this.valuesTable.Delete(oldStat.EncodeValueKey(version)) 143 if err != nil { 144 return err 145 } 146 } 147 148 oldValue += stat.Value 149 stat.Value = oldValue 150 err = this.itemsTable.Set(statKey, stat) 151 if err != nil { 152 return err 153 } 154 155 // insert value into valuesTable 156 err = this.valuesTable.Insert(stat.EncodeValueKey(version), nil) 157 if err != nil { 158 return err 159 } 160 } 161 162 // sum 163 { 164 var sumKey = stat.EncodeSumKey(version) 165 sumResult, err := this.sumTable.Get(sumKey) 166 var count uint64 167 var total uint64 168 if err != nil { 169 if !kvstore.IsNotFound(err) { 170 return err 171 } 172 } else { 173 count, total = DecodeSumValue(sumResult) 174 } 175 176 if isNew { 177 count++ 178 } 179 total += uint64(newValue) 180 181 err = this.sumTable.Set(sumKey, EncodeSumValue(count, total)) 182 if err != nil { 183 return err 184 } 185 } 186 187 return nil 188 } 189 190 func (this *KVTask) Upload(pauseDuration time.Duration) error { 191 var uploadTr = trackers.Begin("METRIC:UPLOAD_STATS") 192 defer uploadTr.End() 193 194 if this.isStopped { 195 return nil 196 } 197 198 this.serverIdMapLocker.Lock() 199 200 // 服务IDs 201 var serverTimeMap = this.serverTimeMap 202 this.serverTimeMap = map[string]zero.Zero{} // 清空数据 203 204 this.serverIdMapLocker.Unlock() 205 206 if len(serverTimeMap) == 0 { 207 return nil 208 } 209 210 // 控制缓存map不要太长 211 if len(this.valuesCacheMap) > 4096 { 212 var newMap = map[string]int64{} 213 var countElements int 214 for k, v := range this.valuesCacheMap { 215 newMap[k] = v 216 countElements++ 217 if countElements >= 2048 { 218 break 219 } 220 } 221 this.valuesCacheMap = newMap 222 } 223 224 // 开始上传 225 rpcClient, err := rpc.SharedRPC() 226 if err != nil { 227 return err 228 } 229 230 var totalCount int 231 232 for serverTime := range serverTimeMap { 233 count, uploadErr := func(serverTime string) (int, error) { 234 serverIdString, timeString, found := strings.Cut(serverTime, "_") 235 if !found { 236 return 0, nil 237 } 238 var serverId = types.Int64(serverIdString) 239 if serverId <= 0 { 240 return 0, nil 241 } 242 243 return this.uploadServerStats(rpcClient, serverId, timeString) 244 }(serverTime) 245 if uploadErr != nil { 246 return uploadErr 247 } 248 249 totalCount += count 250 251 // 休息一下,防止短时间内上传数据过多 252 if pauseDuration > 0 && totalCount >= 100 { 253 time.Sleep(pauseDuration) 254 uploadTr.Add(-pauseDuration) 255 } 256 } 257 258 return nil 259 } 260 261 func (this *KVTask) Start() error { 262 // 读取数据 263 this.statsTicker = utils.NewTicker(1 * time.Minute) 264 if Tea.IsTesting() { 265 this.statsTicker = utils.NewTicker(10 * time.Second) 266 } 267 goman.New(func() { 268 for this.statsTicker.Next() { 269 var tr = trackers.Begin("METRIC:DUMP_STATS_TO_LOCAL_DATABASE") 270 271 this.statsLocker.Lock() 272 var statsMap = this.statsMap 273 this.statsMap = map[string]*Stat{} 274 this.statsLocker.Unlock() 275 276 for _, stat := range statsMap { 277 err := this.InsertStat(stat) 278 if err != nil { 279 remotelogs.Error("METRIC", "insert stat failed: "+err.Error()) 280 } 281 } 282 283 tr.End() 284 } 285 }) 286 287 // 清理 288 this.cleanTicker = time.NewTicker(24 * time.Hour) 289 goman.New(func() { 290 idles.RunTicker(this.cleanTicker, func() { 291 var tr = trackers.Begin("METRIC:CLEAN_EXPIRED") 292 err := this.CleanExpired() 293 tr.End() 294 if err != nil { 295 remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error()) 296 } 297 }) 298 }) 299 300 // 上传 301 this.uploadTicker = utils.NewTicker(this.itemConfig.UploadDuration()) 302 goman.New(func() { 303 for this.uploadTicker.Next() { 304 err := this.Upload(1 * time.Second) 305 if err != nil && !rpc.IsConnError(err) { 306 remotelogs.Error("METRIC", "upload stats failed: "+err.Error()) 307 } 308 } 309 }) 310 311 return nil 312 } 313 314 func (this *KVTask) Stop() error { 315 this.isStopped = true 316 317 if this.cleanTicker != nil { 318 this.cleanTicker.Stop() 319 } 320 if this.uploadTicker != nil { 321 this.uploadTicker.Stop() 322 } 323 if this.statsTicker != nil { 324 this.statsTicker.Stop() 325 } 326 327 return nil 328 } 329 330 func (this *KVTask) Delete() error { 331 this.isStopped = true 332 333 return this.itemsTable.DB().Truncate() 334 } 335 336 func (this *KVTask) CleanExpired() error { 337 if this.isStopped { 338 return nil 339 } 340 341 var versionBytes = int32ToBigEndian(this.itemConfig.Version) 342 var expiresTime = this.itemConfig.LocalExpiresTime() 343 344 var rangeEnd = append([]byte(expiresTime+"_"), versionBytes...) 345 rangeEnd = append(rangeEnd, 0xFF, 0xFF) 346 347 { 348 err := this.itemsTable.DeleteRange("", string(rangeEnd)) 349 if err != nil { 350 return err 351 } 352 } 353 354 { 355 err := this.valuesTable.DeleteRange("", string(rangeEnd)) 356 if err != nil { 357 return err 358 } 359 } 360 361 { 362 err := this.sumTable.DeleteRange("", string(rangeEnd)) 363 if err != nil { 364 return err 365 } 366 } 367 368 return nil 369 } 370 371 func (this *KVTask) Flush() error { 372 return this.itemsTable.DB().Store().Flush() 373 } 374 375 func (this *KVTask) TestInspect(t *testing.T) { 376 var db = this.itemsTable.DB() 377 it, err := db.Store().RawDB().NewIter(&pebble.IterOptions{ 378 LowerBound: []byte(db.Namespace()), 379 UpperBound: append([]byte(db.Namespace()), 0xFF), 380 }) 381 if err != nil { 382 t.Fatal(err) 383 } 384 defer func() { 385 _ = it.Close() 386 }() 387 388 for it.First(); it.Valid(); it.Next() { 389 valueBytes, valueErr := it.ValueAndErr() 390 if valueErr != nil { 391 t.Fatal(valueErr) 392 } 393 var key = string(it.Key()[len(db.Namespace())-1:]) 394 t.Log(key, "=>", string(valueBytes)) 395 if strings.HasPrefix(key, "$values$K$") { 396 _, _, _, value, hash, _ := DecodeValueKey(key[len("$values$K$"):]) 397 t.Log(" |", hash, "=>", value) 398 } else if strings.HasPrefix(key, "$sumValues$K$") { 399 count, sum := DecodeSumValue(valueBytes) 400 t.Log(" |", count, sum) 401 } 402 } 403 } 404 405 func (this *KVTask) Truncate() error { 406 var db = this.itemsTable.DB() 407 err := db.Truncate() 408 if err != nil { 409 return err 410 } 411 return db.Store().Flush() 412 } 413 414 func (this *KVTask) uploadServerStats(rpcClient *rpc.RPCClient, serverId int64, currentTime string) (countValues int, uploadErr error) { 415 var pbStats []*pb.UploadingMetricStat 416 var keepKeys []string 417 418 var prefix = string(byteutils.Concat([]byte(currentTime), []byte{'_'}, int32ToBigEndian(this.itemConfig.Version), int64ToBigEndian(serverId))) 419 var newCachedKeys = map[string]int64{} 420 queryErr := this.valuesTable. 421 Query(). 422 Prefix(prefix). 423 Desc(). 424 Limit(20). 425 FindAll(func(tx *kvstore.Tx[[]byte], item kvstore.Item[[]byte]) (goNext bool, err error) { 426 _, _, version, value, hash, decodeErr := DecodeValueKey(item.Key) 427 if decodeErr != nil { 428 return false, decodeErr 429 } 430 if value <= 0 { 431 return true, nil 432 } 433 434 // value not changed for the key 435 if this.valuesCacheMap[hash] == value { 436 keepKeys = append(keepKeys, hash) 437 return true, nil 438 } 439 440 newCachedKeys[hash] = value 441 442 stat, valueErr := this.itemsTable.Get(string(byteutils.Concat([]byte(currentTime), []byte{'_'}, int32ToBigEndian(version), []byte(hash)))) 443 if valueErr != nil { 444 if kvstore.IsNotFound(valueErr) { 445 return true, nil 446 } 447 return false, valueErr 448 } 449 if stat == nil { 450 return true, nil 451 } 452 453 pbStats = append(pbStats, &pb.UploadingMetricStat{ 454 Id: 0, // not used in node 455 Hash: hash, 456 Keys: stat.Keys, 457 Value: float32(value), 458 }) 459 460 return true, nil 461 }) 462 if queryErr != nil { 463 return 0, queryErr 464 } 465 466 // count & total 467 var count, total uint64 468 { 469 sumValue, err := this.sumTable.Get(prefix) 470 if err != nil { 471 if kvstore.IsNotFound(err) { 472 return 0, nil 473 } 474 return 0, err 475 } 476 count, total = DecodeSumValue(sumValue) 477 } 478 479 _, err := rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{ 480 MetricStats: pbStats, 481 Time: currentTime, 482 ServerId: serverId, 483 ItemId: this.itemConfig.Id, 484 Version: this.itemConfig.Version, 485 Count: int64(count), 486 Total: float32(total), 487 KeepKeys: keepKeys, 488 }) 489 if err != nil { 490 return 0, err 491 } 492 493 // put into cache map MUST be after uploading success 494 for k, v := range newCachedKeys { 495 this.valuesCacheMap[k] = v 496 } 497 498 return len(pbStats), nil 499 } 500 501 func (this *KVTask) loadServerIdMap() error { 502 var offsetKey string 503 var currentTime = this.itemConfig.CurrentTime() 504 for { 505 var found bool 506 err := this.sumTable. 507 Query(). 508 Limit(1000). 509 Offset(offsetKey). 510 FindAll(func(tx *kvstore.Tx[[]byte], item kvstore.Item[[]byte]) (goNext bool, err error) { 511 offsetKey = item.Key 512 found = true 513 514 serverId, timeString, version, decodeErr := DecodeSumKey(item.Key) 515 if decodeErr != nil { 516 return false, decodeErr 517 } 518 519 if version != this.itemConfig.Version || timeString != currentTime { 520 return true, nil 521 } 522 523 this.serverIdMapLocker.Lock() 524 this.serverTimeMap[types.String(serverId)+"_"+timeString] = zero.New() 525 this.serverIdMapLocker.Unlock() 526 527 return true, nil 528 }) 529 if err != nil { 530 return err 531 } 532 if !found { 533 break 534 } 535 } 536 537 return nil 538 }