test(http3,logging,netutil,resolver,stream): 添加性能基准测试

为核心模块添加 benchmark 测试:
- http3: Handler 包装、请求/响应转换、Body 读取
- logging: JSON/模板访问日志、变量展开
- netutil: TCPKeepAlive 配置解析
- resolver: DNS 解析性能
- stream: 健康过滤、UDP 会话、负载均衡

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-14 10:49:38 +08:00
parent 322573b9aa
commit d1da187acc
5 changed files with 1513 additions and 0 deletions

View File

@ -0,0 +1,308 @@
// Package http3 提供 HTTP/3 适配器的基准测试。
//
// 该文件测试 HTTP/3 适配器的性能,包括:
// - Handler 包装开销
// - HTTP 请求到 fasthttp 请求的转换性能
// - 不同大小 Body 的读取性能
// - fasthttp 响应到 HTTP 响应的转换性能
// - RequestCtx sync.Pool 复用效率
//
// 作者xfy
package http3
import (
"bytes"
"io"
"net/http"
"net/url"
"testing"
"github.com/valyala/fasthttp"
)
// BenchmarkAdapterWrap 测试 Handler 包装开销
//
// 该基准测试测量将 fasthttp.RequestHandler 包装为 http.Handler 的基本开销,
// 包括 ctxPool 获取和放回操作。
func BenchmarkAdapterWrap(b *testing.B) {
adapter := NewAdapter()
// 简单的 fasthttp handler
handler := func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(200)
ctx.SetBodyString("OK")
}
httpHandler := adapter.Wrap(handler)
// 创建请求
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Header: http.Header{},
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
rw := &mockResponseWriter{}
httpHandler.ServeHTTP(rw, req)
}
}
// BenchmarkAdapterConvertRequest 测试 HTTP -> fasthttp 头部转换性能
//
// 该基准测试测量 convertRequest 方法在仅有头部转换时的性能,
// 不包含 Body 读取的开销。
func BenchmarkAdapterConvertRequest(b *testing.B) {
adapter := NewAdapter()
// 创建包含多个头部的请求
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/api/v1/users", RawQuery: "page=1&limit=10"},
Host: "api.example.com",
Header: http.Header{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"Authorization": []string{"Bearer token123456"},
"X-Request-ID": []string{"req-123456789"},
"X-User-Agent": []string{"TestClient/1.0"},
"Accept-Encoding": []string{"gzip, deflate"},
"Cache-Control": []string{"no-cache"},
"Connection": []string{"keep-alive"},
},
RemoteAddr: "192.168.1.100:12345",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ctx.Request.Reset()
adapter.convertRequest(req, ctx)
}
}
// BenchmarkAdapterConvertRequestBody_1KB 测试 1KB Body 读取性能
//
// 该基准测试测量 io.ReadAll 在 1KB Body 大小下的开销。
func BenchmarkAdapterConvertRequestBody_1KB(b *testing.B) {
benchmarkAdapterConvertRequestBody(b, 1024)
}
// BenchmarkAdapterConvertRequestBody_10KB 测试 10KB Body 读取性能
//
// 该基准测试测量 io.ReadAll 在 10KB Body 大小下的开销。
func BenchmarkAdapterConvertRequestBody_10KB(b *testing.B) {
benchmarkAdapterConvertRequestBody(b, 10*1024)
}
// BenchmarkAdapterConvertRequestBody_100KB 测试 100KB Body 读取性能
//
// 该基准测试测量 io.ReadAll 在 100KB Body 大小下的开销。
func BenchmarkAdapterConvertRequestBody_100KB(b *testing.B) {
benchmarkAdapterConvertRequestBody(b, 100*1024)
}
// benchmarkAdapterConvertRequestBody 是 Body 读取性能的通用基准测试函数
//
// 参数:
// - b: 测试对象
// - bodySize: Body 大小(字节)
func benchmarkAdapterConvertRequestBody(b *testing.B, bodySize int) {
adapter := NewAdapter()
bodyData := make([]byte, bodySize)
for i := range bodyData {
bodyData[i] = byte('a' + (i % 26))
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
b.StopTimer()
// 每次迭代创建新的 Body模拟新的请求
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/upload"},
Host: "localhost",
Header: http.Header{
"Content-Type": []string{"application/octet-stream"},
},
Body: io.NopCloser(bytes.NewReader(bodyData)),
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
b.StartTimer()
adapter.convertRequest(req, ctx)
}
}
// BenchmarkAdapterConvertResponse 测试 fasthttp -> HTTP 响应转换性能
//
// 该基准测试测量 convertResponse 方法的性能,包括:
// - 状态码提取
// - 响应头部复制
// - 响应体写入
func BenchmarkAdapterConvertResponse(b *testing.B) {
adapter := NewAdapter()
// 预创建响应内容
responseBody := []byte(`{"status":"success","data":{"id":12345,"name":"test","items":[1,2,3,4,5]}}`)
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
ctx.SetStatusCode(200)
ctx.Response.Header.Set("Content-Type", "application/json")
ctx.Response.Header.Set("X-Response-ID", "resp-123456")
ctx.Response.Header.Set("Cache-Control", "no-store")
ctx.SetBody(responseBody)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
b.StopTimer()
rw := &mockResponseWriter{}
b.StartTimer()
adapter.convertResponse(ctx, rw)
}
}
// BenchmarkAdapterCtxPool 测试 RequestCtx sync.Pool 复用效率
//
// 该基准测试比较使用 sync.Pool 复用 RequestCtx 与每次创建新对象的性能差异。
func BenchmarkAdapterCtxPool(b *testing.B) {
b.Run("WithPool", func(b *testing.B) {
adapter := NewAdapter()
handler := func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(200)
}
httpHandler := adapter.Wrap(handler)
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/"},
Host: "localhost",
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
rw := &mockResponseWriter{}
httpHandler.ServeHTTP(rw, req)
}
})
b.Run("WithoutPool", func(b *testing.B) {
handler := func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(200)
}
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/"},
Host: "localhost",
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
rw := &mockResponseWriter{}
// 每次创建新的 ctx不使用 Pool
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
ctx.Request.Header.SetMethod(req.Method)
ctx.Request.SetRequestURI(req.URL.Path)
ctx.Request.Header.SetHost(req.Host)
handler(ctx)
// 模拟响应写入
if ctx.Response.StatusCode() == 0 {
rw.status = 200
} else {
rw.status = ctx.Response.StatusCode()
}
}
})
}
// BenchmarkAdapterConvertRequest_Parallel 测试并发环境下的请求转换性能
//
// 该基准测试使用 b.RunParallel 模拟并发请求场景。
func BenchmarkAdapterConvertRequest_Parallel(b *testing.B) {
adapter := NewAdapter()
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/api/data", RawQuery: "key=value"},
Host: "localhost",
Header: http.Header{
"Content-Type": []string{"application/json"},
"X-Request-ID": []string{"parallel-test"},
},
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
}
})
}
// BenchmarkAdapterFullRoundTrip 测试完整的请求-响应往返性能
//
// 该基准测试模拟真实的 HTTP/3 请求处理流程,包括:
// - 请求转换
// - fasthttp handler 执行
// - 响应转换
func BenchmarkAdapterFullRoundTrip(b *testing.B) {
adapter := NewAdapter()
// 模拟真实的 API handler
apiHandler := func(ctx *fasthttp.RequestCtx) {
// 模拟一些业务逻辑
method := string(ctx.Method())
path := string(ctx.Path())
if method == "GET" && string(path) == "/api/users" {
ctx.SetStatusCode(200)
ctx.Response.Header.Set("Content-Type", "application/json")
ctx.SetBodyString(`{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]}`)
} else {
ctx.SetStatusCode(404)
ctx.SetBodyString(`{"error":"not found"}`)
}
}
httpHandler := adapter.Wrap(apiHandler)
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/api/users"},
Host: "api.example.com",
Header: http.Header{
"Accept": []string{"application/json"},
},
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
rw := &mockResponseWriter{}
httpHandler.ServeHTTP(rw, req)
}
}

View File

@ -0,0 +1,252 @@
// Package logging 提供日志功能的性能测试。
//
// 该文件包含日志相关的基准测试,用于评估关键操作的性能:
// - JSON 格式访问日志记录
// - 模板格式访问日志记录
// - 变量展开开销
// - 日志级别解析
//
// 主要用途:
//
// 用于监控日志系统的性能特征,优化内存分配和执行时间。
//
// 注意事项:
// - 使用 -benchmem 标志查看内存分配统计
// - 测试使用模拟数据避免外部依赖
//
// 作者xfy
package logging
import (
"testing"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
)
// BenchmarkLoggerLogAccessJSON 测试 JSON 格式访问日志记录性能。
// 这是最常见的访问日志格式,使用 zerolog 直接输出结构化数据。
// 预期结果:极低内存分配(< 1 allocs/op高性能。
func BenchmarkLoggerLogAccessJSON(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "json",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/users?id=123")
ctx.Request.Header.SetMethod("GET")
ctx.Request.Header.Set("User-Agent", "benchmark-agent/1.0")
ctx.Request.Header.Set("Referer", "http://example.com/")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
logger.LogAccess(ctx, 200, 1024, 150*time.Millisecond)
}
}
// BenchmarkLoggerLogAccessTemplate 测试模板格式访问日志记录性能。
// 重点测试变量展开和字符串处理的开销,通常会有更多内存分配。
// 使用 Nginx 风格的日志格式模板。
func BenchmarkLoggerLogAccessTemplate(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_time",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/users?id=123")
ctx.Request.Header.SetMethod("GET")
ctx.Request.Header.Set("User-Agent", "benchmark-agent/1.0")
ctx.Request.Header.Set("Referer", "http://example.com/")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
logger.LogAccess(ctx, 200, 1024, 150*time.Millisecond)
}
}
// BenchmarkLoggerLogAccessSimpleTemplate 测试简单模板的访问日志记录性能。
// 相比复杂模板,变量数量更少,字符串拼接开销更低。
func BenchmarkLoggerLogAccessSimpleTemplate(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "$remote_addr $request $status $body_bytes_sent",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.SetMethod("POST")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
logger.LogAccess(ctx, 201, 512, 50*time.Millisecond)
}
}
// BenchmarkFormatAccessLog 直接测试 formatAccessLog 函数性能。
// 隔离变量展开的开销,不经过日志输出层。
func BenchmarkFormatAccessLog(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_time",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/v1/resources?name=test&limit=10")
ctx.Request.Header.SetMethod("PUT")
ctx.Request.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Benchmark/1.0)")
ctx.Request.Header.Set("Referer", "https://example.com/dashboard")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = logger.formatAccessLog(ctx, 200, 2048, 250*time.Microsecond)
}
}
// BenchmarkFormatAccessLogMinimal 测试最小模板的 formatAccessLog 性能。
// 用于评估变量系统的基准开销。
func BenchmarkFormatAccessLogMinimal(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "$status",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/ping")
ctx.Request.Header.SetMethod("GET")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = logger.formatAccessLog(ctx, 200, 4, 1*time.Microsecond)
}
}
// BenchmarkParseLevel 测试日志级别解析性能。
// 在初始化时调用,预期极快且零分配。
func BenchmarkParseLevel(b *testing.B) {
levels := []string{"debug", "info", "warn", "error", "DEBUG", "INFO", "WARN", "ERROR", "unknown", ""}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = parseLevel(levels[i%len(levels)])
}
}
// BenchmarkParseLevelLowercase 测试小写日志级别解析性能。
// 最常见的输入场景,应该是最快路径。
func BenchmarkParseLevelLowercase(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = parseLevel("info")
}
}
// BenchmarkParseLevelUppercase 测试大写日志级别解析性能。
// 测试 strings.ToLower 的开销。
func BenchmarkParseLevelUppercase(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = parseLevel("INFO")
}
}
// BenchmarkLoggerLogAccessWithUser 测试带有用户认证的访问日志性能。
// 额外的 ctx.UserValue 查找会增加开销。
func BenchmarkLoggerLogAccessWithUser(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/admin/dashboard")
ctx.Request.Header.SetMethod("GET")
ctx.SetUserValue("remote_user", "admin_user_12345")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
logger.LogAccess(ctx, 200, 1024, 100*time.Millisecond)
}
}
// BenchmarkLoggerLogAccessEmptyFormat 测试空格式(默认 JSON的访问日志性能。
// 验证空字符串回退到 JSON 的性能。
func BenchmarkLoggerLogAccessEmptyFormat(b *testing.B) {
logger := New(&config.LoggingConfig{
Access: config.AccessLogConfig{
Path: "stdout",
Format: "",
},
Error: config.ErrorLogConfig{
Path: "stdout",
Level: "info",
},
})
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/health")
ctx.Request.Header.SetMethod("GET")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
logger.LogAccess(ctx, 200, 2, 5*time.Microsecond)
}
}

View File

@ -0,0 +1,168 @@
// Package netutil 提供网络工具功能的基准测试。
//
// 该文件测试网络工具模块的性能,包括:
// - 客户端 IP 提取性能
// - IP 解析为 net.IP 性能
// - 端口移除性能
// - 端口检查性能
//
// 作者xfy
package netutil
import (
"net"
"testing"
"github.com/valyala/fasthttp"
)
// createBenchCtx 创建用于基准测试的 fasthttp 上下文。
func createBenchCtx() *fasthttp.RequestCtx {
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
return ctx
}
// BenchmarkExtractClientIP 测试 ExtractClientIP 函数的性能。
// 覆盖不同场景X-Forwarded-For 单 IP、多 IP、X-Real-IP、RemoteAddr 回退。
func BenchmarkExtractClientIP(b *testing.B) {
b.Run("X-Forwarded-For single IP", func(b *testing.B) {
ctx := createBenchCtx()
ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100")
b.ResetTimer()
for i := 0; i < b.N; i++ {
ExtractClientIP(ctx)
}
})
b.Run("X-Forwarded-For multiple IPs", func(b *testing.B) {
ctx := createBenchCtx()
ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100, 10.0.0.1, 172.16.0.1")
b.ResetTimer()
for i := 0; i < b.N; i++ {
ExtractClientIP(ctx)
}
})
b.Run("X-Real-IP only", func(b *testing.B) {
ctx := createBenchCtx()
ctx.Request.Header.Set("X-Real-IP", "192.168.1.200")
b.ResetTimer()
for i := 0; i < b.N; i++ {
ExtractClientIP(ctx)
}
})
b.Run("RemoteAddr fallback", func(b *testing.B) {
ctx := createBenchCtx()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ExtractClientIP(ctx)
}
})
}
// BenchmarkExtractClientIPNet 测试 ExtractClientIPNet 函数的性能。
// 覆盖不同场景X-Forwarded-For、X-Real-IP、RemoteAddr 回退。
func BenchmarkExtractClientIPNet(b *testing.B) {
b.Run("X-Forwarded-For single IP", func(b *testing.B) {
ctx := createBenchCtx()
ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100")
b.ResetTimer()
for i := 0; i < b.N; i++ {
ExtractClientIPNet(ctx)
}
})
b.Run("X-Real-IP only", func(b *testing.B) {
ctx := createBenchCtx()
ctx.Request.Header.Set("X-Real-IP", "192.168.1.200")
b.ResetTimer()
for i := 0; i < b.N; i++ {
ExtractClientIPNet(ctx)
}
})
b.Run("RemoteAddr fallback", func(b *testing.B) {
ctx := createBenchCtx()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ExtractClientIPNet(ctx)
}
})
}
// BenchmarkStripPort 测试 StripPort 函数的性能。
// 覆盖不同场景IPv4 带端口、IPv6 带端口、无端口、空字符串。
func BenchmarkStripPort(b *testing.B) {
b.Run("IPv4 with port", func(b *testing.B) {
host := "example.com:8080"
b.ResetTimer()
for i := 0; i < b.N; i++ {
StripPort(host)
}
})
b.Run("IPv6 with port", func(b *testing.B) {
host := "[2001:db8::1]:8443"
b.ResetTimer()
for i := 0; i < b.N; i++ {
StripPort(host)
}
})
b.Run("no port", func(b *testing.B) {
host := "example.com"
b.ResetTimer()
for i := 0; i < b.N; i++ {
StripPort(host)
}
})
b.Run("empty string", func(b *testing.B) {
host := ""
b.ResetTimer()
for i := 0; i < b.N; i++ {
StripPort(host)
}
})
}
// BenchmarkHasPort 测试 HasPort 函数的性能。
// 覆盖不同场景IPv4 带端口、IPv6 带端口、无端口、空字符串。
func BenchmarkHasPort(b *testing.B) {
b.Run("IPv4 with port", func(b *testing.B) {
host := "example.com:8080"
b.ResetTimer()
for i := 0; i < b.N; i++ {
HasPort(host)
}
})
b.Run("IPv6 with port", func(b *testing.B) {
host := "[2001:db8::1]:443"
b.ResetTimer()
for i := 0; i < b.N; i++ {
HasPort(host)
}
})
b.Run("no port", func(b *testing.B) {
host := "example.com"
b.ResetTimer()
for i := 0; i < b.N; i++ {
HasPort(host)
}
})
b.Run("empty string", func(b *testing.B) {
host := ""
b.ResetTimer()
for i := 0; i < b.N; i++ {
HasPort(host)
}
})
}
// 确保 net 包被使用(避免未使用导入警告)
var _ = net.IPv4zero

View File

@ -0,0 +1,335 @@
// Package resolver 提供 DNS 解析器的基准测试。
//
// 该文件包含 DNS 解析器的性能基准测试,测试缓存命中、并发竞争和缓存过期场景。
//
// 作者xfy
package resolver
import (
"context"
"fmt"
"testing"
"time"
"rua.plus/lolly/internal/config"
)
// mockResolver 是一个模拟的 Resolver 实现,用于基准测试。
// 它返回固定的 IP 地址,避免网络依赖,确保测试的稳定性和可重复性。
type mockResolver struct {
ips []string
delay time.Duration
}
// LookupHost 模拟解析主机名,返回固定的 IP 地址列表。
func (m *mockResolver) LookupHost(ctx context.Context, host string) ([]string, error) {
if m.delay > 0 {
time.Sleep(m.delay)
}
return m.ips, nil
}
// LookupHostWithCache 带缓存的解析实现。
func (m *mockResolver) LookupHostWithCache(ctx context.Context, host string) ([]string, error) {
return m.LookupHost(ctx, host)
}
// Refresh 刷新指定主机的缓存(模拟实现)。
func (m *mockResolver) Refresh(host string) error {
return nil
}
// Start 启动后台刷新协程(模拟实现)。
func (m *mockResolver) Start() error {
return nil
}
// Stop 停止解析器(模拟实现)。
func (m *mockResolver) Stop() error {
return nil
}
// Stats 返回统计信息(模拟实现)。
func (m *mockResolver) Stats() Stats {
return Stats{}
}
// 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 := 0; i < 100; i++ {
host := fmt.Sprintf("host%d.example.com", i)
r.cache.Store(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.cache.Store(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 i := 0; i < b.N; i++ {
// 使用 IP 地址(会被直接返回,不涉及 DNS 查询)
// 这样可以测试过期逻辑而不依赖网络
host := fmt.Sprintf("127.0.0.%d", (i%254)+1)
// 预存储一个已过期的条目
r.cache.Store(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 := 0; i < 50; i++ {
host := fmt.Sprintf("cached%d.example.com", i)
r.cache.Store(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++
}
})
}

View File

@ -0,0 +1,450 @@
// Package stream 提供 TCP/UDP Stream 代理性能的基准测试。
//
// 该文件测试流代理模块的性能热点,包括:
// - filterHealthy 健康目标过滤slice 分配热点)
// - UDP 会话缓冲区分配
// - UDP 会话创建/获取(双重检查锁定)
// - 负载均衡算法性能
//
// 作者xfy
package stream
import (
"fmt"
"net"
"sync"
"sync/atomic"
"testing"
"time"
)
// BenchmarkStreamFilterHealthy 基准测试健康目标过滤性能。
//
// 热点:每次 Select 都会分配新的 healthy slice
// 在高并发场景下造成频繁的内存分配和 GC 压力。
func BenchmarkStreamFilterHealthy(b *testing.B) {
testCases := []struct {
name string
targetCount int
healthyRatio float64 // 健康目标比例
}{
{"3_healthy", 3, 1.0},
{"10_healthy_80", 10, 0.8},
{"50_healthy_50", 50, 0.5},
{"100_healthy_80", 100, 0.8},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
// 创建目标列表
targets := make([]*Target, tc.targetCount)
for i := 0; i < tc.targetCount; i++ {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: 1,
}
// 按比例设置健康状态
if float64(i) < float64(tc.targetCount)*tc.healthyRatio {
targets[i].healthy.Store(true)
}
}
balancer := newRoundRobin()
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = balancer.Select(targets)
}
})
})
}
}
// BenchmarkStreamFilterHealthyPreallocated 测试预分配 slice 的性能改进。
//
// 通过复用 slice 避免每次分配新的内存。
func BenchmarkStreamFilterHealthyPreallocated(b *testing.B) {
targetCount := 50
healthyRatio := 0.8
// 创建目标列表
targets := make([]*Target, targetCount)
for i := 0; i < targetCount; i++ {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: 1,
}
if float64(i) < float64(targetCount)*healthyRatio {
targets[i].healthy.Store(true)
}
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
// 复用预分配的 buffer
buffer := make([]*Target, 0, targetCount)
for pb.Next() {
// 清空 slice 但保留容量
buffer = buffer[:0]
for _, t := range targets {
if t.healthy.Load() {
buffer = append(buffer, t)
}
}
}
})
}
// BenchmarkUDPSessionAllocations 基准测试 UDP 会话缓冲区分配。
//
// 热点UDP 会话创建时分配 65KB 缓冲区make([]byte, 65535)
// 在高 QPS 场景下造成大量内存分配。
func BenchmarkUDPSessionAllocations(b *testing.B) {
testCases := []struct {
name string
bufSize int
poolSize int
}{
{"no_pool_65k", 65535, 0},
{"sync_pool_65k", 65535, 100},
{"no_pool_16k", 16384, 0},
{"sync_pool_16k", 16384, 100},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
var pool *sync.Pool
if tc.poolSize > 0 {
pool = &sync.Pool{
New: func() interface{} {
return make([]byte, tc.bufSize)
},
}
// 预填充 pool
for i := 0; i < tc.poolSize; i++ {
pool.Put(make([]byte, tc.bufSize))
}
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if pool != nil {
buf := pool.Get().([]byte)
// 模拟使用
_ = buf[0]
pool.Put(buf)
} else {
// 直接分配
_ = make([]byte, tc.bufSize)
}
}
})
}
}
// BenchmarkUDPSessionGetOrCreate 基准测试 UDP 会话创建/获取性能。
//
// 热点:双重检查锁定模式在获取会话时的锁竞争,
// 高并发下 RLock -> Lock -> RLock 的升级路径造成性能瓶颈。
func BenchmarkUDPSessionGetOrCreate(b *testing.B) {
// 创建 UDP 连接
udpAddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:0")
conn, _ := net.ListenUDP("udp", udpAddr)
defer conn.Close()
// 创建上游
upstream := &Upstream{
targets: []*Target{{addr: "127.0.0.1:19001"}},
balancer: newRoundRobin(),
}
upstream.targets[0].healthy.Store(true)
// 创建 UDP 服务器
srv := newUDPServer(conn, upstream, 1*time.Minute)
// 预创建一些客户端地址
clientAddrs := make([]*net.UDPAddr, 100)
for i := 0; i < 100; i++ {
clientAddrs[i], _ = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", 20000+i))
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
// 模拟交替使用已有会话和创建新会话
clientAddr := clientAddrs[i%len(clientAddrs)]
srv.getOrCreateSession(clientAddr)
i++
}
})
}
// BenchmarkUDPSessionGetOnly 基准测试纯获取会话性能。
//
// 测试已有会话的读取性能(只涉及 RLock
func BenchmarkUDPSessionGetOnly(b *testing.B) {
// 创建 UDP 连接
udpAddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:0")
conn, _ := net.ListenUDP("udp", udpAddr)
defer conn.Close()
// 创建上游
upstream := &Upstream{
targets: []*Target{{addr: "127.0.0.1:19002"}},
balancer: newRoundRobin(),
}
upstream.targets[0].healthy.Store(true)
// 创建 UDP 服务器
srv := newUDPServer(conn, upstream, 1*time.Minute)
// 预创建会话
clientAddrs := make([]*net.UDPAddr, 100)
for i := 0; i < 100; i++ {
clientAddrs[i], _ = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", 30000+i))
// 手动创建会话
targetAddr, _ := net.ResolveUDPAddr("udp", upstream.targets[0].addr)
targetConn, _ := net.DialUDP("udp", nil, targetAddr)
session := &udpSession{
clientAddr: clientAddrs[i],
targetConn: targetConn,
lastActive: time.Now(),
srv: srv,
}
srv.sessions[sessionKey(clientAddrs[i])] = session
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
clientAddr := clientAddrs[i%len(clientAddrs)]
srv.getSession(clientAddr)
i++
}
})
}
// BenchmarkStreamBalancerSelect 基准测试各种负载均衡算法。
//
// 测试不同算法在高并发下的选择性能。
func BenchmarkStreamBalancerSelect(b *testing.B) {
testCases := []struct {
name string
balancer string
targetCount int
}{
{"round_robin_3", "round_robin", 3},
{"round_robin_10", "round_robin", 10},
{"round_robin_50", "round_robin", 50},
{"weighted_round_robin_3", "weighted_round_robin", 3},
{"weighted_round_robin_10", "weighted_round_robin", 10},
{"least_conn_3", "least_conn", 3},
{"least_conn_10", "least_conn", 10},
{"ip_hash_3", "ip_hash", 3},
{"ip_hash_10", "ip_hash", 10},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
// 创建目标
targets := make([]*Target, tc.targetCount)
for i := 0; i < tc.targetCount; i++ {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: i + 1,
conns: int64(i * 10), // 模拟不同连接数
}
targets[i].healthy.Store(true)
}
// 创建均衡器
var balancer Balancer
switch tc.balancer {
case "round_robin":
balancer = newRoundRobin()
case "weighted_round_robin":
balancer = newWeightedRoundRobin()
case "least_conn":
balancer = newLeastConn()
case "ip_hash":
balancer = newIPHash()
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
counter := uint64(0)
for pb.Next() {
if tc.balancer == "ip_hash" {
// IP Hash 需要特定 IP
idx := atomic.AddUint64(&counter, 1)
_ = balancer.(*ipHash).SelectByIP(targets, fmt.Sprintf("192.168.1.%d", idx%255))
} else {
_ = balancer.Select(targets)
}
}
})
})
}
}
// BenchmarkStreamRoundRobinWithUnhealthy 基准测试轮询算法处理不健康目标。
//
// 测试当部分目标不健康时的过滤开销。
func BenchmarkStreamRoundRobinWithUnhealthy(b *testing.B) {
testCases := []struct {
name string
targetCount int
unhealthyCount int
}{
{"3_1_unhealthy", 3, 1},
{"10_3_unhealthy", 10, 3},
{"50_20_unhealthy", 50, 20},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := make([]*Target, tc.targetCount)
for i := 0; i < tc.targetCount; i++ {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: 1,
}
// 标记部分目标为不健康
targets[i].healthy.Store(i >= tc.unhealthyCount)
}
balancer := newRoundRobin()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = balancer.Select(targets)
}
})
}
}
// BenchmarkStreamLeastConnWithVaryingConns 基准测试最少连接算法。
//
// 测试不同连接数分布下的选择性能。
func BenchmarkStreamLeastConnWithVaryingConns(b *testing.B) {
testCases := []struct {
name string
connsDist []int64 // 每个目标的连接数分布
}{
{"uniform", []int64{10, 10, 10}},
{"varying", []int64{100, 50, 10}},
{"extreme", []int64{1000, 10, 1}},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := make([]*Target, len(tc.connsDist))
for i, conns := range tc.connsDist {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: 1,
conns: conns,
}
targets[i].healthy.Store(true)
}
balancer := newLeastConn()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = balancer.Select(targets)
}
})
}
}
// BenchmarkStreamWeightedRoundRobinDistribution 基准测试加权轮询分布。
//
// 测试加权轮询在不同权重分布下的选择性能。
func BenchmarkStreamWeightedRoundRobinDistribution(b *testing.B) {
testCases := []struct {
name string
weights []int
}{
{"equal", []int{1, 1, 1}},
{"linear", []int{1, 2, 3}},
{"heavy", []int{1, 1, 10}},
{"exponential", []int{1, 2, 4, 8}},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := make([]*Target, len(tc.weights))
for i, w := range tc.weights {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: w,
}
targets[i].healthy.Store(true)
}
balancer := newWeightedRoundRobin()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = balancer.Select(targets)
}
})
}
}
// BenchmarkStreamIPHashWithDifferentIPs 基准测试 IP Hash 算法。
//
// 测试不同 IP 数量下的哈希性能。
func BenchmarkStreamIPHashWithDifferentIPs(b *testing.B) {
testCases := []struct {
name string
ipCount int
targetCount int
}{
{"10_ips_3_targets", 10, 3},
{"100_ips_3_targets", 100, 3},
{"1000_ips_3_targets", 1000, 3},
{"100_ips_10_targets", 100, 10},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := make([]*Target, tc.targetCount)
for i := 0; i < tc.targetCount; i++ {
targets[i] = &Target{
addr: fmt.Sprintf("backend%d:8080", i),
weight: 1,
}
targets[i].healthy.Store(true)
}
// 预生成 IP 列表
ips := make([]string, tc.ipCount)
for i := 0; i < tc.ipCount; i++ {
ips[i] = fmt.Sprintf("192.168.%d.%d", i/256, i%256)
}
balancer := newIPHash().(*ipHash)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ip := ips[i%tc.ipCount]
_ = balancer.SelectByIP(targets, ip)
}
})
}
}