From 12caed5d4eb0b9dfa2624bf4ca57b173f63aab26 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 4 Jun 2026 00:13:10 +0800 Subject: [PATCH] perf(resolver): replace slice-based LRU with container/list for O(1) operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/resolver/cache.go | 15 ++++++------ internal/resolver/mock_dns_test.go | 20 +++++++++------ internal/resolver/resolver.go | 39 ++++++++++++------------------ 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/internal/resolver/cache.go b/internal/resolver/cache.go index 196e83d..2288edb 100644 --- a/internal/resolver/cache.go +++ b/internal/resolver/cache.go @@ -6,6 +6,7 @@ package resolver import ( + "container/list" "time" ) @@ -59,23 +60,21 @@ func (r *DNSResolver) GetCacheEntry(host string) (*DNSCacheEntry, bool) { // DeleteCacheEntry 删除指定主机的缓存条目。 func (r *DNSResolver) DeleteCacheEntry(host string) { r.mu.Lock() + defer r.mu.Unlock() delete(r.cache, host) - // 从 LRU 链表中移除 - for i, h := range r.lruOrder { - if h == host { - r.lruOrder = append(r.lruOrder[:i], r.lruOrder[i+1:]...) - break - } + if elem, ok := r.lruIndex[host]; ok { + r.lruList.Remove(elem) + delete(r.lruIndex, host) } delete(r.refreshHosts, host) - r.mu.Unlock() } // ClearCache 清空所有缓存。 func (r *DNSResolver) ClearCache() { r.mu.Lock() 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.mu.Unlock() } diff --git a/internal/resolver/mock_dns_test.go b/internal/resolver/mock_dns_test.go index e7635fe..3f76103 100644 --- a/internal/resolver/mock_dns_test.go +++ b/internal/resolver/mock_dns_test.go @@ -421,22 +421,28 @@ func TestMockDNSMoveToFrontLocked(t *testing.T) { resolver := New(cfg).(*DNSResolver) - // 设置缓存 resolver.cache = map[string]*DNSCacheEntry{ "a": {}, "b": {}, "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.moveToFrontLocked("a") + resolver.lruList.MoveToFront(resolver.lruIndex["a"]) - // 验证顺序 + order := []string{} + for e := resolver.lruList.Back(); e != nil; e = e.Prev() { + order = append(order, e.Value.(string)) + } expected := []string{"b", "c", "a"} for i, v := range expected { - if resolver.lruOrder[i] != v { - t.Errorf("LRU order[%d] = %s, want %s", i, resolver.lruOrder[i], v) + if order[i] != v { + t.Errorf("LRU order[%d] = %s, want %s", i, order[i], v) } } } diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 8f11873..113ede5 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -16,6 +16,7 @@ package resolver import ( + "container/list" "context" "fmt" "net" @@ -61,41 +62,31 @@ func (r *DNSResolver) storeCache(host string, entry *DNSCacheEntry) { r.mu.Lock() defer r.mu.Unlock() - // 已存在则更新并移到头部 - if _, ok := r.cache[host]; ok { + if elem, ok := r.lruIndex[host]; ok { r.cache[host] = entry - r.moveToFrontLocked(host) + r.lruList.MoveToFront(elem) return } - // 检查是否需要淘汰 if r.config.CacheSize > 0 && len(r.cache) >= r.config.CacheSize { r.evictLRULocked() } r.cache[host] = entry - r.lruOrder = append(r.lruOrder, host) + elem := r.lruList.PushFront(host) + r.lruIndex[host] = elem } // evictLRULocked 淘汰最久未使用的条目(需持有锁)。 func (r *DNSResolver) evictLRULocked() { - if len(r.lruOrder) == 0 { + oldest := r.lruList.Back() + if oldest == nil { return } - oldest := r.lruOrder[0] - delete(r.cache, oldest) - r.lruOrder = r.lruOrder[1:] -} - -// 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 - } - } + host := oldest.Value.(string) + delete(r.cache, host) + delete(r.lruIndex, host) + r.lruList.Remove(oldest) } // DNSResolver 实现 Resolver 接口的 DNS 解析器。 @@ -103,8 +94,9 @@ type DNSResolver struct { config *config.ResolverConfig stopCh chan struct{} refreshHosts map[string]struct{} - cache map[string]*DNSCacheEntry // DNS 缓存 - lruOrder []string // LRU 访问顺序(最旧在前) + cache map[string]*DNSCacheEntry + lruList *list.List + lruIndex map[string]*list.Element hits atomic.Int64 misses atomic.Int64 errors atomic.Int64 @@ -166,7 +158,8 @@ func New(cfg *config.ResolverConfig) Resolver { stopCh: make(chan struct{}), refreshHosts: make(map[string]struct{}), cache: make(map[string]*DNSCacheEntry), - lruOrder: make([]string, 0, cfg.CacheSize), + lruList: list.New(), + lruIndex: make(map[string]*list.Element), } }