lolly/internal/proxy/cache_handler.go
xfy 83d4e5e860 perf(proxy): inline FNV-1a in buildCacheKeyHash/buildCacheKeyHashValue
Replaces fnv.New64a() with direct inline hash computation over
fasthttp's []byte slices, eliminating 1 allocation per cache key
computation and 1 []byte(":") allocation.
2026-06-04 10:45:55 +08:00

197 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package proxy 提供反向代理的核心功能,支持请求转发、负载均衡、健康检查等特性。
//
// 包含缓存处理器相关的结构体,用于配置代理缓存行为。
//
// 作者xfy
package proxy
import (
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/cache"
"rua.plus/lolly/internal/loadbalance"
)
// buildCacheKeyHash 使用 FNV-64a 计算缓存键的 uint64 哈希值。
// 使用零分配方式构建哈希,避免 []byte(origKey) 转换。
func (p *Proxy) buildCacheKeyHash(ctx *fasthttp.RequestCtx) (uint64, string) {
method := ctx.Request.Header.Method()
uri := ctx.Request.URI().RequestURI()
var h uint64 = 14695981039346656037
for i := 0; i < len(method); i++ {
h ^= uint64(method[i])
h *= 1099511628211
}
h ^= uint64(':')
h *= 1099511628211
for i := 0; i < len(uri); i++ {
h ^= uint64(uri[i])
h *= 1099511628211
}
origKey := b2s(method) + ":" + b2s(uri)
return h, origKey
}
func (p *Proxy) buildCacheKeyHashValue(ctx *fasthttp.RequestCtx) uint64 {
method := ctx.Request.Header.Method()
uri := ctx.Request.URI().RequestURI()
var h uint64 = 14695981039346656037
for i := 0; i < len(method); i++ {
h ^= uint64(method[i])
h *= 1099511628211
}
h ^= uint64(':')
h *= 1099511628211
for i := 0; i < len(uri); i++ {
h ^= uint64(uri[i])
h *= 1099511628211
}
return h
}
// writeCachedResponse 将缓存的响应写入 FastHTTP 响应上下文。
//
// 设置响应体、状态码、响应头,并添加 X-Cache: HIT 头标记缓存命中。
//
// 参数:
// - ctx: FastHTTP 请求上下文
// - entry: 缓存条目,包含响应数据和元数据
func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.ProxyCacheEntry) {
ctx.Response.SetBody(entry.Data)
ctx.Response.SetStatusCode(entry.Status)
for key, value := range entry.Headers {
ctx.Response.Header.Set(key, value)
}
ctx.Response.Header.Set("X-Cache", "HIT")
}
// backgroundRefresh 在后台异步刷新缓存条目。
//
// 向对应的上游目标发送请求,获取最新响应并更新缓存。
// 该方法在独立 goroutine 中运行,不阻塞主请求流程。
//
// 参数:
// - req: 预复制的请求副本(调用方负责 Acquire/Release
// - target: 要刷新的后端目标
// - hashKey: 缓存哈希键
// - origKey: 缓存原始键
func (p *Proxy) backgroundRefresh(req *fasthttp.Request, target *loadbalance.Target, hashKey uint64, origKey string) {
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp)
// 如果启用 Revalidate添加条件请求头
if p.config.Cache.Revalidate {
if entry, ok, _ := p.cache.Get(hashKey, origKey); ok {
if entry.LastModified != "" {
req.Header.Set("If-Modified-Since", entry.LastModified)
}
if entry.ETag != "" {
req.Header.Set("If-None-Match", entry.ETag)
}
}
}
// 获取客户端
client := p.getClient(target.URL)
if client == nil {
return
}
// 执行请求
err := client.Do(req, resp)
if err != nil {
p.cache.ReleaseLock(hashKey, err)
return
}
// 处理 304 Not Modified 响应
if resp.StatusCode() == 304 {
newHeaders := make(map[string]string, 5) // 预分配,通常只有 Last-Modified 和 ETag
if lm := resp.Header.Peek("Last-Modified"); len(lm) > 0 {
newHeaders["Last-Modified"] = b2s(lm)
}
if et := resp.Header.Peek("ETag"); len(et) > 0 {
newHeaders["ETag"] = b2s(et)
}
p.cache.RefreshTTL(hashKey, origKey, newHeaders)
return
}
// 提取响应头(使用 pool 复用 map
headers, ok := headersPool.Get().(map[string]string)
if !ok {
headers = make(map[string]string, 20)
}
for k := range headers {
delete(headers, k)
}
for key, value := range resp.Header.All() {
headers[string(key)] = string(value)
}
// 更新缓存
p.cache.Set(hashKey, origKey, resp.Body(), headers, resp.StatusCode(), p.getCacheDuration(resp.StatusCode()))
}
// GetCache 返回代理的 ProxyCache 实例(用于 purge handler
// 如果缓存未启用,返回 nil。
func (p *Proxy) GetCache() *cache.ProxyCache {
return p.cache
}
// GetCacheStats 返回代理缓存的统计信息。
// 如果缓存未启用,返回 nil。
func (p *Proxy) GetCacheStats() *cache.ProxyCacheStats {
if p.cache == nil {
return nil
}
stats := p.cache.Stats()
return &stats
}
// getCacheDuration 根据状态码获取缓存时间。
// 优先级CacheValid 配置 > MaxAge
//
// 映射规则:
// - 200-299: CacheValid.OK0 时继承 MaxAge
// - 301/302: CacheValid.Redirect
// - 404: CacheValid.NotFound
// - 400-499除 404: CacheValid.ClientError
// - 500-599: CacheValid.ServerError
// - 其他: 不缓存(返回 0
func (p *Proxy) getCacheDuration(statusCode int) time.Duration {
// 无 CacheValid 配置,使用 MaxAge
if p.config.CacheValid == nil {
return p.config.Cache.MaxAge
}
cv := p.config.CacheValid
switch {
case statusCode >= 200 && statusCode < 300:
if cv.OK > 0 {
return cv.OK
}
return p.config.Cache.MaxAge // 0 表示继承 MaxAge
case statusCode == 301 || statusCode == 302:
return cv.Redirect // 0 表示不缓存
case statusCode == 404:
return cv.NotFound
case statusCode >= 400 && statusCode < 500:
return cv.ClientError
case statusCode >= 500:
return cv.ServerError
default:
return 0 // 不缓存
}
}