621 lines
15 KiB
Go
621 lines
15 KiB
Go
// 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
|
||
}
|
||
})
|
||
}
|