lolly/internal/proxy/proxy_bench_test.go

621 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package proxy 提供反向代理性能的基准测试。
package proxy
import (
"fmt"
"sync/atomic"
"testing"
"time"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/loadbalance"
"rua.plus/lolly/internal/testutil"
"rua.plus/lolly/internal/variable"
)
// setupMockBackend 设置一个模拟后端服务器用于基准测试。
// 返回监听器地址和清理函数。
func setupMockBackend(body []byte) (string, func()) {
ln := fasthttputil.NewInmemoryListener()
server := &fasthttp.Server{
Handler: func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK)
_, _ = ctx.Write(body)
},
}
go func() {
_ = server.Serve(ln)
}()
// 等待服务器启动
time.Sleep(5 * time.Millisecond)
addr := ln.Addr().String()
cleanup := func() {
_ = ln.Close()
}
return addr, cleanup
}
// BenchmarkProxyForward 基准测试代理转发性能。
func BenchmarkProxyForward(b *testing.B) {
testCases := []struct {
name string
concurrency int
}{
{"concurrency1", 1},
{"concurrency10", 10},
{"concurrency100", 100},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
addr, cleanup := setupMockBackend([]byte("Hello, World!"))
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
})
}
}
// BenchmarkProxyForwardSmallRequest 基准测试小请求/小响应代理转发。
func BenchmarkProxyForwardSmallRequest(b *testing.B) {
smallBody := make([]byte, 100)
for i := range smallBody {
smallBody[i] = byte('a' + i%26)
}
addr, cleanup := setupMockBackend(smallBody)
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.SetBodyString(string(smallBody))
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyForwardLargeRequest 基准测试大请求/大响应代理转发。
func BenchmarkProxyForwardLargeRequest(b *testing.B) {
// 1KB 请求10KB 响应
requestBody := make([]byte, 1024)
for i := range requestBody {
requestBody[i] = byte('a' + i%26)
}
responseBody := make([]byte, 10*1024)
for i := range responseBody {
responseBody[i] = byte('A' + i%26)
}
addr, cleanup := setupMockBackend(responseBody)
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.SetBody(requestBody)
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyForwardMultipleTargets 基准测试多目标代理转发。
func BenchmarkProxyForwardMultipleTargets(b *testing.B) {
smallBody := []byte("OK")
numTargets := 5
targets := make([]*loadbalance.Target, numTargets)
cleanups := make([]func(), numTargets)
for i := range numTargets {
addr, cleanup := setupMockBackend(smallBody)
cleanups[i] = cleanup
targets[i] = &loadbalance.Target{
URL: "http://" + addr,
Weight: i + 1,
}
targets[i].Healthy.Store(true)
}
defer func() {
for _, cleanup := range cleanups {
cleanup()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "weighted_round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyHostClient 基准测试 HostClient 性能。
func BenchmarkProxyHostClient(b *testing.B) {
smallBody := []byte("Hello")
addr, cleanup := setupMockBackend(smallBody)
defer cleanup()
timeout := config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
}
client := createHostClient("http://"+addr, timeout, nil, nil, "", nil)
b.ResetTimer()
for b.Loop() {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
req.SetRequestURI("http://" + addr + "/api/test")
req.Header.SetMethod(fasthttp.MethodGet)
_ = client.Do(req, resp)
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}
}
// BenchmarkProxyHostClientParallel 基准测试 HostClient 并行性能。
func BenchmarkProxyHostClientParallel(b *testing.B) {
smallBody := []byte("Hello")
addr, cleanup := setupMockBackend(smallBody)
defer cleanup()
timeout := config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
}
client := createHostClient("http://"+addr, timeout, nil, nil, "", nil)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
req.SetRequestURI("http://" + addr + "/api/test")
req.Header.SetMethod(fasthttp.MethodGet)
_ = client.Do(req, resp)
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}
})
}
// BenchmarkProxyWithMockBackend 基准测试使用 mock_backend 工具的代理转发。
func BenchmarkProxyWithMockBackend(b *testing.B) {
// 使用 testutil 包启动 mock 后端
addr, cleanup := testutil.SimpleMockBackend(fasthttp.StatusOK, []byte("Hello from mock backend"))
defer cleanup()
// 等待服务器完全启动
time.Sleep(10 * time.Millisecond)
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyLoadBalancerSelection 基准测试代理负载均衡器选择性能。
func BenchmarkProxyLoadBalancerSelection(b *testing.B) {
testCases := []struct {
name string
loadBalance string
targetCount int
}{
{"round_robin_3", "round_robin", 3},
{"round_robin_50", "round_robin", 50},
{"weighted_round_robin_3", "weighted_round_robin", 3},
{"least_conn_3", "least_conn", 3},
{"ip_hash_3", "ip_hash", 3},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := make([]*loadbalance.Target, tc.targetCount)
for i := 0; i < tc.targetCount; i++ {
targets[i] = &loadbalance.Target{
URL: fmt.Sprintf("http://backend%d:8080", i),
Weight: i + 1,
}
targets[i].Healthy.Store(true)
}
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: tc.loadBalance,
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
counter := uint64(0)
// 每个 goroutine 使用独立的上下文
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
for pb.Next() {
idx := atomic.AddUint64(&counter, 1)
ctx.Request.Header.Set("X-Forwarded-For", fmt.Sprintf("192.168.1.%d", idx%255))
p.selectTarget(ctx)
}
})
})
}
}
// BenchmarkProxyHeaderProcessing 基准测试代理请求头处理性能。
func BenchmarkProxyHeaderProcessing(b *testing.B) {
target := &loadbalance.Target{URL: "http://localhost:8080"}
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
Headers: config.ProxyHeaders{
SetRequest: map[string]string{
"X-Custom-Header": "custom-value",
"X-Another": "another-value",
},
Remove: []string{"X-Remove-Me"},
},
}
targets := []*loadbalance.Target{
target,
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100")
ctx.Request.Header.Set("X-Remove-Me", "should-be-removed")
p.modifyRequestHeaders(ctx, target)
}
})
}
// BenchmarkBuildCacheKeyHash 基准测试缓存键哈希计算性能。
func BenchmarkBuildCacheKeyHash(b *testing.B) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test?query=1")
p, err := NewProxy(&config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}, []*loadbalance.Target{{URL: "http://localhost:8080"}}, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.Run("buildCacheKeyHash_with_string", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
hashKey, _ := p.buildCacheKeyHash(ctx)
_ = hashKey
}
})
b.Run("buildCacheKeyHashValue_direct", func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
hashKey := p.buildCacheKeyHashValue(ctx)
_ = hashKey
}
})
}
// BenchmarkProxyObjectPoolGetRelease 基准测试 proxy 中对象池的获取/释放效果。
// 验证 UpstreamTiming 和变量上下文的池复用性能,对比有无池化的差异。
func BenchmarkProxyObjectPoolGetRelease(b *testing.B) {
addr, cleanup := setupMockBackend([]byte("OK"))
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{{URL: "http://" + addr}}
targets[0].Healthy.Store(true)
_, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
// 本测试聚焦上游计时器和变量上下文池化效果Proxy 仅用于初始化验证
b.Run("UpstreamTiming_Pooled", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
timing := NewUpstreamTiming()
timing.MarkConnectStart()
time.Sleep(time.Microsecond)
timing.MarkConnectEnd()
timing.MarkHeaderReceived()
timing.MarkResponseEnd()
_ = timing.GetConnectTime()
_ = timing.GetHeaderTime()
_ = timing.GetResponseTime()
}
})
b.Run("VariableContext_Pooled", func(b *testing.B) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.1")
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
vc := variable.NewContext(ctx)
vc.Set("key", "value")
_ = vc.Expand("$key")
variable.ReleaseContext(vc)
}
})
}
// BenchmarkProxyResponsePoolParallel 基准测试并行场景下的响应池获取/释放性能。
// 验证 fasthttp.Request/Response 对象池在并发代理请求中的表现。
func BenchmarkProxyResponsePoolParallel(b *testing.B) {
addr, cleanup := setupMockBackend([]byte("parallel response"))
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{{URL: "http://" + addr}}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyZeroAllocPath 基准测试零分配路径性能。
// 验证 buildCacheKeyHashValue 的零分配优化效果,
// 对比旧的字符串构建方式与直接哈希写入的差异。
func BenchmarkProxyZeroAllocPath(b *testing.B) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test?query=value&foo=bar")
p, err := NewProxy(&config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}, []*loadbalance.Target{{URL: "http://localhost:8080"}}, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.Run("ZeroAlloc_buildCacheKeyHashValue", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
hash := p.buildCacheKeyHashValue(ctx)
_ = hash
}
})
b.Run("WithAlloc_buildCacheKeyHash", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
hash, key := p.buildCacheKeyHash(ctx)
_ = hash
_ = key
}
})
b.Run("ForwardedHeaders_ExtractSet", func(b *testing.B) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.Set("X-Forwarded-For", "10.0.0.1")
ctx.Request.Header.Set("X-Real-IP", "10.0.0.2")
ctx.Request.Header.Set("Forwarded", "for=10.0.0.3")
ctx.Request.Header.Set("X-Forwarded-Proto", "https")
ctx.Request.Header.Set("X-Forwarded-Host", "example.com")
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
fh := ExtractForwardedHeaders(ctx)
_ = fh
}
})
}