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:
parent
ba8c746a2e
commit
12caed5d4e
@ -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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user