mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-04 16:00:25 +08:00
文件缓存增加自动限速/提升本地缓存数据库写入和查询速度
This commit is contained in:
@@ -58,6 +58,7 @@ func (this *FileListDB) Open(dbPath string) error {
|
|||||||
writeDB.SetMaxOpenConns(1)
|
writeDB.SetMaxOpenConns(1)
|
||||||
|
|
||||||
// TODO 耗时过长,暂时不整理数据库
|
// TODO 耗时过长,暂时不整理数据库
|
||||||
|
// TODO 需要根据行数来判断是否VACUUM
|
||||||
/**_, err = db.Exec("VACUUM")
|
/**_, err = db.Exec("VACUUM")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -109,7 +110,7 @@ func (this *FileListDB) Init() error {
|
|||||||
this.total = total
|
this.total = total
|
||||||
|
|
||||||
// 常用语句
|
// 常用语句
|
||||||
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -354,7 +355,7 @@ func (this *FileListDB) Close() error {
|
|||||||
func (this *FileListDB) initTables(times int) error {
|
func (this *FileListDB) initTables(times int) error {
|
||||||
{
|
{
|
||||||
// expiredAt - 过期时间,用来判断有无过期
|
// expiredAt - 过期时间,用来判断有无过期
|
||||||
// staleAt - 陈旧最大时间,用来清理缓存
|
// staleAt - 过时缓存最大时间,用来清理缓存
|
||||||
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"hash" varchar(32),
|
"hash" varchar(32),
|
||||||
@@ -370,15 +371,9 @@ func (this *FileListDB) initTables(times int) error {
|
|||||||
"serverId" integer
|
"serverId" integer
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "createdAt"
|
DROP INDEX IF EXISTS "createdAt";
|
||||||
ON "` + this.itemsTableName + `" (
|
DROP INDEX IF EXISTS "expiredAt";
|
||||||
"createdAt" ASC
|
DROP INDEX IF EXISTS "serverId";
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "expiredAt"
|
|
||||||
ON "` + this.itemsTableName + `" (
|
|
||||||
"expiredAt" ASC
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "staleAt"
|
CREATE INDEX IF NOT EXISTS "staleAt"
|
||||||
ON "` + this.itemsTableName + `" (
|
ON "` + this.itemsTableName + `" (
|
||||||
@@ -389,11 +384,6 @@ CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
|||||||
ON "` + this.itemsTableName + `" (
|
ON "` + this.itemsTableName + `" (
|
||||||
"hash" ASC
|
"hash" ASC
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "serverId"
|
|
||||||
ON "` + this.itemsTableName + `" (
|
|
||||||
"serverId" ASC
|
|
||||||
);
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
83
internal/caches/max_open_files.go
Normal file
83
internal/caches/max_open_files.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import (
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minOpenFilesValue int32 = 2
|
||||||
|
maxOpenFilesValue int32 = 65535
|
||||||
|
|
||||||
|
modSlow int32 = 1
|
||||||
|
modFast int32 = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type MaxOpenFiles struct {
|
||||||
|
step int32
|
||||||
|
maxOpenFiles int32
|
||||||
|
ptr *int32
|
||||||
|
ticker *time.Ticker
|
||||||
|
mod int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMaxOpenFiles(step int32) *MaxOpenFiles {
|
||||||
|
if step <= 0 {
|
||||||
|
step = 2
|
||||||
|
}
|
||||||
|
var f = &MaxOpenFiles{
|
||||||
|
step: step,
|
||||||
|
maxOpenFiles: 2,
|
||||||
|
}
|
||||||
|
if teaconst.DiskIsFast {
|
||||||
|
f.maxOpenFiles = 32
|
||||||
|
}
|
||||||
|
f.ptr = &f.maxOpenFiles
|
||||||
|
f.ticker = time.NewTicker(1 * time.Second)
|
||||||
|
goman.New(func() {
|
||||||
|
for range f.ticker.C {
|
||||||
|
var mod = atomic.LoadInt32(&f.mod)
|
||||||
|
switch mod {
|
||||||
|
case modSlow:
|
||||||
|
// we decrease more quickly, with more steps
|
||||||
|
if atomic.AddInt32(f.ptr, -step*2) <= 0 {
|
||||||
|
atomic.StoreInt32(f.ptr, minOpenFilesValue)
|
||||||
|
}
|
||||||
|
case modFast:
|
||||||
|
if atomic.AddInt32(f.ptr, step) >= maxOpenFilesValue {
|
||||||
|
atomic.StoreInt32(f.ptr, maxOpenFilesValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset mod
|
||||||
|
atomic.StoreInt32(&f.mod, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MaxOpenFiles) Fast() {
|
||||||
|
if atomic.LoadInt32(&this.mod) == 0 {
|
||||||
|
this.mod = modFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MaxOpenFiles) Slow() {
|
||||||
|
atomic.StoreInt32(&this.mod, modSlow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MaxOpenFiles) Max() int32 {
|
||||||
|
if atomic.LoadInt32(&this.mod) == modSlow {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var v = atomic.LoadInt32(this.ptr)
|
||||||
|
if v <= minOpenFilesValue {
|
||||||
|
return minOpenFilesValue
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
28
internal/caches/max_open_files_test.go
Normal file
28
internal/caches/max_open_files_test.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewMaxOpenFiles(t *testing.T) {
|
||||||
|
var maxOpenFiles = caches.NewMaxOpenFiles(2)
|
||||||
|
maxOpenFiles.Fast()
|
||||||
|
t.Log(maxOpenFiles.Max())
|
||||||
|
|
||||||
|
maxOpenFiles.Fast()
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
t.Log(maxOpenFiles.Max())
|
||||||
|
|
||||||
|
maxOpenFiles.Slow()
|
||||||
|
t.Log(maxOpenFiles.Max())
|
||||||
|
|
||||||
|
maxOpenFiles.Slow()
|
||||||
|
t.Log(maxOpenFiles.Max())
|
||||||
|
|
||||||
|
maxOpenFiles.Slow()
|
||||||
|
t.Log(maxOpenFiles.Max())
|
||||||
|
}
|
||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -58,21 +57,15 @@ const (
|
|||||||
HotItemSize = 1024 // 热点数据数量
|
HotItemSize = 1024 // 热点数据数量
|
||||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||||
|
FileTmpSuffix = ".tmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||||
var sharedWritingFileKeyLocker = sync.Mutex{}
|
var sharedWritingFileKeyLocker = sync.Mutex{}
|
||||||
|
|
||||||
var maxOpenFiles = 3
|
var maxOpenFiles = NewMaxOpenFiles(2)
|
||||||
|
|
||||||
func init() {
|
const maxOpenFilesSlowCost = 1 * time.Microsecond
|
||||||
if teaconst.DiskIsFast {
|
|
||||||
maxOpenFiles = runtime.NumCPU()
|
|
||||||
}
|
|
||||||
if maxOpenFiles < 3 {
|
|
||||||
maxOpenFiles = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileStorage 文件缓存
|
// FileStorage 文件缓存
|
||||||
// 文件结构:
|
// 文件结构:
|
||||||
@@ -263,7 +256,7 @@ func (this *FileStorage) Init() error {
|
|||||||
var count = stat.Count
|
var count = stat.Count
|
||||||
var size = stat.Size
|
var size = stat.Size
|
||||||
|
|
||||||
cost := time.Since(before).Seconds() * 1000
|
var cost = time.Since(before).Seconds() * 1000
|
||||||
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
||||||
if size > 1*sizes.G {
|
if size > 1*sizes.G {
|
||||||
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
|
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
|
||||||
@@ -435,7 +428,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isFlushing && len(sharedWritingFileKeyMap) >= maxOpenFiles {
|
if len(sharedWritingFileKeyMap) >= int(maxOpenFiles.Max()) {
|
||||||
sharedWritingFileKeyLocker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
return nil, ErrTooManyOpenFiles
|
return nil, ErrTooManyOpenFiles
|
||||||
}
|
}
|
||||||
@@ -464,6 +457,8 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hash = stringutil.Md5(key)
|
var hash = stringutil.Md5(key)
|
||||||
|
|
||||||
|
// TODO 可以只stat一次
|
||||||
var dir = this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
var dir = this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
||||||
_, err = os.Stat(dir)
|
_, err = os.Stat(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -491,7 +486,15 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
// 防止并发连续写入
|
// 防止并发连续写入
|
||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
var tmpPath = cachePath + ".tmp"
|
var tmpPath = cachePath
|
||||||
|
var existsFile = false
|
||||||
|
if stat != nil {
|
||||||
|
existsFile = true
|
||||||
|
|
||||||
|
// 如果已经存在,则增加一个.tmp后缀,防止读写冲突
|
||||||
|
tmpPath += FileTmpSuffix
|
||||||
|
}
|
||||||
|
|
||||||
if isPartial {
|
if isPartial {
|
||||||
tmpPath = cachePathName + ".cache"
|
tmpPath = cachePathName + ".cache"
|
||||||
}
|
}
|
||||||
@@ -523,13 +526,19 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
var flags = os.O_CREATE | os.O_WRONLY
|
var flags = os.O_CREATE | os.O_WRONLY
|
||||||
if isNewCreated {
|
if isNewCreated && existsFile {
|
||||||
flags |= os.O_TRUNC
|
flags |= os.O_TRUNC
|
||||||
}
|
}
|
||||||
|
var before = time.Now()
|
||||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if time.Since(before) >= maxOpenFilesSlowCost {
|
||||||
|
maxOpenFiles.Slow()
|
||||||
|
} else {
|
||||||
|
maxOpenFiles.Fast()
|
||||||
|
}
|
||||||
|
|
||||||
var removeOnFailure = true
|
var removeOnFailure = true
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ func (this *FileWriter) Close() error {
|
|||||||
err = this.rawWriter.Close()
|
err = this.rawWriter.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
} else {
|
} else if strings.HasSuffix(path, FileTmpSuffix) {
|
||||||
err = os.Rename(path, strings.Replace(path, ".tmp", "", 1))
|
err = os.Rename(path, strings.Replace(path, FileTmpSuffix, "", 1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Header().Set("X-Cache", "BYPASS, open failed")
|
this.Header().Set("X-Cache", "BYPASS, too many requests")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.cacheWriter = cacheWriter
|
this.cacheWriter = cacheWriter
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ func (this *DB) EnableStat(b bool) {
|
|||||||
this.enableStat = b
|
this.enableStat = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *DB) Begin() (*sql.Tx, error) {
|
||||||
|
return this.rawDB.Begin()
|
||||||
|
}
|
||||||
|
|
||||||
func (this *DB) Prepare(query string) (*Stmt, error) {
|
func (this *DB) Prepare(query string) (*Stmt, error) {
|
||||||
stmt, err := this.rawDB.Prepare(query)
|
stmt, err := this.rawDB.Prepare(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user