// Package resolver 提供 DNS 解析器的基准测试。 // // 该文件包含 DNS 解析器的性能基准测试,测试缓存命中、并发竞争和缓存过期场景。 // // 作者:xfy package resolver import ( "context" "fmt" "testing" "time" "rua.plus/lolly/internal/config" ) // createTestResolver 创建一个用于基准测试的 DNSResolver 实例。 // 预填充缓存以模拟真实场景。 func createTestResolver() *DNSResolver { cfg := &config.ResolverConfig{ Enabled: true, Valid: 30 * time.Second, Timeout: 5 * time.Second, IPv4: true, } r := New(cfg).(*DNSResolver) // 预填充缓存条目,模拟真实的解析场景 for i := range 100 { host := fmt.Sprintf("host%d.example.com", i) r.storeCache(host, &DNSCacheEntry{ IPs: []string{fmt.Sprintf("192.168.1.%d", i%256), fmt.Sprintf("192.168.2.%d", i%256)}, ExpiresAt: time.Now().Add(30 * time.Second), LastLookup: time.Now(), }) r.mu.Lock() r.refreshHosts[host] = struct{}{} r.mu.Unlock() } return r } // BenchmarkDNSResolverLookupWithCache 测试缓存命中的性能。 // // 该基准测试测量在缓存已预热的情况下,LookupHostWithCache 方法的性能。 // 所有查询都应该命中缓存,不涉及 DNS 查询。 // // 预期结果: // - 低延迟(< 1μs) // - 零内存分配(sync.Map 读操作) // - 高并发安全 func BenchmarkDNSResolverLookupWithCache(b *testing.B) { r := createTestResolver() ctx := context.Background() hosts := []string{ "host0.example.com", "host25.example.com", "host50.example.com", "host75.example.com", } // 重置缓存命中统计 r.hits.Store(0) r.misses.Store(0) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { host := hosts[i%len(hosts)] _, err := r.LookupHostWithCache(ctx, host) if err != nil { b.Fatalf("LookupHostWithCache failed: %v", err) } i++ } }) // 验证所有请求都命中缓存 hits := r.GetCacheHits() if hits != int64(b.N) { b.Logf("缓存命中率: %d/%d (%.2f%%)", hits, b.N, float64(hits)*100/float64(b.N)) } } // BenchmarkDNSResolverConcurrent 测试并发解析同一主机时的锁竞争。 // // 该基准测试模拟高并发场景下多个 goroutine 同时访问同一 DNSCacheEntry 的情况。 // 主要测试 DNSCacheEntry.mu 读写锁的争用性能。 // // 关键指标: // - 锁等待时间 // - 并发吞吐量 // - 无死锁风险 func BenchmarkDNSResolverConcurrent(b *testing.B) { cfg := &config.ResolverConfig{ Enabled: true, Valid: 30 * time.Second, Timeout: 5 * time.Second, IPv4: true, } r := New(cfg).(*DNSResolver) // 只添加一个缓存条目,所有 goroutine 都访问同一个条目 targetHost := "concurrent.example.com" r.storeCache(targetHost, &DNSCacheEntry{ IPs: []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}, ExpiresAt: time.Now().Add(30 * time.Second), LastLookup: time.Now(), }) r.mu.Lock() r.refreshHosts[targetHost] = struct{}{} r.mu.Unlock() ctx := context.Background() b.ResetTimer() // 使用 RunParallel 模拟并发访问 b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := r.LookupHostWithCache(ctx, targetHost) if err != nil { b.Fatalf("LookupHostWithCache failed: %v", err) } } }) // 验证并发统计 hits := r.GetCacheHits() b.Logf("并发缓存命中次数: %d", hits) } // BenchmarkDNSResolverCacheExpiry 测试缓存过期后的刷新性能。 // // 该基准测试模拟缓存条目过期后,重新解析的场景。 // 使用 IP 地址直接绕过 DNS 查询,专注于测试过期逻辑的性能。 func BenchmarkDNSResolverCacheExpiry(b *testing.B) { cfg := &config.ResolverConfig{ Enabled: true, Valid: 1 * time.Millisecond, // 极短的 TTL 以便快速过期 Timeout: 5 * time.Second, IPv4: true, } r := New(cfg).(*DNSResolver) ctx := context.Background() b.ResetTimer() b.ReportAllocs() for b.Loop() { // 使用 IP 地址(会被直接返回,不涉及 DNS 查询) // 这样可以测试过期逻辑而不依赖网络 host := "127.0.0.1" // 预存储一个已过期的条目 r.storeCache(host, &DNSCacheEntry{ IPs: []string{"192.168.1.1"}, ExpiresAt: time.Now().Add(-1 * time.Second), // 已过期 LastLookup: time.Now().Add(-2 * time.Second), }) // 查询 IP 地址应该直接返回,不会触发缓存过期逻辑 // 这样可以测试缓存过期检测的性能 _, _ = r.LookupHostWithCache(ctx, host) } } // BenchmarkDNSResolverCacheWriteLock 测试缓存写入时的锁竞争。 // // 该基准测试模拟多个 goroutine 同时写入缓存的场景, // 测试 sync.Map 的 Store 操作性能。 func BenchmarkDNSResolverCacheWriteLock(b *testing.B) { cfg := &config.ResolverConfig{ Enabled: true, Valid: 30 * time.Second, Timeout: 5 * time.Second, IPv4: true, } r := New(cfg).(*DNSResolver) ctx := context.Background() // 使用 IP 地址避免 DNS 查询,专注于缓存写入性能 b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { host := fmt.Sprintf("127.0.0.%d", (i%254)+1) _, _ = r.LookupHostWithCache(ctx, host) i++ } }) } // BenchmarkDNSResolverMixedWorkload 测试混合读写负载。 // // 模拟真实场景:80% 缓存命中,20% 新条目写入。 func BenchmarkDNSResolverMixedWorkload(b *testing.B) { cfg := &config.ResolverConfig{ Enabled: true, Valid: 30 * time.Second, Timeout: 5 * time.Second, IPv4: true, } r := New(cfg).(*DNSResolver) ctx := context.Background() // 预填充一些缓存 for i := range 50 { host := fmt.Sprintf("cached%d.example.com", i) r.storeCache(host, &DNSCacheEntry{ IPs: []string{fmt.Sprintf("192.168.1.%d", i%256)}, ExpiresAt: time.Now().Add(30 * time.Second), }) r.mu.Lock() r.refreshHosts[host] = struct{}{} r.mu.Unlock() } b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { // 80% 概率访问已缓存的条目 if i%5 != 0 { host := fmt.Sprintf("cached%d.example.com", i%50) _, _ = r.LookupHostWithCache(ctx, host) } else { // 20% 概率访问新条目(使用 IP 避免网络) host := fmt.Sprintf("127.0.0.%d", (i%254)+1) _, _ = r.LookupHostWithCache(ctx, host) } i++ } }) // 输出缓存统计 stats := r.Stats() b.Logf("缓存统计: 命中=%d, 未命中=%d, 命中率=%.2f%%", stats.CacheHits, stats.CacheMisses, r.GetHitRate()*100) } // BenchmarkDNSCacheEntryRLock 测试 DNSCacheEntry 读写锁的读性能。 // // 该基准测试直接测试 DNSCacheEntry 的 RLock 性能, // 这是 resolver 并发性能的关键路径。 func BenchmarkDNSCacheEntryRLock(b *testing.B) { entry := &DNSCacheEntry{ IPs: []string{"192.168.1.1", "192.168.1.2"}, ExpiresAt: time.Now().Add(30 * time.Second), LastLookup: time.Now(), } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { entry.mu.RLock() _ = entry.IPs _ = entry.ExpiresAt entry.mu.RUnlock() } }) } // BenchmarkDNSCacheEntryRWLock 测试 DNSCacheEntry 读写锁的写性能。 // // 该基准测试直接测试 DNSCacheEntry 的 Lock 性能。 func BenchmarkDNSCacheEntryRWLock(b *testing.B) { b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { entry := &DNSCacheEntry{ IPs: []string{fmt.Sprintf("192.168.1.%d", i%256)}, ExpiresAt: time.Now().Add(30 * time.Second), LastLookup: time.Now(), } entry.mu.Lock() entry.IPs = []string{fmt.Sprintf("10.0.0.%d", i%256)} entry.LastLookup = time.Now() entry.mu.Unlock() i++ } }) }