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 }