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:
parent
322573b9aa
commit
d1da187acc
308
internal/http3/adapter_bench_test.go
Normal file
308
internal/http3/adapter_bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
252
internal/logging/logging_bench_test.go
Normal file
252
internal/logging/logging_bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
168
internal/netutil/netutil_bench_test.go
Normal file
168
internal/netutil/netutil_bench_test.go
Normal 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
|
||||
335
internal/resolver/resolver_bench_test.go
Normal file
335
internal/resolver/resolver_bench_test.go
Normal 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++
|
||||
}
|
||||
})
|
||||
}
|
||||
450
internal/stream/stream_bench_test.go
Normal file
450
internal/stream/stream_bench_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user