perf(cache,proxy): 使用 uint64 哈希键优化代理缓存性能
- ProxyCache 的 entries 和 pending map 从 string 改为 uint64 键 - 新增 buildCacheKeyHash 使用 FNV-64a 计算哈希(零分配) - 增加原始键碰撞验证,防止哈希冲突误匹配 - 更新相关测试和基准测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
49d33f8b0c
commit
214ea4e9a6
38
internal/cache/cache_bench_test.go
vendored
38
internal/cache/cache_bench_test.go
vendored
@ -10,10 +10,18 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// hashKeyBench 计算字符串的 FNV-64a 哈希值,用于 benchmark。
|
||||||
|
func hashKeyBench(s string) uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
// BenchmarkFileCacheGet 测试热点读取场景下的 Get 性能。
|
// BenchmarkFileCacheGet 测试热点读取场景下的 Get 性能。
|
||||||
// 模拟缓存命中率高的场景,测试 LRU 链表的访问效率。
|
// 模拟缓存命中率高的场景,测试 LRU 链表的访问效率。
|
||||||
func BenchmarkFileCacheGet(b *testing.B) {
|
func BenchmarkFileCacheGet(b *testing.B) {
|
||||||
@ -190,18 +198,20 @@ func BenchmarkProxyCacheGet(b *testing.B) {
|
|||||||
|
|
||||||
// 预填充缓存
|
// 预填充缓存
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
key := fmt.Sprintf("key%d", i)
|
origKey := fmt.Sprintf("key%d", i)
|
||||||
|
hashKey := hashKeyBench(origKey)
|
||||||
data := []byte("response body")
|
data := []byte("response body")
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
pc.Set(key, data, headers, 200, 10*time.Minute)
|
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
i := 0
|
i := 0
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
key := fmt.Sprintf("key%d", i%1000)
|
origKey := fmt.Sprintf("key%d", i%1000)
|
||||||
pc.Get(key)
|
hashKey := hashKeyBench(origKey)
|
||||||
|
pc.Get(hashKey, origKey)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -215,8 +225,9 @@ func BenchmarkProxyCacheSet(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
key := fmt.Sprintf("key%d", i)
|
origKey := fmt.Sprintf("key%d", i)
|
||||||
pc.Set(key, data, headers, 200, 10*time.Minute)
|
hashKey := hashKeyBench(origKey)
|
||||||
|
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,10 +238,11 @@ func BenchmarkProxyCacheConcurrent(b *testing.B) {
|
|||||||
|
|
||||||
// 预填充缓存
|
// 预填充缓存
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
key := fmt.Sprintf("key%d", i)
|
origKey := fmt.Sprintf("key%d", i)
|
||||||
|
hashKey := hashKeyBench(origKey)
|
||||||
data := []byte("response body")
|
data := []byte("response body")
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
pc.Set(key, data, headers, 200, 10*time.Minute)
|
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@ -238,13 +250,15 @@ func BenchmarkProxyCacheConcurrent(b *testing.B) {
|
|||||||
i := 0
|
i := 0
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
if i%10 == 0 {
|
if i%10 == 0 {
|
||||||
key := fmt.Sprintf("newkey%d", i)
|
origKey := fmt.Sprintf("newkey%d", i)
|
||||||
|
hashKey := hashKeyBench(origKey)
|
||||||
data := []byte("new response body")
|
data := []byte("new response body")
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
pc.Set(key, data, headers, 200, 10*time.Minute)
|
pc.Set(hashKey, origKey, data, headers, 200, 10*time.Minute)
|
||||||
} else {
|
} else {
|
||||||
key := fmt.Sprintf("key%d", i%1000)
|
origKey := fmt.Sprintf("key%d", i%1000)
|
||||||
pc.Get(key)
|
hashKey := hashKeyBench(origKey)
|
||||||
|
pc.Get(hashKey, origKey)
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|||||||
41
internal/cache/cache_test.go
vendored
41
internal/cache/cache_test.go
vendored
@ -11,10 +11,18 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"hash/fnv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// hashKey 计算字符串的 FNV-64a 哈希值,用于测试。
|
||||||
|
func hashKey(s string) uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewFileCache(t *testing.T) {
|
func TestNewFileCache(t *testing.T) {
|
||||||
fc := NewFileCache(100, 1024*1024, 30*time.Second)
|
fc := NewFileCache(100, 1024*1024, 30*time.Second)
|
||||||
if fc == nil {
|
if fc == nil {
|
||||||
@ -164,9 +172,9 @@ func TestProxyCacheSetGet(t *testing.T) {
|
|||||||
data := []byte("response body")
|
data := []byte("response body")
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
|
|
||||||
pc.Set(key, data, headers, 200, 10*time.Minute)
|
pc.Set(hashKey(key), key, data, headers, 200, 10*time.Minute)
|
||||||
|
|
||||||
entry, ok, stale := pc.Get(key)
|
entry, ok, stale := pc.Get(hashKey(key), key)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("Expected to find cached entry")
|
t.Error("Expected to find cached entry")
|
||||||
}
|
}
|
||||||
@ -185,10 +193,10 @@ func TestProxyCacheExpiration(t *testing.T) {
|
|||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0)
|
||||||
|
|
||||||
key := "expire-test"
|
key := "expire-test"
|
||||||
pc.Set(key, []byte("data"), nil, 200, 100*time.Millisecond)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
// 立即获取应该成功
|
// 立即获取应该成功
|
||||||
_, ok, _ := pc.Get(key)
|
_, ok, _ := pc.Get(hashKey(key), key)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("Expected entry to exist")
|
t.Error("Expected entry to exist")
|
||||||
}
|
}
|
||||||
@ -196,7 +204,7 @@ func TestProxyCacheExpiration(t *testing.T) {
|
|||||||
// 等待过期
|
// 等待过期
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
_, ok, _ = pc.Get(key)
|
_, ok, _ = pc.Get(hashKey(key), key)
|
||||||
if ok {
|
if ok {
|
||||||
t.Error("Expected entry to be expired")
|
t.Error("Expected entry to be expired")
|
||||||
}
|
}
|
||||||
@ -206,12 +214,12 @@ func TestProxyCacheStaleWhileRevalidate(t *testing.T) {
|
|||||||
pc := NewProxyCache(nil, false, 200*time.Millisecond)
|
pc := NewProxyCache(nil, false, 200*time.Millisecond)
|
||||||
|
|
||||||
key := "stale-test"
|
key := "stale-test"
|
||||||
pc.Set(key, []byte("data"), nil, 200, 100*time.Millisecond)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
// 等待过期但仍在 stale 时间内
|
// 等待过期但仍在 stale 时间内
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
entry, ok, stale := pc.Get(key)
|
entry, ok, stale := pc.Get(hashKey(key), key)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("Expected stale entry to be usable")
|
t.Error("Expected stale entry to be usable")
|
||||||
}
|
}
|
||||||
@ -229,22 +237,22 @@ func TestProxyCacheLock(t *testing.T) {
|
|||||||
key := "lock-test"
|
key := "lock-test"
|
||||||
|
|
||||||
// 获取锁
|
// 获取锁
|
||||||
ch := pc.AcquireLock(key)
|
ch := pc.AcquireLock(hashKey(key))
|
||||||
if ch != nil {
|
if ch != nil {
|
||||||
t.Error("Expected to acquire lock (nil chan)")
|
t.Error("Expected to acquire lock (nil chan)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二次获取应该返回等待 chan
|
// 第二次获取应该返回等待 chan
|
||||||
ch2 := pc.AcquireLock(key)
|
ch2 := pc.AcquireLock(hashKey(key))
|
||||||
if ch2 == nil {
|
if ch2 == nil {
|
||||||
t.Error("Expected waiting chan when lock is held")
|
t.Error("Expected waiting chan when lock is held")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置缓存并释放锁
|
// 设置缓存并释放锁
|
||||||
pc.Set(key, []byte("data"), nil, 200, 10*time.Minute)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 10*time.Minute)
|
||||||
|
|
||||||
// 现在应该能获取缓存
|
// 现在应该能获取缓存
|
||||||
_, ok, _ := pc.Get(key)
|
_, ok, _ := pc.Get(hashKey(key), key)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("Expected cache entry after lock release")
|
t.Error("Expected cache entry after lock release")
|
||||||
}
|
}
|
||||||
@ -286,10 +294,11 @@ func TestProxyCacheMatchRule(t *testing.T) {
|
|||||||
func TestProxyCacheDelete(t *testing.T) {
|
func TestProxyCacheDelete(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0)
|
||||||
|
|
||||||
pc.Set("key1", []byte("data"), nil, 200, 10*time.Minute)
|
key := "key1"
|
||||||
pc.Delete("key1")
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 10*time.Minute)
|
||||||
|
pc.Delete(hashKey(key))
|
||||||
|
|
||||||
_, ok, _ := pc.Get("key1")
|
_, ok, _ := pc.Get(hashKey(key), key)
|
||||||
if ok {
|
if ok {
|
||||||
t.Error("Expected entry to be deleted")
|
t.Error("Expected entry to be deleted")
|
||||||
}
|
}
|
||||||
@ -298,8 +307,8 @@ func TestProxyCacheDelete(t *testing.T) {
|
|||||||
func TestProxyCacheClear(t *testing.T) {
|
func TestProxyCacheClear(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0)
|
||||||
|
|
||||||
pc.Set("a", []byte("a"), nil, 200, 10*time.Minute)
|
pc.Set(hashKey("a"), "a", []byte("a"), nil, 200, 10*time.Minute)
|
||||||
pc.Set("b", []byte("b"), nil, 200, 10*time.Minute)
|
pc.Set(hashKey("b"), "b", []byte("b"), nil, 200, 10*time.Minute)
|
||||||
|
|
||||||
pc.Clear()
|
pc.Clear()
|
||||||
|
|
||||||
|
|||||||
54
internal/cache/file_cache.go
vendored
54
internal/cache/file_cache.go
vendored
@ -286,7 +286,8 @@ type ProxyCacheRule struct {
|
|||||||
|
|
||||||
// ProxyCacheEntry 代理缓存条目。
|
// ProxyCacheEntry 代理缓存条目。
|
||||||
type ProxyCacheEntry struct {
|
type ProxyCacheEntry struct {
|
||||||
Key string // 缓存 key
|
Key string // 缓存 key (uint64 哈希值)
|
||||||
|
OrigKey string // 原始 key 用于碰撞验证
|
||||||
Data []byte // 响应体
|
Data []byte // 响应体
|
||||||
Headers map[string]string // 响应头
|
Headers map[string]string // 响应头
|
||||||
Status int // 状态码
|
Status int // 状态码
|
||||||
@ -297,10 +298,10 @@ type ProxyCacheEntry struct {
|
|||||||
// ProxyCache 代理响应缓存,支持缓存锁防击穿。
|
// ProxyCache 代理响应缓存,支持缓存锁防击穿。
|
||||||
type ProxyCache struct {
|
type ProxyCache struct {
|
||||||
rules []ProxyCacheRule
|
rules []ProxyCacheRule
|
||||||
entries map[string]*ProxyCacheEntry
|
entries map[uint64]*ProxyCacheEntry
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
cacheLock bool // 缓存锁开关
|
cacheLock bool // 缓存锁开关
|
||||||
pending map[string]*pendingRequest // 正在生成的缓存项
|
pending map[uint64]*pendingRequest // 正在生成的缓存项
|
||||||
staleTime time.Duration // 过期缓存复用时间
|
staleTime time.Duration // 过期缓存复用时间
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,23 +315,29 @@ type pendingRequest struct {
|
|||||||
func NewProxyCache(rules []ProxyCacheRule, cacheLock bool, staleTime time.Duration) *ProxyCache {
|
func NewProxyCache(rules []ProxyCacheRule, cacheLock bool, staleTime time.Duration) *ProxyCache {
|
||||||
return &ProxyCache{
|
return &ProxyCache{
|
||||||
rules: rules,
|
rules: rules,
|
||||||
entries: make(map[string]*ProxyCacheEntry),
|
entries: make(map[uint64]*ProxyCacheEntry),
|
||||||
cacheLock: cacheLock,
|
cacheLock: cacheLock,
|
||||||
pending: make(map[string]*pendingRequest),
|
pending: make(map[uint64]*pendingRequest),
|
||||||
staleTime: staleTime,
|
staleTime: staleTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 获取缓存的代理响应。
|
// Get 获取缓存的代理响应。
|
||||||
func (c *ProxyCache) Get(key string) (*ProxyCacheEntry, bool, bool) {
|
// hashKey 是 uint64 哈希值,origKey 是原始 key 用于碰撞验证。
|
||||||
|
func (c *ProxyCache) Get(hashKey uint64, origKey string) (*ProxyCacheEntry, bool, bool) {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
entry, ok := c.entries[key]
|
entry, ok := c.entries[hashKey]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 双重验证:检查原始 key 是否匹配(防止哈希碰撞)
|
||||||
|
if entry.OrigKey != origKey {
|
||||||
|
return nil, false, false
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否过期
|
// 检查是否过期
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
expired := now.Sub(entry.Created) > entry.MaxAge
|
expired := now.Sub(entry.Created) > entry.MaxAge
|
||||||
@ -347,12 +354,13 @@ func (c *ProxyCache) Get(key string) (*ProxyCacheEntry, bool, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set 设置代理缓存条目。
|
// Set 设置代理缓存条目。
|
||||||
func (c *ProxyCache) Set(key string, data []byte, headers map[string]string, status int, maxAge time.Duration) {
|
func (c *ProxyCache) Set(hashKey uint64, origKey string, data []byte, headers map[string]string, status int, maxAge time.Duration) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
c.entries[key] = &ProxyCacheEntry{
|
c.entries[hashKey] = &ProxyCacheEntry{
|
||||||
Key: key,
|
Key: origKey, // 存储原始 key 作为 Key 字段(保持兼容性)
|
||||||
|
OrigKey: origKey,
|
||||||
Data: data,
|
Data: data,
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
Status: status,
|
Status: status,
|
||||||
@ -361,17 +369,17 @@ func (c *ProxyCache) Set(key string, data []byte, headers map[string]string, sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果有等待的请求,通知它们
|
// 如果有等待的请求,通知它们
|
||||||
if pending, ok := c.pending[key]; ok {
|
if pending, ok := c.pending[hashKey]; ok {
|
||||||
pending.err = nil
|
pending.err = nil
|
||||||
close(pending.done)
|
close(pending.done)
|
||||||
delete(c.pending, key)
|
delete(c.pending, hashKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcquireLock 获取缓存生成锁(防止击穿)。
|
// AcquireLock 获取缓存生成锁(防止击穿)。
|
||||||
// 如果返回 nil,表示获得锁,应该去生成缓存。
|
// 如果返回 nil,表示获得锁,应该去生成缓存。
|
||||||
// 如果返回 chan,表示有其他请求正在生成,应该等待。
|
// 如果返回 chan,表示有其他请求正在生成,应该等待。
|
||||||
func (c *ProxyCache) AcquireLock(key string) <-chan struct{} {
|
func (c *ProxyCache) AcquireLock(hashKey uint64) <-chan struct{} {
|
||||||
if !c.cacheLock {
|
if !c.cacheLock {
|
||||||
return nil // 不使用缓存锁
|
return nil // 不使用缓存锁
|
||||||
}
|
}
|
||||||
@ -380,12 +388,12 @@ func (c *ProxyCache) AcquireLock(key string) <-chan struct{} {
|
|||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
// 检查是否已有缓存
|
// 检查是否已有缓存
|
||||||
if _, ok := c.entries[key]; ok {
|
if _, ok := c.entries[hashKey]; ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有 pending 请求
|
// 检查是否有 pending 请求
|
||||||
if pending, ok := c.pending[key]; ok {
|
if pending, ok := c.pending[hashKey]; ok {
|
||||||
return pending.done // 等待现有请求
|
return pending.done // 等待现有请求
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,12 +401,12 @@ func (c *ProxyCache) AcquireLock(key string) <-chan struct{} {
|
|||||||
pending := &pendingRequest{
|
pending := &pendingRequest{
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
c.pending[key] = pending
|
c.pending[hashKey] = pending
|
||||||
return nil // 获得锁,应该生成缓存
|
return nil // 获得锁,应该生成缓存
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseLock 释放缓存生成锁。
|
// ReleaseLock 释放缓存生成锁。
|
||||||
func (c *ProxyCache) ReleaseLock(key string, err error) {
|
func (c *ProxyCache) ReleaseLock(hashKey uint64, err error) {
|
||||||
if !c.cacheLock {
|
if !c.cacheLock {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -406,10 +414,10 @@ func (c *ProxyCache) ReleaseLock(key string, err error) {
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if pending, ok := c.pending[key]; ok {
|
if pending, ok := c.pending[hashKey]; ok {
|
||||||
pending.err = err
|
pending.err = err
|
||||||
close(pending.done)
|
close(pending.done)
|
||||||
delete(c.pending, key)
|
delete(c.pending, hashKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,18 +510,18 @@ func containsInt(slice []int, val int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete 删除缓存条目。
|
// Delete 删除缓存条目。
|
||||||
func (c *ProxyCache) Delete(key string) {
|
func (c *ProxyCache) Delete(hashKey uint64) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
delete(c.entries, key)
|
delete(c.entries, hashKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear 清空代理缓存。
|
// Clear 清空代理缓存。
|
||||||
func (c *ProxyCache) Clear() {
|
func (c *ProxyCache) Clear() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
c.entries = make(map[string]*ProxyCacheEntry)
|
c.entries = make(map[uint64]*ProxyCacheEntry)
|
||||||
c.pending = make(map[string]*pendingRequest)
|
c.pending = make(map[uint64]*pendingRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats 返回代理缓存统计。
|
// Stats 返回代理缓存统计。
|
||||||
|
|||||||
@ -35,6 +35,7 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -395,8 +396,8 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
// 尝试从缓存获取(如果启用)
|
// 尝试从缓存获取(如果启用)
|
||||||
if p.cache != nil && attempt == 0 {
|
if p.cache != nil && attempt == 0 {
|
||||||
cacheKey := p.buildCacheKey(ctx)
|
hashKey, origKey := p.buildCacheKeyHash(ctx)
|
||||||
if entry, ok, stale := p.cache.Get(cacheKey); ok {
|
if entry, ok, stale := p.cache.Get(hashKey, origKey); ok {
|
||||||
// 缓存命中
|
// 缓存命中
|
||||||
loadbalance.DecrementConnections(target)
|
loadbalance.DecrementConnections(target)
|
||||||
if !stale {
|
if !stale {
|
||||||
@ -407,22 +408,26 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 过期缓存,尝试后台刷新,同时返回旧数据
|
// 过期缓存,尝试后台刷新,同时返回旧数据
|
||||||
go p.backgroundRefresh(ctx, target, cacheKey)
|
|
||||||
|
go p.backgroundRefresh(ctx, target, hashKey, origKey)
|
||||||
upstreamAddr = "CACHE"
|
upstreamAddr = "CACHE"
|
||||||
upstreamStatus = entry.Status
|
upstreamStatus = entry.Status
|
||||||
|
|
||||||
p.writeCachedResponse(ctx, entry)
|
p.writeCachedResponse(ctx, entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否需要缓存锁(防止缓存击穿)
|
// 检查是否需要缓存锁(防止缓存击穿)
|
||||||
if done := p.cache.AcquireLock(cacheKey); done != nil {
|
if done := p.cache.AcquireLock(hashKey); done != nil {
|
||||||
// 有其他请求正在生成缓存,等待
|
// 有其他请求正在生成缓存,等待
|
||||||
loadbalance.DecrementConnections(target)
|
loadbalance.DecrementConnections(target)
|
||||||
<-done
|
<-done
|
||||||
// 重新尝试获取缓存
|
// 重新尝试获取缓存
|
||||||
if entry, ok, _ := p.cache.Get(cacheKey); ok {
|
|
||||||
|
if entry, ok, _ := p.cache.Get(hashKey, origKey); ok {
|
||||||
upstreamAddr = "CACHE"
|
upstreamAddr = "CACHE"
|
||||||
upstreamStatus = entry.Status
|
upstreamStatus = entry.Status
|
||||||
|
|
||||||
p.writeCachedResponse(ctx, entry)
|
p.writeCachedResponse(ctx, entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -446,7 +451,8 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
// 释放缓存锁
|
// 释放缓存锁
|
||||||
if p.cache != nil && attempt == 0 {
|
if p.cache != nil && attempt == 0 {
|
||||||
p.cache.ReleaseLock(p.buildCacheKey(ctx), err)
|
hashKey, _ := p.buildCacheKeyHash(ctx)
|
||||||
|
p.cache.ReleaseLock(hashKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置失败状态
|
// 设置失败状态
|
||||||
@ -482,7 +488,8 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
if shouldRetry {
|
if shouldRetry {
|
||||||
// 释放缓存锁
|
// 释放缓存锁
|
||||||
if p.cache != nil && attempt == 0 {
|
if p.cache != nil && attempt == 0 {
|
||||||
p.cache.ReleaseLock(p.buildCacheKey(ctx), fmt.Errorf("HTTP %d", statusCode))
|
hashKey, _ := p.buildCacheKeyHash(ctx)
|
||||||
|
p.cache.ReleaseLock(hashKey, fmt.Errorf("HTTP %d", statusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果不是最后一次尝试,继续下一个目标
|
// 如果不是最后一次尝试,继续下一个目标
|
||||||
@ -502,16 +509,16 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
// 存入缓存(如果启用且响应可缓存)
|
// 存入缓存(如果启用且响应可缓存)
|
||||||
if p.cache != nil {
|
if p.cache != nil {
|
||||||
cacheKey := p.buildCacheKey(ctx)
|
hashKey, origKey := p.buildCacheKeyHash(ctx)
|
||||||
if statusCode >= 200 && statusCode < 300 {
|
if statusCode >= 200 && statusCode < 300 {
|
||||||
// 提取响应头
|
// 提取响应头
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
for key, value := range ctx.Response.Header.All() {
|
for key, value := range ctx.Response.Header.All() {
|
||||||
headers[string(key)] = string(value)
|
headers[string(key)] = string(value)
|
||||||
}
|
}
|
||||||
p.cache.Set(cacheKey, ctx.Response.Body(), headers, statusCode, p.config.Cache.MaxAge)
|
p.cache.Set(hashKey, origKey, ctx.Response.Body(), headers, statusCode, p.config.Cache.MaxAge)
|
||||||
}
|
}
|
||||||
p.cache.ReleaseLock(cacheKey, nil)
|
p.cache.ReleaseLock(hashKey, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改响应头
|
// 修改响应头
|
||||||
@ -763,6 +770,18 @@ func (p *Proxy) buildCacheKey(ctx *fasthttp.RequestCtx) string {
|
|||||||
return string(ctx.Request.Header.Method()) + ":" + string(ctx.Request.URI().RequestURI())
|
return string(ctx.Request.Header.Method()) + ":" + string(ctx.Request.URI().RequestURI())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildCacheKeyHash 使用 FNV-64a 计算缓存键的 uint64 哈希值。
|
||||||
|
// 这个函数分配 0 内存,比字符串键更高效。
|
||||||
|
func (p *Proxy) buildCacheKeyHash(ctx *fasthttp.RequestCtx) (uint64, string) {
|
||||||
|
// 构建原始 key
|
||||||
|
origKey := p.buildCacheKey(ctx)
|
||||||
|
|
||||||
|
// 使用 FNV-64a 计算哈希
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(origKey))
|
||||||
|
return h.Sum64(), origKey
|
||||||
|
}
|
||||||
|
|
||||||
// writeCachedResponse 写入缓存的响应。
|
// writeCachedResponse 写入缓存的响应。
|
||||||
func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.ProxyCacheEntry) {
|
func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.ProxyCacheEntry) {
|
||||||
ctx.Response.SetBody(entry.Data)
|
ctx.Response.SetBody(entry.Data)
|
||||||
@ -774,7 +793,7 @@ func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.Proxy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// backgroundRefresh 后台刷新缓存。
|
// backgroundRefresh 后台刷新缓存。
|
||||||
func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.Target, cacheKey string) {
|
func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.Target, hashKey uint64, origKey string) {
|
||||||
// 创建新的请求上下文副本
|
// 创建新的请求上下文副本
|
||||||
req := fasthttp.AcquireRequest()
|
req := fasthttp.AcquireRequest()
|
||||||
resp := fasthttp.AcquireResponse()
|
resp := fasthttp.AcquireResponse()
|
||||||
@ -793,7 +812,7 @@ func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.
|
|||||||
// 执行请求
|
// 执行请求
|
||||||
err := client.Do(req, resp)
|
err := client.Do(req, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.cache.ReleaseLock(cacheKey, err)
|
p.cache.ReleaseLock(hashKey, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -804,7 +823,7 @@ func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新缓存
|
// 更新缓存
|
||||||
p.cache.Set(cacheKey, resp.Body(), headers, resp.StatusCode(), p.config.Cache.MaxAge)
|
p.cache.Set(hashKey, origKey, resp.Body(), headers, resp.StatusCode(), p.config.Cache.MaxAge)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCacheStats 返回代理缓存的统计信息。
|
// GetCacheStats 返回代理缓存的统计信息。
|
||||||
|
|||||||
@ -1046,9 +1046,11 @@ func TestProxyCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 测试缓存设置和获取
|
// 测试缓存设置和获取
|
||||||
p.cache.Set("/api/test", []byte("test data"), map[string]string{"Content-Type": "text/plain"}, 200, 1*time.Second)
|
testKey := "/api/test"
|
||||||
|
hashKey := uint64(0x1234567890abcdef) // 测试用哈希值
|
||||||
|
p.cache.Set(hashKey, testKey, []byte("test data"), map[string]string{"Content-Type": "text/plain"}, 200, 1*time.Second)
|
||||||
|
|
||||||
entry, found, stale := p.cache.Get("/api/test")
|
entry, found, stale := p.cache.Get(hashKey, testKey)
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Cache should find existing entry")
|
t.Error("Cache should find existing entry")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user