mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-08 19:40:25 +08:00
缓存策略移除“容纳Key数量”选项;缓存占用空间统计改成统计缓存目录所在文件系统
This commit is contained in:
@@ -52,9 +52,8 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
MaxKeys: 1,
|
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
@@ -95,9 +94,9 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
|
|||||||
func TestManager_ChangePolicy_File(t *testing.T) {
|
func TestManager_ChangePolicy_File(t *testing.T) {
|
||||||
var policies = []*serverconfigs.HTTPCachePolicy{
|
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||||
{
|
{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/data/cache-index/p1",
|
"dir": Tea.Root + "/data/cache-index/p1",
|
||||||
},
|
},
|
||||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||||
@@ -106,9 +105,9 @@ func TestManager_ChangePolicy_File(t *testing.T) {
|
|||||||
SharedManager.UpdatePolicies(policies)
|
SharedManager.UpdatePolicies(policies)
|
||||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||||
{
|
{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/data/cache-index/p1",
|
"dir": Tea.Root + "/data/cache-index/p1",
|
||||||
},
|
},
|
||||||
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
@@ -21,9 +22,6 @@ import (
|
|||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"golang.org/x/text/message"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -32,7 +30,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -53,12 +50,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||||
HotItemSize = 1024 // 热点数据数量
|
HotItemSize = 1024 // 热点数据数量
|
||||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||||
FileTmpSuffix = ".tmp"
|
FileTmpSuffix = ".tmp"
|
||||||
MinDiskSpace = 5 << 30 // 当前磁盘最小剩余空间
|
MinDiskSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||||
)
|
)
|
||||||
|
|
||||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||||
@@ -77,7 +74,6 @@ type FileStorage struct {
|
|||||||
policy *serverconfigs.HTTPCachePolicy
|
policy *serverconfigs.HTTPCachePolicy
|
||||||
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
||||||
memoryStorage *MemoryStorage // 一级缓存
|
memoryStorage *MemoryStorage // 一级缓存
|
||||||
totalSize int64
|
|
||||||
|
|
||||||
list ListInterface
|
list ListInterface
|
||||||
locker sync.RWMutex
|
locker sync.RWMutex
|
||||||
@@ -92,7 +88,6 @@ type FileStorage struct {
|
|||||||
|
|
||||||
openFileCache *OpenFileCache
|
openFileCache *OpenFileCache
|
||||||
|
|
||||||
mainDir string
|
|
||||||
mainDiskIsFull bool
|
mainDiskIsFull bool
|
||||||
|
|
||||||
subDirs []*FileDir
|
subDirs []*FileDir
|
||||||
@@ -255,19 +250,6 @@ func (this *FileStorage) Init() error {
|
|||||||
}
|
}
|
||||||
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||||
this.list = list
|
this.list = list
|
||||||
stat, err := list.Stat(func(hash string) bool {
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
this.totalSize = stat.Size
|
|
||||||
this.list.OnAdd(func(item *Item) {
|
|
||||||
atomic.AddInt64(&this.totalSize, item.TotalSize())
|
|
||||||
})
|
|
||||||
this.list.OnRemove(func(item *Item) {
|
|
||||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查目录是否存在
|
// 检查目录是否存在
|
||||||
_, err = os.Stat(dir)
|
_, err = os.Stat(dir)
|
||||||
@@ -284,19 +266,17 @@ func (this *FileStorage) Init() error {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// 统计
|
// 统计
|
||||||
var count = stat.Count
|
var totalSize = this.TotalDiskSize()
|
||||||
var size = stat.Size
|
|
||||||
|
|
||||||
var cost = time.Since(before).Seconds() * 1000
|
var cost = time.Since(before).Seconds() * 1000
|
||||||
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
var sizeMB = types.String(totalSize) + " Bytes"
|
||||||
if size > 1*sizes.G {
|
if totalSize > 1*sizes.G {
|
||||||
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
|
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G))
|
||||||
} else if size > 1*sizes.M {
|
} else if totalSize > 1*sizes.M {
|
||||||
sizeMB = fmt.Sprintf("%.3f M", float64(size)/float64(sizes.M))
|
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M))
|
||||||
} else if size > 1*sizes.K {
|
} else if totalSize > 1*sizes.K {
|
||||||
sizeMB = fmt.Sprintf("%.3f K", float64(size)/float64(sizes.K))
|
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K))
|
||||||
}
|
}
|
||||||
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
|
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 初始化list
|
// 初始化list
|
||||||
@@ -480,17 +460,10 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 检查是否超出最大值
|
// 检查是否超出容量
|
||||||
count, err := this.list.Count()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys {
|
|
||||||
return nil, NewCapacityError("write file cache failed: too many keys in cache storage")
|
|
||||||
}
|
|
||||||
var capacityBytes = this.diskCapacityBytes()
|
var capacityBytes = this.diskCapacityBytes()
|
||||||
if capacityBytes > 0 && capacityBytes <= this.totalSize {
|
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize() {
|
||||||
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10))
|
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + types.String(this.TotalDiskSize()) + " bytes, capacity: " + types.String(capacityBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = stringutil.Md5(key)
|
var hash = stringutil.Md5(key)
|
||||||
@@ -924,7 +897,11 @@ func (this *FileStorage) Stop() {
|
|||||||
|
|
||||||
// TotalDiskSize 消耗的磁盘尺寸
|
// TotalDiskSize 消耗的磁盘尺寸
|
||||||
func (this *FileStorage) TotalDiskSize() int64 {
|
func (this *FileStorage) TotalDiskSize() int64 {
|
||||||
return atomic.LoadInt64(&this.totalSize)
|
stat, err := fsutils.StatCache(this.options.Dir)
|
||||||
|
if err == nil {
|
||||||
|
return int64(stat.UsedSize())
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalMemorySize 内存尺寸
|
// TotalMemorySize 内存尺寸
|
||||||
@@ -1364,7 +1341,6 @@ func (this *FileStorage) createMemoryStorage() error {
|
|||||||
Name: this.policy.Name,
|
Name: this.policy.Name,
|
||||||
Description: this.policy.Description,
|
Description: this.policy.Description,
|
||||||
Capacity: this.options.MemoryPolicy.Capacity,
|
Capacity: this.options.MemoryPolicy.Capacity,
|
||||||
MaxKeys: this.policy.MaxKeys,
|
|
||||||
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
||||||
Type: serverconfigs.CachePolicyStorageMemory,
|
Type: serverconfigs.CachePolicyStorageMemory,
|
||||||
Options: this.policy.Options,
|
Options: this.policy.Options,
|
||||||
@@ -1440,20 +1416,16 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
|||||||
// 检查磁盘剩余空间
|
// 检查磁盘剩余空间
|
||||||
func (this *FileStorage) checkDiskSpace() {
|
func (this *FileStorage) checkDiskSpace() {
|
||||||
if this.options != nil && len(this.options.Dir) > 0 {
|
if this.options != nil && len(this.options.Dir) > 0 {
|
||||||
var stat unix.Statfs_t
|
stat, err := fsutils.Stat(this.options.Dir)
|
||||||
err := unix.Statfs(this.options.Dir, &stat)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
this.mainDiskIsFull = stat.AvailableSize() < MinDiskSpace
|
||||||
this.mainDiskIsFull = availableBytes < MinDiskSpace
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var subDirs = this.subDirs // copy slice
|
var subDirs = this.subDirs // copy slice
|
||||||
for _, subDir := range subDirs {
|
for _, subDir := range subDirs {
|
||||||
var stat unix.Statfs_t
|
stat, err := fsutils.Stat(subDir.Path)
|
||||||
err := unix.Statfs(subDir.Path, &stat)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
subDir.IsFull = stat.AvailableSize() < MinDiskSpace
|
||||||
subDir.IsFull = availableBytes < MinDiskSpace
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,13 +200,6 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否超出最大值
|
// 检查是否超出最大值
|
||||||
totalKeys, err := this.list.Count()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if this.policy.MaxKeys > 0 && totalKeys > this.policy.MaxKeys {
|
|
||||||
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
|
|
||||||
}
|
|
||||||
capacityBytes := this.memoryCapacityBytes()
|
capacityBytes := this.memoryCapacityBytes()
|
||||||
if bodySize < 0 {
|
if bodySize < 0 {
|
||||||
bodySize = 0
|
bodySize = 0
|
||||||
@@ -216,7 +209,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 先删除
|
// 先删除
|
||||||
err = this.deleteWithoutLocker(key)
|
err := this.deleteWithoutLocker(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
81
internal/utils/fs/stat.go
Normal file
81
internal/utils/fs/stat.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package fsutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stat device contains the path
|
||||||
|
func Stat(path string) (*StatResult, error) {
|
||||||
|
var stat = &unix.Statfs_t{}
|
||||||
|
err := unix.Statfs(path, stat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewStatResult(stat), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var locker = &sync.RWMutex{}
|
||||||
|
var cacheMap = map[string]*StatResult{} // path => StatResult
|
||||||
|
|
||||||
|
const cacheLife = 3 // seconds
|
||||||
|
|
||||||
|
// StatCache stat device with cache
|
||||||
|
func StatCache(path string) (*StatResult, error) {
|
||||||
|
locker.RLock()
|
||||||
|
stat, ok := cacheMap[path]
|
||||||
|
if ok && stat.updatedAt >= fasttime.Now().Unix()-cacheLife {
|
||||||
|
locker.RUnlock()
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
locker.RUnlock()
|
||||||
|
|
||||||
|
locker.Lock()
|
||||||
|
defer locker.Unlock()
|
||||||
|
|
||||||
|
stat, err := Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheMap[path] = stat
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatResult struct {
|
||||||
|
rawStat *unix.Statfs_t
|
||||||
|
blockSize uint64
|
||||||
|
|
||||||
|
updatedAt int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatResult(rawStat *unix.Statfs_t) *StatResult {
|
||||||
|
var blockSize = rawStat.Bsize
|
||||||
|
if blockSize < 0 {
|
||||||
|
blockSize = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StatResult{
|
||||||
|
rawStat: rawStat,
|
||||||
|
blockSize: uint64(blockSize),
|
||||||
|
updatedAt: fasttime.Now().Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *StatResult) AvailableSize() uint64 {
|
||||||
|
return this.rawStat.Bavail * this.blockSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *StatResult) TotalSize() uint64 {
|
||||||
|
return this.rawStat.Blocks * this.blockSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *StatResult) UsedSize() uint64 {
|
||||||
|
if this.rawStat.Bavail <= this.rawStat.Blocks {
|
||||||
|
return (this.rawStat.Blocks - this.rawStat.Bavail) * this.blockSize
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
69
internal/utils/fs/stat_test.go
Normal file
69
internal/utils/fs/stat_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package fsutils_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStat(t *testing.T) {
|
||||||
|
stat, err := fsutils.Stat("/usr/local")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("available:", stat.AvailableSize()/(1<<30), "total:", stat.TotalSize()/(1<<30), "used:", stat.UsedSize()/(1<<30))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatCache(t *testing.T) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
stat, err := fsutils.StatCache("/usr/local")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("available:", stat.AvailableSize()/(1<<30), "total:", stat.TotalSize()/(1<<30), "used:", stat.UsedSize()/(1<<30))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrent(t *testing.T) {
|
||||||
|
var before = time.Now()
|
||||||
|
defer func() {
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
}()
|
||||||
|
|
||||||
|
var count = 10000
|
||||||
|
var wg = sync.WaitGroup{}
|
||||||
|
wg.Add(count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
_, _ = fsutils.Stat("/usr/local")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStat(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_, err := fsutils.Stat("/usr/local")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStatCache(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_, err := fsutils.StatCache("/usr/local")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user