perf(resolver): replace slice-based LRU with container/list for O(1) operations

DNS resolver LRU cache used []string slice for access ordering:
- moveToFrontLocked: O(n) linear scan + slice重组 per cache update
- storeCache held exclusive write lock during entire O(n) operation,
  blocking all concurrent reads

Replace with container/list + map[string]*list.Element:
- storeCache: O(1) MoveToFront via list.Element pointer
- evictLRULocked: O(1) Back() + Remove()
- DeleteCacheEntry: O(1) instead of O(n) scan

This matches the LRU pattern already used by FileCache and
FileInfoCache. Write lock duration reduced from O(n) to O(1),
significantly reducing read contention under mixed workloads.
This commit is contained in:
xfy 2026-06-04 00:13:10 +08:00
parent ba8c746a2e
commit 12caed5d4e
3 changed files with 36 additions and 38 deletions

View File

@ -6,6 +6,7 @@
package resolver package resolver
import ( import (
"container/list"
"time" "time"
) )
@ -59,23 +60,21 @@ func (r *DNSResolver) GetCacheEntry(host string) (*DNSCacheEntry, bool) {
// DeleteCacheEntry 删除指定主机的缓存条目。 // DeleteCacheEntry 删除指定主机的缓存条目。
func (r *DNSResolver) DeleteCacheEntry(host string) { func (r *DNSResolver) DeleteCacheEntry(host string) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock()
delete(r.cache, host) delete(r.cache, host)
// 从 LRU 链表中移除 if elem, ok := r.lruIndex[host]; ok {
for i, h := range r.lruOrder { r.lruList.Remove(elem)
if h == host { delete(r.lruIndex, host)
r.lruOrder = append(r.lruOrder[:i], r.lruOrder[i+1:]...)
break
}
} }
delete(r.refreshHosts, host) delete(r.refreshHosts, host)
r.mu.Unlock()
} }
// ClearCache 清空所有缓存。 // ClearCache 清空所有缓存。
func (r *DNSResolver) ClearCache() { func (r *DNSResolver) ClearCache() {
r.mu.Lock() r.mu.Lock()
r.cache = make(map[string]*DNSCacheEntry) r.cache = make(map[string]*DNSCacheEntry)
r.lruOrder = make([]string, 0, r.config.CacheSize) r.lruList = list.New()
r.lruIndex = make(map[string]*list.Element)
r.refreshHosts = make(map[string]struct{}) r.refreshHosts = make(map[string]struct{})
r.mu.Unlock() r.mu.Unlock()
} }

View File

@ -421,22 +421,28 @@ func TestMockDNSMoveToFrontLocked(t *testing.T) {
resolver := New(cfg).(*DNSResolver) resolver := New(cfg).(*DNSResolver)
// 设置缓存
resolver.cache = map[string]*DNSCacheEntry{ resolver.cache = map[string]*DNSCacheEntry{
"a": {}, "a": {},
"b": {}, "b": {},
"c": {}, "c": {},
} }
resolver.lruOrder = []string{"a", "b", "c"} elemA := resolver.lruList.PushFront("a")
elemB := resolver.lruList.PushFront("b")
elemC := resolver.lruList.PushFront("c")
resolver.lruIndex["a"] = elemA
resolver.lruIndex["b"] = elemB
resolver.lruIndex["c"] = elemC
// 移动 "a" 到前端 resolver.lruList.MoveToFront(resolver.lruIndex["a"])
resolver.moveToFrontLocked("a")
// 验证顺序 order := []string{}
for e := resolver.lruList.Back(); e != nil; e = e.Prev() {
order = append(order, e.Value.(string))
}
expected := []string{"b", "c", "a"} expected := []string{"b", "c", "a"}
for i, v := range expected { for i, v := range expected {
if resolver.lruOrder[i] != v { if order[i] != v {
t.Errorf("LRU order[%d] = %s, want %s", i, resolver.lruOrder[i], v) t.Errorf("LRU order[%d] = %s, want %s", i, order[i], v)
} }
} }
} }

View File

@ -16,6 +16,7 @@
package resolver package resolver
import ( import (
"container/list"
"context" "context"
"fmt" "fmt"
"net" "net"
@ -61,41 +62,31 @@ func (r *DNSResolver) storeCache(host string, entry *DNSCacheEntry) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
// 已存在则更新并移到头部 if elem, ok := r.lruIndex[host]; ok {
if _, ok := r.cache[host]; ok {
r.cache[host] = entry r.cache[host] = entry
r.moveToFrontLocked(host) r.lruList.MoveToFront(elem)
return return
} }
// 检查是否需要淘汰
if r.config.CacheSize > 0 && len(r.cache) >= r.config.CacheSize { if r.config.CacheSize > 0 && len(r.cache) >= r.config.CacheSize {
r.evictLRULocked() r.evictLRULocked()
} }
r.cache[host] = entry r.cache[host] = entry
r.lruOrder = append(r.lruOrder, host) elem := r.lruList.PushFront(host)
r.lruIndex[host] = elem
} }
// evictLRULocked 淘汰最久未使用的条目(需持有锁)。 // evictLRULocked 淘汰最久未使用的条目(需持有锁)。
func (r *DNSResolver) evictLRULocked() { func (r *DNSResolver) evictLRULocked() {
if len(r.lruOrder) == 0 { oldest := r.lruList.Back()
if oldest == nil {
return return
} }
oldest := r.lruOrder[0] host := oldest.Value.(string)
delete(r.cache, oldest) delete(r.cache, host)
r.lruOrder = r.lruOrder[1:] delete(r.lruIndex, host)
} r.lruList.Remove(oldest)
// moveToFrontLocked 将条目移到 LRU 链表尾部(最新)(需持有锁)。
func (r *DNSResolver) moveToFrontLocked(host string) {
for i, h := range r.lruOrder {
if h == host {
r.lruOrder = append(r.lruOrder[:i], r.lruOrder[i+1:]...)
r.lruOrder = append(r.lruOrder, host)
return
}
}
} }
// DNSResolver 实现 Resolver 接口的 DNS 解析器。 // DNSResolver 实现 Resolver 接口的 DNS 解析器。
@ -103,8 +94,9 @@ type DNSResolver struct {
config *config.ResolverConfig config *config.ResolverConfig
stopCh chan struct{} stopCh chan struct{}
refreshHosts map[string]struct{} refreshHosts map[string]struct{}
cache map[string]*DNSCacheEntry // DNS 缓存 cache map[string]*DNSCacheEntry
lruOrder []string // LRU 访问顺序(最旧在前) lruList *list.List
lruIndex map[string]*list.Element
hits atomic.Int64 hits atomic.Int64
misses atomic.Int64 misses atomic.Int64
errors atomic.Int64 errors atomic.Int64
@ -166,7 +158,8 @@ func New(cfg *config.ResolverConfig) Resolver {
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
refreshHosts: make(map[string]struct{}), refreshHosts: make(map[string]struct{}),
cache: make(map[string]*DNSCacheEntry), cache: make(map[string]*DNSCacheEntry),
lruOrder: make([]string, 0, cfg.CacheSize), lruList: list.New(),
lruIndex: make(map[string]*list.Element),
} }
} }