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

     1  // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package caches
     4  
     5  import (
     6  	"database/sql"
     7  	"errors"
     8  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     9  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    10  	"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
    11  	"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
    12  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
    13  	"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
    14  	fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
    15  	"github.com/TeaOSLab/EdgeNode/internal/zero"
    16  	"github.com/iwind/TeaGo/types"
    17  	"os"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  )
    22  
    23  const CountFileDB = 20
    24  
    25  // SQLiteFileList 文件缓存列表管理
    26  type SQLiteFileList struct {
    27  	dir    string
    28  	dbList [CountFileDB]*SQLiteFileListDB
    29  
    30  	onAdd    func(item *Item)
    31  	onRemove func(item *Item)
    32  
    33  	memoryCache *ttlcache.Cache[zero.Zero]
    34  
    35  	// 老数据库地址
    36  	oldDir string
    37  }
    38  
    39  func NewSQLiteFileList(dir string) ListInterface {
    40  	return &SQLiteFileList{
    41  		dir:         dir,
    42  		memoryCache: ttlcache.NewCache[zero.Zero](),
    43  	}
    44  }
    45  
    46  func (this *SQLiteFileList) SetOldDir(oldDir string) {
    47  	this.oldDir = oldDir
    48  }
    49  
    50  func (this *SQLiteFileList) Init() error {
    51  	// 检查目录是否存在
    52  	_, err := os.Stat(this.dir)
    53  	if err != nil {
    54  		err = os.MkdirAll(this.dir, 0777)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'")
    59  	}
    60  
    61  	var dir = this.dir
    62  	if dir == "/" {
    63  		// 防止sqlite提示authority错误
    64  		dir = ""
    65  	}
    66  
    67  	remotelogs.Println("CACHE", "loading database from '"+dir+"' ...")
    68  	var wg = &sync.WaitGroup{}
    69  	var locker = sync.Mutex{}
    70  	var lastErr error
    71  
    72  	for i := 0; i < CountFileDB; i++ {
    73  		wg.Add(1)
    74  		go func(i int) {
    75  			defer wg.Done()
    76  
    77  			var db = NewSQLiteFileListDB()
    78  			dbErr := db.Open(dir + "/db-" + types.String(i) + ".db")
    79  			if dbErr != nil {
    80  				lastErr = dbErr
    81  				return
    82  			}
    83  
    84  			dbErr = db.Init()
    85  			if dbErr != nil {
    86  				lastErr = dbErr
    87  				return
    88  			}
    89  
    90  			locker.Lock()
    91  			this.dbList[i] = db
    92  			locker.Unlock()
    93  		}(i)
    94  	}
    95  	wg.Wait()
    96  
    97  	if lastErr != nil {
    98  		return lastErr
    99  	}
   100  
   101  	// 升级老版本数据库
   102  	goman.New(func() {
   103  		this.upgradeOldDB()
   104  	})
   105  
   106  	return nil
   107  }
   108  
   109  func (this *SQLiteFileList) Reset() error {
   110  	// 不做任何事情
   111  	return nil
   112  }
   113  
   114  func (this *SQLiteFileList) Add(hash string, item *Item) error {
   115  	var db = this.GetDB(hash)
   116  
   117  	if !db.IsReady() {
   118  		return nil
   119  	}
   120  
   121  	err := db.AddSync(hash, item)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(item.ExpiresAt))
   127  
   128  	if this.onAdd != nil {
   129  		this.onAdd(item)
   130  	}
   131  	return nil
   132  }
   133  
   134  func (this *SQLiteFileList) Exist(hash string) (bool, int64, error) {
   135  	var db = this.GetDB(hash)
   136  
   137  	if !db.IsReady() {
   138  		return false, -1, nil
   139  	}
   140  
   141  	// 如果Hash列表里不存在,那么必然不存在
   142  	if !db.hashMap.Exist(hash) {
   143  		return false, -1, nil
   144  	}
   145  
   146  	var item = this.memoryCache.Read(hash)
   147  	if item != nil {
   148  		return true, -1, nil
   149  	}
   150  
   151  	var row = db.existsByHashStmt.QueryRow(hash, time.Now().Unix())
   152  	if row.Err() != nil {
   153  		return false, -1, nil
   154  	}
   155  
   156  	var expiredAt int64
   157  	err := row.Scan(&expiredAt)
   158  	if err != nil {
   159  		if errors.Is(err, sql.ErrNoRows) {
   160  			err = nil
   161  		}
   162  		return false, -1, err
   163  	}
   164  
   165  	if expiredAt <= fasttime.Now().Unix() {
   166  		return false, -1, nil
   167  	}
   168  
   169  	this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt))
   170  	return true, -1, nil
   171  }
   172  
   173  func (this *SQLiteFileList) ExistQuick(hash string) (isReady bool, found bool) {
   174  	var db = this.GetDB(hash)
   175  
   176  	if !db.IsReady() || !db.HashMapIsLoaded() {
   177  		return
   178  	}
   179  
   180  	isReady = true
   181  	found = db.hashMap.Exist(hash)
   182  	return
   183  }
   184  
   185  // CleanPrefix 清理某个前缀的缓存数据
   186  func (this *SQLiteFileList) CleanPrefix(prefix string) error {
   187  	if len(prefix) == 0 {
   188  		return nil
   189  	}
   190  
   191  	defer func() {
   192  		// TODO 需要优化
   193  		this.memoryCache.Clean()
   194  	}()
   195  
   196  	for _, db := range this.dbList {
   197  		err := db.CleanPrefix(prefix)
   198  		if err != nil {
   199  			return err
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  // CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
   206  func (this *SQLiteFileList) CleanMatchKey(key string) error {
   207  	if len(key) == 0 {
   208  		return nil
   209  	}
   210  
   211  	defer func() {
   212  		// TODO 需要优化
   213  		this.memoryCache.Clean()
   214  	}()
   215  
   216  	for _, db := range this.dbList {
   217  		err := db.CleanMatchKey(key)
   218  		if err != nil {
   219  			return err
   220  		}
   221  	}
   222  	return nil
   223  }
   224  
   225  // CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
   226  func (this *SQLiteFileList) CleanMatchPrefix(prefix string) error {
   227  	if len(prefix) == 0 {
   228  		return nil
   229  	}
   230  
   231  	defer func() {
   232  		// TODO 需要优化
   233  		this.memoryCache.Clean()
   234  	}()
   235  
   236  	for _, db := range this.dbList {
   237  		err := db.CleanMatchPrefix(prefix)
   238  		if err != nil {
   239  			return err
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  func (this *SQLiteFileList) Remove(hash string) error {
   246  	_, err := this.remove(hash, false)
   247  	return err
   248  }
   249  
   250  // Purge 清理过期的缓存
   251  // count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
   252  // callback 每次发现过期key的调用
   253  func (this *SQLiteFileList) Purge(count int, callback func(hash string) error) (int, error) {
   254  	count /= CountFileDB
   255  	if count <= 0 {
   256  		count = 100
   257  	}
   258  
   259  	var countFound = 0
   260  	for _, db := range this.dbList {
   261  		hashStrings, err := db.ListExpiredItems(count)
   262  		if err != nil {
   263  			return 0, nil
   264  		}
   265  
   266  		if len(hashStrings) == 0 {
   267  			continue
   268  		}
   269  
   270  		countFound += len(hashStrings)
   271  
   272  		// 不在 rows.Next() 循环中操作是为了避免死锁
   273  		for _, hash := range hashStrings {
   274  			_, err = this.remove(hash, true)
   275  			if err != nil {
   276  				return 0, err
   277  			}
   278  
   279  			err = callback(hash)
   280  			if err != nil {
   281  				return 0, err
   282  			}
   283  		}
   284  
   285  		_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
   286  		if err != nil {
   287  			return 0, err
   288  		}
   289  	}
   290  
   291  	return countFound, nil
   292  }
   293  
   294  func (this *SQLiteFileList) PurgeLFU(count int, callback func(hash string) error) error {
   295  	count /= CountFileDB
   296  	if count <= 0 {
   297  		count = 100
   298  	}
   299  
   300  	for _, db := range this.dbList {
   301  		hashStrings, err := db.ListLFUItems(count)
   302  		if err != nil {
   303  			return err
   304  		}
   305  
   306  		if len(hashStrings) == 0 {
   307  			continue
   308  		}
   309  
   310  		// 不在 rows.Next() 循环中操作是为了避免死锁
   311  		for _, hash := range hashStrings {
   312  			_, err = this.remove(hash, true)
   313  			if err != nil {
   314  				return err
   315  			}
   316  
   317  			err = callback(hash)
   318  			if err != nil {
   319  				return err
   320  			}
   321  		}
   322  
   323  		_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
   324  		if err != nil {
   325  			return err
   326  		}
   327  	}
   328  	return nil
   329  }
   330  
   331  func (this *SQLiteFileList) CleanAll() error {
   332  	defer this.memoryCache.Clean()
   333  
   334  	for _, db := range this.dbList {
   335  		err := db.CleanAll()
   336  		if err != nil {
   337  			return err
   338  		}
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func (this *SQLiteFileList) Stat(check func(hash string) bool) (*Stat, error) {
   345  	var result = &Stat{}
   346  
   347  	for _, db := range this.dbList {
   348  		if !db.IsReady() {
   349  			return &Stat{}, nil
   350  		}
   351  
   352  		// 这里不设置过期时间、不使用 check 函数,目的是让查询更快速一些
   353  		_ = check
   354  
   355  		var row = db.statStmt.QueryRow()
   356  		if row.Err() != nil {
   357  			return nil, row.Err()
   358  		}
   359  		var stat = &Stat{}
   360  		err := row.Scan(&stat.Count, &stat.Size, &stat.ValueSize)
   361  		if err != nil {
   362  			return nil, err
   363  		}
   364  		result.Count += stat.Count
   365  		result.Size += stat.Size
   366  		result.ValueSize += stat.ValueSize
   367  	}
   368  
   369  	return result, nil
   370  }
   371  
   372  // Count 总数量
   373  // 常用的方法,所以避免直接查询数据库
   374  func (this *SQLiteFileList) Count() (int64, error) {
   375  	var total int64
   376  	for _, db := range this.dbList {
   377  		count, err := db.Total()
   378  		if err != nil {
   379  			return 0, err
   380  		}
   381  		total += count
   382  	}
   383  	return total, nil
   384  }
   385  
   386  // IncreaseHit 增加点击量
   387  func (this *SQLiteFileList) IncreaseHit(hash string) error {
   388  	var db = this.GetDB(hash)
   389  
   390  	if !db.IsReady() {
   391  		return nil
   392  	}
   393  
   394  	return db.IncreaseHitAsync(hash)
   395  }
   396  
   397  // OnAdd 添加事件
   398  func (this *SQLiteFileList) OnAdd(f func(item *Item)) {
   399  	this.onAdd = f
   400  }
   401  
   402  // OnRemove 删除事件
   403  func (this *SQLiteFileList) OnRemove(f func(item *Item)) {
   404  	this.onRemove = f
   405  }
   406  
   407  func (this *SQLiteFileList) Close() error {
   408  	this.memoryCache.Destroy()
   409  
   410  	var group = goman.NewTaskGroup()
   411  	for _, db := range this.dbList {
   412  		var dbCopy = db
   413  		group.Run(func() {
   414  			if dbCopy != nil {
   415  				_ = dbCopy.Close()
   416  			}
   417  		})
   418  	}
   419  	group.Wait()
   420  
   421  	return nil
   422  }
   423  
   424  func (this *SQLiteFileList) GetDBIndex(hash string) uint64 {
   425  	return fnv.HashString(hash) % CountFileDB
   426  }
   427  
   428  func (this *SQLiteFileList) GetDB(hash string) *SQLiteFileListDB {
   429  	return this.dbList[fnv.HashString(hash)%CountFileDB]
   430  }
   431  
   432  func (this *SQLiteFileList) HashMapIsLoaded() bool {
   433  	for _, db := range this.dbList {
   434  		if !db.HashMapIsLoaded() {
   435  			return false
   436  		}
   437  	}
   438  	return true
   439  }
   440  
   441  func (this *SQLiteFileList) remove(hash string, isDeleted bool) (notFound bool, err error) {
   442  	var db = this.GetDB(hash)
   443  
   444  	if !db.IsReady() {
   445  		return false, nil
   446  	}
   447  
   448  	// HashMap中不存在,则确定不存在
   449  	if !db.hashMap.Exist(hash) {
   450  		return true, nil
   451  	}
   452  	defer db.hashMap.Delete(hash)
   453  
   454  	// 从缓存中删除
   455  	this.memoryCache.Delete(hash)
   456  
   457  	if !isDeleted {
   458  		err = db.DeleteSync(hash)
   459  		if err != nil {
   460  			return false, db.WrapError(err)
   461  		}
   462  	}
   463  
   464  	if this.onRemove != nil {
   465  		// when remove file item, no any extra information needed
   466  		this.onRemove(nil)
   467  	}
   468  
   469  	return false, nil
   470  }
   471  
   472  // 升级老版本数据库
   473  func (this *SQLiteFileList) upgradeOldDB() {
   474  	if len(this.oldDir) == 0 {
   475  		return
   476  	}
   477  	_ = this.UpgradeV3(this.oldDir, false)
   478  }
   479  
   480  func (this *SQLiteFileList) UpgradeV3(oldDir string, brokenOnError bool) error {
   481  	// index.db
   482  	var indexDBPath = oldDir + "/index.db"
   483  	_, err := os.Stat(indexDBPath)
   484  	if err != nil {
   485  		return nil
   486  	}
   487  	remotelogs.Println("CACHE", "upgrading local database from '"+oldDir+"' ...")
   488  
   489  	defer func() {
   490  		_ = fsutils.Remove(indexDBPath)
   491  		remotelogs.Println("CACHE", "upgrading local database finished")
   492  	}()
   493  
   494  	db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
   495  	if err != nil {
   496  		return err
   497  	}
   498  
   499  	defer func() {
   500  		_ = db.Close()
   501  	}()
   502  
   503  	var isFinished = false
   504  	var offset = 0
   505  	var count = 10000
   506  
   507  	for {
   508  		if isFinished {
   509  			break
   510  		}
   511  
   512  		err = func() error {
   513  			defer func() {
   514  				offset += count
   515  			}()
   516  
   517  			rows, err := db.Query(`SELECT "hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "createdAt", "host", "serverId" FROM "cacheItems_v3" ORDER BY "id" ASC LIMIT ?, ?`, offset, count)
   518  			if err != nil {
   519  				return err
   520  			}
   521  			defer func() {
   522  				_ = rows.Close()
   523  			}()
   524  
   525  			var hash = ""
   526  			var key = ""
   527  			var headerSize int64
   528  			var bodySize int64
   529  			var metaSize int64
   530  			var expiredAt int64
   531  			var staleAt int64
   532  			var createdAt int64
   533  			var host string
   534  			var serverId int64
   535  
   536  			isFinished = true
   537  
   538  			for rows.Next() {
   539  				isFinished = false
   540  
   541  				err = rows.Scan(&hash, &key, &headerSize, &bodySize, &metaSize, &expiredAt, &staleAt, &createdAt, &host, &serverId)
   542  				if err != nil {
   543  					if brokenOnError {
   544  						return err
   545  					}
   546  					return nil
   547  				}
   548  
   549  				err = this.Add(hash, &Item{
   550  					Type:       ItemTypeFile,
   551  					Key:        key,
   552  					ExpiresAt:  expiredAt,
   553  					StaleAt:    staleAt,
   554  					HeaderSize: headerSize,
   555  					BodySize:   bodySize,
   556  					MetaSize:   metaSize,
   557  					Host:       host,
   558  					ServerId:   serverId,
   559  				})
   560  				if err != nil {
   561  					if brokenOnError {
   562  						return err
   563  					}
   564  				}
   565  			}
   566  
   567  			return nil
   568  		}()
   569  		if err != nil {
   570  			return err
   571  		}
   572  
   573  		time.Sleep(1 * time.Second)
   574  	}
   575  
   576  	return nil
   577  }
   578  
   579  func (this *SQLiteFileList) maxExpiresAtForMemoryCache(expiresAt int64) int64 {
   580  	var maxTimestamp = fasttime.Now().Unix() + 3600
   581  	if expiresAt > maxTimestamp {
   582  		return maxTimestamp
   583  	}
   584  	return expiresAt
   585  }