// Package proxy 提供反向代理性能的基准测试。 package proxy import ( "fmt" "sync/atomic" "testing" "time" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttputil" "rua.plus/lolly/internal/benchmark/tools" "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/loadbalance" "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 := 0; i < numTargets; i++ { 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) 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) 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) { // 使用 tools 包启动 mock 后端 addr, cleanup := tools.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 } }) }