feat(cache): 添加缓存 TTL 新鲜度验证优化
在 FileEntry 中新增 CachedAt 字段记录缓存时间,实现 TTL 窗口内的缓存新鲜度验证: - TTL 内跳过 ModTime 验证,减少 os.Stat 调用 - TTL 过期后验证 ModTime,文件未修改时刷新 CachedAt - 默认 TTL 设置为 5 秒 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0380f8798
commit
fa55bfd497
26
internal/cache/file_cache.go
vendored
26
internal/cache/file_cache.go
vendored
@ -27,6 +27,7 @@ import (
|
|||||||
// FileEntry 文件缓存条目,存储单个文件的缓存信息。
|
// FileEntry 文件缓存条目,存储单个文件的缓存信息。
|
||||||
type FileEntry struct {
|
type FileEntry struct {
|
||||||
ModTime time.Time
|
ModTime time.Time
|
||||||
|
CachedAt time.Time // 缓存时间,用于 TTL 验证(新鲜度)
|
||||||
LastAccess time.Time
|
LastAccess time.Time
|
||||||
element *list.Element
|
element *list.Element
|
||||||
Path string
|
Path string
|
||||||
@ -100,6 +101,12 @@ func (c *FileCache) Get(path string) (*FileEntry, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 迁移处理: CachedAt 为零值时视为刚刚缓存(旧条目)
|
||||||
|
// 在锁内执行,确保并发安全
|
||||||
|
if entry.CachedAt.IsZero() {
|
||||||
|
entry.CachedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
// 更新访问时间并移到 LRU 链表头部
|
// 更新访问时间并移到 LRU 链表头部
|
||||||
entry.LastAccess = time.Now()
|
entry.LastAccess = time.Now()
|
||||||
c.lruList.MoveToFront(entry.element)
|
c.lruList.MoveToFront(entry.element)
|
||||||
@ -130,6 +137,7 @@ func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time)
|
|||||||
entry.Data = data
|
entry.Data = data
|
||||||
entry.Size = size
|
entry.Size = size
|
||||||
entry.ModTime = modTime
|
entry.ModTime = modTime
|
||||||
|
entry.CachedAt = time.Now() // 更新缓存时间
|
||||||
entry.LastAccess = time.Now()
|
entry.LastAccess = time.Now()
|
||||||
c.currentSize += size
|
c.currentSize += size
|
||||||
c.lruList.MoveToFront(entry.element)
|
c.lruList.MoveToFront(entry.element)
|
||||||
@ -143,6 +151,7 @@ func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time)
|
|||||||
Data: data,
|
Data: data,
|
||||||
Size: size,
|
Size: size,
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
|
CachedAt: time.Now(), // 设置缓存时间
|
||||||
LastAccess: time.Now(),
|
LastAccess: time.Now(),
|
||||||
}
|
}
|
||||||
entry.element = c.lruList.PushFront(entry)
|
entry.element = c.lruList.PushFront(entry)
|
||||||
@ -168,6 +177,23 @@ func (c *FileCache) Delete(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefreshCachedAt 更新 CachedAt 并移动 LRU 位置。
|
||||||
|
//
|
||||||
|
// 用于 TTL 过期但文件未修改时刷新缓存时间。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - path: 文件路径,作为缓存键
|
||||||
|
func (c *FileCache) RefreshCachedAt(path string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if entry, ok := c.entries[path]; ok {
|
||||||
|
entry.CachedAt = time.Now()
|
||||||
|
entry.LastAccess = time.Now()
|
||||||
|
c.lruList.MoveToFront(entry.element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// removeEntry 内部删除条目(不加锁)。
|
// removeEntry 内部删除条目(不加锁)。
|
||||||
//
|
//
|
||||||
// 从 LRU 链表和条目映射中移除指定条目,更新当前内存使用量。
|
// 从 LRU 链表和条目映射中移除指定条目,更新当前内存使用量。
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"rua.plus/lolly/internal/cache"
|
"rua.plus/lolly/internal/cache"
|
||||||
@ -41,6 +42,7 @@ import (
|
|||||||
// - alias 与 root 互斥,同时配置时 alias 优先
|
// - alias 与 root 互斥,同时配置时 alias 优先
|
||||||
type StaticHandler struct {
|
type StaticHandler struct {
|
||||||
fileCache *cache.FileCache
|
fileCache *cache.FileCache
|
||||||
|
cacheTTL time.Duration // 缓存新鲜度 TTL(默认 5s,0 表示每次验证 ModTime)
|
||||||
gzipStatic *compression.GzipStatic
|
gzipStatic *compression.GzipStatic
|
||||||
router *Router
|
router *Router
|
||||||
root string
|
root string
|
||||||
@ -202,6 +204,23 @@ func (h *StaticHandler) SetSymlinkCheck(enabled bool) {
|
|||||||
h.symlinkCheck = enabled
|
h.symlinkCheck = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheTTL 设置缓存新鲜度 TTL。
|
||||||
|
//
|
||||||
|
// TTL 控制缓存条目的新鲜度验证间隔。
|
||||||
|
// 在 TTL 窗口内,缓存命中时跳过 ModTime 验证以减少 os.Stat 调用。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - ttl: TTL 时间间隔
|
||||||
|
//
|
||||||
|
// TTL 值说明:
|
||||||
|
// - ttl > 0: TTL 内跳过 ModTime 验证,过期后验证
|
||||||
|
// - ttl = 0: 每次请求验证 ModTime(向后兼容)
|
||||||
|
//
|
||||||
|
// 默认 TTL 为 5 秒。
|
||||||
|
func (h *StaticHandler) SetCacheTTL(ttl time.Duration) {
|
||||||
|
h.cacheTTL = ttl
|
||||||
|
}
|
||||||
|
|
||||||
// Handle 处理静态文件请求。
|
// Handle 处理静态文件请求。
|
||||||
//
|
//
|
||||||
// 根据请求路径查找并返回对应的静态文件。
|
// 根据请求路径查找并返回对应的静态文件。
|
||||||
@ -456,7 +475,36 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接返回文件
|
// Phase 2: 缓存查找 + TTL 验证
|
||||||
|
// 在 serveFile 调用前检查缓存,减少 os.ReadFile 调用
|
||||||
|
// 注意: CachedAt 迁移已在 FileCache.Get() 内部完成,确保并发安全
|
||||||
|
if h.fileCache != nil {
|
||||||
|
if entry, ok := h.fileCache.Get(filePath); ok {
|
||||||
|
// TTL 验证(cacheTTL > 0 时启用)
|
||||||
|
if h.cacheTTL > 0 && time.Since(entry.CachedAt) < h.cacheTTL {
|
||||||
|
// TTL 内直接返回(无需验证 ModTime)
|
||||||
|
ctx.Response.SetBody(entry.Data)
|
||||||
|
ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTL 过期或未启用 TTL,验证文件新鲜度
|
||||||
|
if entry.ModTime.Equal(info.ModTime()) {
|
||||||
|
// 文件未修改,刷新 TTL 并返回
|
||||||
|
if h.cacheTTL > 0 {
|
||||||
|
h.fileCache.RefreshCachedAt(filePath)
|
||||||
|
}
|
||||||
|
ctx.Response.SetBody(entry.Data)
|
||||||
|
ctx.Response.Header.SetContentType(mimeutil.DetectContentType(filePath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件已修改,删除缓存继续处理
|
||||||
|
h.fileCache.Delete(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3: 缓存未命中,调用 serveFile 处理
|
||||||
h.serveFile(ctx, filePath, info)
|
h.serveFile(ctx, filePath, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1115,6 +1115,8 @@ func (s *Server) registerStaticHandlers(router *handler.Router, cfg *config.Serv
|
|||||||
)
|
)
|
||||||
if s.fileCache != nil {
|
if s.fileCache != nil {
|
||||||
staticHandler.SetFileCache(s.fileCache)
|
staticHandler.SetFileCache(s.fileCache)
|
||||||
|
// 设置默认缓存 TTL (5s)
|
||||||
|
staticHandler.SetCacheTTL(5 * time.Second)
|
||||||
}
|
}
|
||||||
if cfg.Compression.GzipStatic {
|
if cfg.Compression.GzipStatic {
|
||||||
staticHandler.SetGzipStatic(true, cfg.Compression.GzipStaticExtensions)
|
staticHandler.SetGzipStatic(true, cfg.Compression.GzipStaticExtensions)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user