github.com/TeaOSLab/EdgeNode@v1.3.8/internal/caches/storage_memory.go (about)

     1  package caches
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
     6  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     7  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
     8  	"github.com/TeaOSLab/EdgeNode/internal/trackers"
     9  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    10  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
    11  	fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
    12  	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
    13  	setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
    14  	"github.com/TeaOSLab/EdgeNode/internal/zero"
    15  	"github.com/cespare/xxhash/v2"
    16  	"github.com/iwind/TeaGo/types"
    17  	"math"
    18  	"runtime"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  	"sync/atomic"
    23  	"time"
    24  )
    25  
    26  type MemoryItem struct {
    27  	ExpiresAt   int64
    28  	HeaderValue []byte
    29  	BodyValue   []byte
    30  	Status      int
    31  	IsDone      bool
    32  	ModifiedAt  int64
    33  
    34  	IsPrepared  bool
    35  	WriteOffset int64
    36  
    37  	isReferring bool // if it is referring by other objects
    38  }
    39  
    40  func (this *MemoryItem) IsExpired() bool {
    41  	return this.ExpiresAt < fasttime.Now().Unix()
    42  }
    43  
    44  type MemoryStorage struct {
    45  	parentStorage StorageInterface
    46  
    47  	policy *serverconfigs.HTTPCachePolicy
    48  	list   ListInterface
    49  	locker *sync.RWMutex
    50  
    51  	valuesMap map[uint64]*MemoryItem // hash => item
    52  
    53  	dirtyChan      chan string // hash chan
    54  	dirtyQueueSize int
    55  
    56  	purgeTicker *utils.Ticker
    57  
    58  	usedSize       int64
    59  	totalDirtySize int64
    60  
    61  	writingKeyMap map[string]zero.Zero // key => bool
    62  
    63  	ignoreKeys *setutils.FixedSet
    64  }
    65  
    66  func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage StorageInterface) *MemoryStorage {
    67  	var dirtyChan chan string
    68  	var queueSize = policy.MemoryAutoFlushQueueSize
    69  
    70  	if parentStorage != nil {
    71  		if queueSize <= 0 {
    72  			queueSize = memutils.SystemMemoryGB() * 100_000
    73  		}
    74  
    75  		dirtyChan = make(chan string, queueSize)
    76  	}
    77  	return &MemoryStorage{
    78  		parentStorage:  parentStorage,
    79  		policy:         policy,
    80  		list:           NewMemoryList(),
    81  		locker:         &sync.RWMutex{},
    82  		valuesMap:      map[uint64]*MemoryItem{},
    83  		dirtyChan:      dirtyChan,
    84  		dirtyQueueSize: queueSize,
    85  		writingKeyMap:  map[string]zero.Zero{},
    86  		ignoreKeys:     setutils.NewFixedSet(32768),
    87  	}
    88  }
    89  
    90  // Init 初始化
    91  func (this *MemoryStorage) Init() error {
    92  	_ = this.list.Init()
    93  
    94  	this.list.OnAdd(func(item *Item) {
    95  		atomic.AddInt64(&this.usedSize, item.TotalSize())
    96  	})
    97  	this.list.OnRemove(func(item *Item) {
    98  		atomic.AddInt64(&this.usedSize, -item.TotalSize())
    99  	})
   100  
   101  	this.initPurgeTicker()
   102  
   103  	// 启动定时Flush memory to disk任务
   104  	if this.parentStorage != nil {
   105  		var threads = 2
   106  		var numCPU = runtime.NumCPU()
   107  		if fsutils.DiskIsExtremelyFast() {
   108  			if numCPU >= 8 {
   109  				threads = 8
   110  			} else {
   111  				threads = 4
   112  			}
   113  		} else if fsutils.DiskIsFast() {
   114  			if numCPU >= 4 {
   115  				threads = 4
   116  			}
   117  		}
   118  		for i := 0; i < threads; i++ {
   119  			goman.New(func() {
   120  				this.startFlush()
   121  			})
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // OpenReader 读取缓存
   129  func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
   130  	var hash = this.hash(key)
   131  
   132  	// check if exists in list
   133  	exists, _, _ := this.list.Exist(types.String(hash))
   134  	if !exists {
   135  		return nil, ErrNotFound
   136  	}
   137  
   138  	// read from valuesMap
   139  	this.locker.RLock()
   140  	var item = this.valuesMap[hash]
   141  
   142  	if item != nil {
   143  		item.isReferring = true
   144  	}
   145  
   146  	if item == nil || !item.IsDone {
   147  		this.locker.RUnlock()
   148  		return nil, ErrNotFound
   149  	}
   150  
   151  	if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
   152  		var reader = NewMemoryReader(item)
   153  		err := reader.Init()
   154  		if err != nil {
   155  			this.locker.RUnlock()
   156  			return nil, err
   157  		}
   158  		this.locker.RUnlock()
   159  
   160  		return reader, nil
   161  	}
   162  	this.locker.RUnlock()
   163  
   164  	_ = this.Delete(key)
   165  
   166  	return nil, ErrNotFound
   167  }
   168  
   169  // OpenWriter 打开缓存写入器等待写入
   170  func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
   171  	if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
   172  		return nil, ErrEntityTooLarge
   173  	}
   174  
   175  	// TODO 内存缓存暂时不支持分块内容存储
   176  	if isPartial {
   177  		return nil, fmt.Errorf("%w (004)", ErrFileIsWriting)
   178  	}
   179  	return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
   180  }
   181  
   182  // OpenFlushWriter 打开从其他媒介直接刷入的写入器
   183  func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
   184  	return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, true)
   185  }
   186  
   187  func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isDirty bool) (Writer, error) {
   188  	// 待写入队列是否已满
   189  	if isDirty &&
   190  		this.parentStorage != nil &&
   191  		this.dirtyQueueSize > 0 &&
   192  		len(this.dirtyChan) >= this.dirtyQueueSize-64 /** delta **/ { // 缓存时间过长
   193  		return nil, ErrWritingQueueFull
   194  	}
   195  
   196  	this.locker.Lock()
   197  	defer this.locker.Unlock()
   198  
   199  	// 是否正在写入
   200  	var isWriting = false
   201  	_, ok := this.writingKeyMap[key]
   202  	if ok {
   203  		return nil, fmt.Errorf("%w (005)", ErrFileIsWriting)
   204  	}
   205  	this.writingKeyMap[key] = zero.New()
   206  	defer func() {
   207  		if !isWriting {
   208  			delete(this.writingKeyMap, key)
   209  		}
   210  	}()
   211  
   212  	// 检查是否过期
   213  	var hash = this.hash(key)
   214  	item, ok := this.valuesMap[hash]
   215  	if ok && !item.IsExpired() {
   216  		var hashString = types.String(hash)
   217  		exists, _, _ := this.list.Exist(hashString)
   218  		if !exists {
   219  			// remove from values map
   220  			delete(this.valuesMap, hash)
   221  			_ = this.list.Remove(hashString)
   222  			item = nil
   223  		} else {
   224  			return nil, fmt.Errorf("%w (006)", ErrFileIsWriting)
   225  		}
   226  	}
   227  
   228  	// 检查是否超出容量最大值
   229  	var capacityBytes = this.memoryCapacityBytes()
   230  	if bodySize < 0 {
   231  		bodySize = 0
   232  	}
   233  	if capacityBytes > 0 && capacityBytes <= atomic.LoadInt64(&this.usedSize)+bodySize {
   234  		return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.usedSize, 10) + " bytes")
   235  	}
   236  
   237  	// 先删除
   238  	err := this.deleteWithoutLocker(key)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	isWriting = true
   244  	return NewMemoryWriter(this, key, expiresAt, status, isDirty, bodySize, maxSize, func(valueItem *MemoryItem) {
   245  		this.locker.Lock()
   246  		delete(this.writingKeyMap, key)
   247  		this.locker.Unlock()
   248  	}), nil
   249  }
   250  
   251  // Delete 删除某个键值对应的缓存
   252  func (this *MemoryStorage) Delete(key string) error {
   253  	var hash = this.hash(key)
   254  	this.locker.Lock()
   255  	delete(this.valuesMap, hash)
   256  	_ = this.list.Remove(types.String(hash))
   257  	this.locker.Unlock()
   258  	return nil
   259  }
   260  
   261  // Stat 统计缓存
   262  func (this *MemoryStorage) Stat() (*Stat, error) {
   263  	this.locker.RLock()
   264  	defer this.locker.RUnlock()
   265  
   266  	return this.list.Stat(func(hash string) bool {
   267  		return true
   268  	})
   269  }
   270  
   271  // CleanAll 清除所有缓存
   272  func (this *MemoryStorage) CleanAll() error {
   273  	this.locker.Lock()
   274  	this.valuesMap = map[uint64]*MemoryItem{}
   275  	_ = this.list.Reset()
   276  	atomic.StoreInt64(&this.usedSize, 0)
   277  	this.locker.Unlock()
   278  	return nil
   279  }
   280  
   281  // Purge 批量删除缓存
   282  func (this *MemoryStorage) Purge(keys []string, urlType string) error {
   283  	// 目录
   284  	if urlType == "dir" {
   285  		for _, key := range keys {
   286  			// 检查是否有通配符 http(s)://*.example.com
   287  			var schemeIndex = strings.Index(key, "://")
   288  			if schemeIndex > 0 {
   289  				var keyRight = key[schemeIndex+3:]
   290  				if strings.HasPrefix(keyRight, "*.") {
   291  					err := this.list.CleanMatchPrefix(key)
   292  					if err != nil {
   293  						return err
   294  					}
   295  					continue
   296  				}
   297  			}
   298  
   299  			err := this.list.CleanPrefix(key)
   300  			if err != nil {
   301  				return err
   302  			}
   303  		}
   304  		return nil
   305  	}
   306  
   307  	// URL
   308  	for _, key := range keys {
   309  		// 检查是否有通配符 http(s)://*.example.com
   310  		var schemeIndex = strings.Index(key, "://")
   311  		if schemeIndex > 0 {
   312  			var keyRight = key[schemeIndex+3:]
   313  			if strings.HasPrefix(keyRight, "*.") {
   314  				err := this.list.CleanMatchKey(key)
   315  				if err != nil {
   316  					return err
   317  				}
   318  				continue
   319  			}
   320  		}
   321  
   322  		err := this.Delete(key)
   323  		if err != nil {
   324  			return err
   325  		}
   326  	}
   327  	return nil
   328  }
   329  
   330  // Stop 停止缓存策略
   331  func (this *MemoryStorage) Stop() {
   332  	this.locker.Lock()
   333  
   334  	this.valuesMap = map[uint64]*MemoryItem{}
   335  	this.writingKeyMap = map[string]zero.Zero{}
   336  	_ = this.list.Reset()
   337  	if this.purgeTicker != nil {
   338  		this.purgeTicker.Stop()
   339  	}
   340  
   341  	if this.dirtyChan != nil {
   342  		close(this.dirtyChan)
   343  	}
   344  
   345  	this.usedSize = 0
   346  	this.totalDirtySize = 0
   347  
   348  	_ = this.list.Close()
   349  
   350  	this.locker.Unlock()
   351  
   352  	this.ignoreKeys.Reset()
   353  
   354  	// 回收内存
   355  	runtime.GC()
   356  
   357  	remotelogs.Println("CACHE", "close memory storage '"+strconv.FormatInt(this.policy.Id, 10)+"'")
   358  }
   359  
   360  // Policy 获取当前存储的Policy
   361  func (this *MemoryStorage) Policy() *serverconfigs.HTTPCachePolicy {
   362  	return this.policy
   363  }
   364  
   365  // UpdatePolicy 修改策略
   366  func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) {
   367  	var oldPolicy = this.policy
   368  	this.policy = newPolicy
   369  
   370  	if oldPolicy.MemoryAutoPurgeInterval != newPolicy.MemoryAutoPurgeInterval {
   371  		this.initPurgeTicker()
   372  	}
   373  
   374  	// 如果是空的,则清空
   375  	if newPolicy.CapacityBytes() == 0 {
   376  		_ = this.CleanAll()
   377  	}
   378  
   379  	// reset ignored keys
   380  	this.ignoreKeys.Reset()
   381  }
   382  
   383  // CanUpdatePolicy 检查策略是否可以更新
   384  func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
   385  	return newPolicy != nil && newPolicy.Type == serverconfigs.CachePolicyStorageMemory
   386  }
   387  
   388  // AddToList 将缓存添加到列表
   389  func (this *MemoryStorage) AddToList(item *Item) {
   390  	// skip added item
   391  	if item.MetaSize > 0 {
   392  		return
   393  	}
   394  
   395  	item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
   396  	var hash = types.String(this.hash(item.Key))
   397  
   398  	if len(item.Host) == 0 {
   399  		item.Host = ParseHost(item.Key)
   400  	}
   401  
   402  	_ = this.list.Add(hash, item)
   403  }
   404  
   405  // TotalDiskSize 消耗的磁盘尺寸
   406  func (this *MemoryStorage) TotalDiskSize() int64 {
   407  	return 0
   408  }
   409  
   410  // TotalMemorySize 内存尺寸
   411  func (this *MemoryStorage) TotalMemorySize() int64 {
   412  	return atomic.LoadInt64(&this.usedSize)
   413  }
   414  
   415  // IgnoreKey 忽略某个Key,即不缓存某个Key
   416  func (this *MemoryStorage) IgnoreKey(key string, maxSize int64) {
   417  	this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
   418  }
   419  
   420  // CanSendfile 是否支持Sendfile
   421  func (this *MemoryStorage) CanSendfile() bool {
   422  	return false
   423  }
   424  
   425  // HasFreeSpaceForHotItems 是否有足够的空间提供给热门内容
   426  func (this *MemoryStorage) HasFreeSpaceForHotItems() bool {
   427  	return atomic.LoadInt64(&this.usedSize) < this.memoryCapacityBytes()*3/4
   428  }
   429  
   430  // 计算Key Hash
   431  func (this *MemoryStorage) hash(key string) uint64 {
   432  	return xxhash.Sum64String(key)
   433  }
   434  
   435  // 清理任务
   436  func (this *MemoryStorage) purgeLoop() {
   437  	// 清理过期
   438  	var purgeCount = this.policy.MemoryAutoPurgeCount
   439  	if purgeCount <= 0 {
   440  		purgeCount = 2000
   441  	}
   442  	_, _ = this.list.Purge(purgeCount, func(hash string) error {
   443  		uintHash, err := strconv.ParseUint(hash, 10, 64)
   444  		if err == nil {
   445  			this.locker.Lock()
   446  			delete(this.valuesMap, uintHash)
   447  			this.locker.Unlock()
   448  		}
   449  		return nil
   450  	})
   451  
   452  	// LFU
   453  	// 计算是否应该开启LFU清理
   454  	var capacityBytes = this.policy.CapacityBytes()
   455  	var startLFU = false
   456  
   457  	var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
   458  	var lfuFreePercent = this.policy.MemoryLFUFreePercent
   459  	if lfuFreePercent <= 0 {
   460  		lfuFreePercent = 5
   461  	}
   462  	if capacityBytes > 0 {
   463  		if lfuFreePercent < 100 {
   464  			if usedPercent >= 100-lfuFreePercent {
   465  				startLFU = true
   466  			}
   467  		}
   468  	}
   469  
   470  	if startLFU {
   471  		var total, _ = this.list.Count()
   472  		if total > 0 {
   473  			var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
   474  			if count > 0 {
   475  				// 限制单次清理的条数,防止占用太多系统资源
   476  				if count > 2000 {
   477  					count = 2000
   478  				}
   479  
   480  				// 这里不提示LFU,因为此事件将会非常频繁
   481  
   482  				err := this.list.PurgeLFU(count, func(hash string) error {
   483  					uintHash, err := strconv.ParseUint(hash, 10, 64)
   484  					if err == nil {
   485  						this.locker.Lock()
   486  						delete(this.valuesMap, uintHash)
   487  						this.locker.Unlock()
   488  					}
   489  					return nil
   490  				})
   491  				if err != nil {
   492  					remotelogs.Warn("CACHE", "purge memory storage in LFU failed: "+err.Error())
   493  				}
   494  			}
   495  		}
   496  	}
   497  }
   498  
   499  // 开始Flush任务
   500  func (this *MemoryStorage) startFlush() {
   501  	var statCount = 0
   502  
   503  	for key := range this.dirtyChan {
   504  		statCount++
   505  
   506  		if statCount == 100 {
   507  			statCount = 0
   508  		}
   509  
   510  		this.flushItem(key)
   511  
   512  		if fsutils.IsInExtremelyHighLoad {
   513  			time.Sleep(1 * time.Second)
   514  		}
   515  	}
   516  }
   517  
   518  // 单次Flush任务
   519  func (this *MemoryStorage) flushItem(fullKey string) {
   520  	sizeString, key, found := strings.Cut(fullKey, "@")
   521  	if !found {
   522  		return
   523  	}
   524  	defer func() {
   525  		atomic.AddInt64(&this.totalDirtySize, -types.Int64(sizeString))
   526  	}()
   527  
   528  	if this.parentStorage == nil {
   529  		return
   530  	}
   531  	var hash = this.hash(key)
   532  
   533  	this.locker.RLock()
   534  	item, ok := this.valuesMap[hash]
   535  	this.locker.RUnlock()
   536  
   537  	// 从内存中移除,并确保无论如何都会执行
   538  	defer func() {
   539  		_ = this.Delete(key)
   540  	}()
   541  
   542  	if !ok {
   543  		return
   544  	}
   545  
   546  	if !item.IsDone {
   547  		remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
   548  		return
   549  	}
   550  	if item.IsExpired() {
   551  		return
   552  	}
   553  
   554  	// 检查是否在列表中,防止未加入列表时就开始flush
   555  	isInList, _, err := this.list.Exist(types.String(hash))
   556  	if err != nil {
   557  		remotelogs.Error("CACHE", "flush items failed: "+err.Error())
   558  		return
   559  	}
   560  	if !isInList {
   561  		for i := 0; i < 1000; i++ {
   562  			isInList, _, err = this.list.Exist(types.String(hash))
   563  			if err != nil {
   564  				remotelogs.Error("CACHE", "flush items failed: "+err.Error())
   565  				time.Sleep(1 * time.Second)
   566  				continue
   567  			}
   568  			if isInList {
   569  				break
   570  			}
   571  			time.Sleep(1 * time.Millisecond)
   572  		}
   573  		if !isInList {
   574  			// discard
   575  			return
   576  		}
   577  	}
   578  
   579  	writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
   580  	if err != nil {
   581  		if !CanIgnoreErr(err) {
   582  			remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
   583  		}
   584  		return
   585  	}
   586  
   587  	_, err = writer.WriteHeader(item.HeaderValue)
   588  	if err != nil {
   589  		_ = writer.Discard()
   590  		remotelogs.Error("CACHE", "flush items failed: write header failed: "+err.Error())
   591  		return
   592  	}
   593  
   594  	_, err = writer.Write(item.BodyValue)
   595  	if err != nil {
   596  		_ = writer.Discard()
   597  		remotelogs.Error("CACHE", "flush items failed: writer body failed: "+err.Error())
   598  		return
   599  	}
   600  
   601  	err = writer.Close()
   602  	if err != nil {
   603  		_ = writer.Discard()
   604  		remotelogs.Error("CACHE", "flush items failed: close writer failed: "+err.Error())
   605  		return
   606  	}
   607  
   608  	this.parentStorage.AddToList(&Item{
   609  		Type:       writer.ItemType(),
   610  		Key:        key,
   611  		Host:       ParseHost(key),
   612  		ExpiresAt:  item.ExpiresAt,
   613  		HeaderSize: writer.HeaderSize(),
   614  		BodySize:   writer.BodySize(),
   615  	})
   616  }
   617  
   618  func (this *MemoryStorage) memoryCapacityBytes() int64 {
   619  	var maxSystemBytes = SharedManager.MaxSystemMemoryBytesPerStorage()
   620  	if this.policy == nil {
   621  		return maxSystemBytes
   622  	}
   623  
   624  	if SharedManager.MaxMemoryCapacity != nil {
   625  		var capacityBytes = SharedManager.MaxMemoryCapacity.Bytes()
   626  		if capacityBytes > 0 {
   627  			if capacityBytes > maxSystemBytes {
   628  				return maxSystemBytes
   629  			}
   630  
   631  			return capacityBytes
   632  		}
   633  	}
   634  
   635  	var capacity = this.policy.Capacity // copy
   636  	if capacity != nil {
   637  		var capacityBytes = capacity.Bytes()
   638  		if capacityBytes > 0 {
   639  			if capacityBytes > maxSystemBytes {
   640  				return maxSystemBytes
   641  			}
   642  			return capacityBytes
   643  		}
   644  	}
   645  
   646  	return maxSystemBytes
   647  }
   648  
   649  func (this *MemoryStorage) deleteWithoutLocker(key string) error {
   650  	hash := this.hash(key)
   651  	delete(this.valuesMap, hash)
   652  	_ = this.list.Remove(types.String(hash))
   653  	return nil
   654  }
   655  
   656  func (this *MemoryStorage) initPurgeTicker() {
   657  	var autoPurgeInterval = this.policy.MemoryAutoPurgeInterval
   658  	if autoPurgeInterval <= 0 {
   659  		autoPurgeInterval = 5
   660  	}
   661  
   662  	// 启动定时清理任务
   663  
   664  	if this.purgeTicker != nil {
   665  		this.purgeTicker.Stop()
   666  	}
   667  
   668  	this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
   669  	goman.New(func() {
   670  		for this.purgeTicker.Next() {
   671  			var tr = trackers.Begin("MEMORY_CACHE_STORAGE_PURGE_LOOP")
   672  			this.purgeLoop()
   673  			tr.End()
   674  		}
   675  	})
   676  }