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  }