// Package integration 提供后端故障切换 E2E 基准测试。 // // 该文件测试负载均衡器剔除/恢复后端的开销。 // // 测试场景: // - 健康后端正常选择 // - 后端标记不健康后的剔除开销 // - 后端重新标记健康后的恢复开销 // // 作者:xfy package integration import ( "strconv" "sync/atomic" "testing" "time" "github.com/valyala/fasthttp" "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/loadbalance" "rua.plus/lolly/internal/proxy" ) // setupFailoverBackends 创建多后端用于故障切换测试。 // // 参数: // - count: 后端数量 // - healthyCount: 初始健康后端数量 // // 返回值: // - targets: 目标列表 // - cleanups: 清理函数列表 func setupFailoverBackends(b *testing.B, count, healthyCount int) ([]*loadbalance.Target, []func()) { b.Helper() targets := make([]*loadbalance.Target, count) cleanups := make([]func(), count) for i := range count { addr, cleanup := setupNetworkBackend(b, fasthttp.StatusOK, []byte(`{"backend":`+strconv.Itoa(i)+`}`)) cleanups[i] = cleanup targets[i] = &loadbalance.Target{ URL: "http://" + addr, Weight: 1, } // 设置健康状态 if i < healthyCount { targets[i].Healthy.Store(true) } else { targets[i].Healthy.Store(false) } } return targets, cleanups } // BenchmarkE2EFailover_NormalSelect 测试健康后端正常选择。 // // 所有后端健康时的负载均衡选择开销。 func BenchmarkE2EFailover_NormalSelect(b *testing.B) { targets, cleanups := setupFailoverBackends(b, 5, 5) defer func() { for _, c := range cleanups { c() } }() cfg := &config.ProxyConfig{ Path: "/api", LoadBalance: "round_robin", Timeout: config.ProxyTimeout{ Connect: 5 * time.Second, Read: 30 * time.Second, Write: 30 * time.Second, }, } p, err := proxy.NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } warmupProxy(p, "/api/test", 10) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { ctx := &fasthttp.RequestCtx{} ctx.Request.SetRequestURI("/api/test") ctx.Request.Header.SetMethod(fasthttp.MethodGet) p.ServeHTTP(ctx) } }) } // BenchmarkE2EFailover_OneUnhealthy 测试一个后端不健康。 // // 4/5 后端健康时的选择开销。 func BenchmarkE2EFailover_OneUnhealthy(b *testing.B) { targets, cleanups := setupFailoverBackends(b, 5, 4) defer func() { for _, c := range cleanups { c() } }() cfg := &config.ProxyConfig{ Path: "/api", LoadBalance: "round_robin", Timeout: config.ProxyTimeout{ Connect: 5 * time.Second, Read: 30 * time.Second, Write: 30 * time.Second, }, } p, err := proxy.NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } warmupProxy(p, "/api/test", 10) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { ctx := &fasthttp.RequestCtx{} ctx.Request.SetRequestURI("/api/test") ctx.Request.Header.SetMethod(fasthttp.MethodGet) p.ServeHTTP(ctx) } }) } // BenchmarkE2EFailover_MostUnhealthy 测试多数后端不健康。 // // 1/5 后端健康时的选择开销(剔除开销增大)。 func BenchmarkE2EFailover_MostUnhealthy(b *testing.B) { targets, cleanups := setupFailoverBackends(b, 5, 1) defer func() { for _, c := range cleanups { c() } }() cfg := &config.ProxyConfig{ Path: "/api", LoadBalance: "round_robin", Timeout: config.ProxyTimeout{ Connect: 5 * time.Second, Read: 30 * time.Second, Write: 30 * time.Second, }, } p, err := proxy.NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } warmupProxy(p, "/api/test", 10) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { ctx := &fasthttp.RequestCtx{} ctx.Request.SetRequestURI("/api/test") ctx.Request.Header.SetMethod(fasthttp.MethodGet) p.ServeHTTP(ctx) } }) } // BenchmarkE2EFailover_DynamicToggle 测试动态健康状态切换。 // // 模拟后端健康状态在测试中变化。 func BenchmarkE2EFailover_DynamicToggle(b *testing.B) { targets, cleanups := setupFailoverBackends(b, 3, 3) defer func() { for _, c := range cleanups { c() } }() cfg := &config.ProxyConfig{ Path: "/api", LoadBalance: "round_robin", Timeout: config.ProxyTimeout{ Connect: 5 * time.Second, Read: 30 * time.Second, Write: 30 * time.Second, }, } p, err := proxy.NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } warmupProxy(p, "/api/test", 10) var toggleCounter atomic.Uint64 b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { // 每 100 次请求切换一个后端的健康状态 count := toggleCounter.Add(1) if count%100 == 0 { targetIdx := int(count/100) % len(targets) current := targets[targetIdx].Healthy.Load() targets[targetIdx].Healthy.Store(!current) } ctx := &fasthttp.RequestCtx{} ctx.Request.SetRequestURI("/api/test") ctx.Request.Header.SetMethod(fasthttp.MethodGet) p.ServeHTTP(ctx) } }) } // BenchmarkE2EFailover_AllUnhealthy 测试所有后端不健康。 // // 无可用后端时的选择开销(应该返回错误)。 func BenchmarkE2EFailover_AllUnhealthy(b *testing.B) { targets, cleanups := setupFailoverBackends(b, 3, 0) defer func() { for _, c := range cleanups { c() } }() cfg := &config.ProxyConfig{ Path: "/api", LoadBalance: "round_robin", Timeout: config.ProxyTimeout{ Connect: 5 * time.Second, Read: 30 * time.Second, Write: 30 * time.Second, }, } p, err := proxy.NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } b.ReportAllocs() b.ResetTimer() for b.Loop() { ctx := &fasthttp.RequestCtx{} ctx.Request.SetRequestURI("/api/test") ctx.Request.Header.SetMethod(fasthttp.MethodGet) p.ServeHTTP(ctx) } } // BenchmarkE2EFailover_SelectOnly 测试纯选择开销(无实际请求)。 // // 验证负载均衡器选择逻辑的分配。 func BenchmarkE2EFailover_SelectOnly(b *testing.B) { targets := make([]*loadbalance.Target, 5) for i := range 5 { targets[i] = &loadbalance.Target{ URL: "http://backend" + strconv.Itoa(i) + ":8080", Weight: 1, } targets[i].Healthy.Store(true) } rr := loadbalance.NewRoundRobin() b.ReportAllocs() b.ResetTimer() for b.Loop() { _ = rr.Select(targets) } }