From 017bb13295c33af88791a6ae22931a14db2f9e0d Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:18:52 +0800 Subject: [PATCH] perf: reuse reverse proxy candidate slices --- reverseproxy.go | 7 ++++ reverseproxy_benchmark_test.go | 65 +++++++++++++++++++++++++++++++--- reverseproxy_lb.go | 22 +++++++++--- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/reverseproxy.go b/reverseproxy.go index 1d9c07d..5b178d5 100644 --- a/reverseproxy.go +++ b/reverseproxy.go @@ -91,6 +91,13 @@ var reverseProxyCopyBufferPool = sync.Pool{ }, } +var reverseProxyCandidatePool = sync.Pool{ + New: func() any { + s := make([]*reverseProxyUpstream, 0, 8) + return &s + }, +} + type reverseProxyStatusError struct { status int err error diff --git a/reverseproxy_benchmark_test.go b/reverseproxy_benchmark_test.go index 5d82037..f55f5f0 100644 --- a/reverseproxy_benchmark_test.go +++ b/reverseproxy_benchmark_test.go @@ -110,10 +110,10 @@ func BenchmarkReverseProxyCopyResponse(b *testing.B) { func BenchmarkReverseProxyAvailableUpstreams(b *testing.B) { proxy := &reverseProxyHandler{ upstreams: []*reverseProxyUpstream{ - {key: "a"}, - {key: "b"}, - {key: "c"}, - {key: "d"}, + {key: "a", index: 0}, + {key: "b", index: 1}, + {key: "c", index: 2}, + {key: "d", index: 3}, }, config: ReverseProxyConfig{ PassiveHealth: ReverseProxyPassiveHealthConfig{ @@ -135,6 +135,38 @@ func BenchmarkReverseProxyAvailableUpstreams(b *testing.B) { } } +func BenchmarkReverseProxySelectUpstream(b *testing.B) { + proxy := &reverseProxyHandler{ + upstreams: []*reverseProxyUpstream{ + {key: "a", index: 0}, + {key: "b", index: 1}, + {key: "c", index: 2}, + {key: "d", index: 3}, + }, + config: ReverseProxyConfig{ + LoadBalancing: ReverseProxyLoadBalancingConfig{Policy: LBRoundRobin()}, + PassiveHealth: ReverseProxyPassiveHealthConfig{ + FailDuration: time.Minute, + MaxFails: 3, + }, + }, + } + proxy.upstreams[0].failures = []time.Time{time.Now().Add(-30 * time.Second)} + + c, _ := CreateTestContext(nil) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + selected, err := proxy.selectUpstream(c, nil) + if err != nil { + b.Fatalf("selectUpstream failed: %v", err) + } + benchmarkReverseProxySink = selected.index + } +} + func TestReverseProxyCopyResponseWithoutBufferPool(t *testing.T) { proxy := newReverseProxyHandler(ReverseProxyConfig{}) dst := newBenchmarkResponseWriter() @@ -203,4 +235,29 @@ func TestReverseProxyCopyResponseRespectsCustomBufferLength(t *testing.T) { } } +func TestReverseProxyAvailableUpstreamsFiltersExcludedAndUnhealthy(t *testing.T) { + now := time.Now() + proxy := &reverseProxyHandler{ + upstreams: []*reverseProxyUpstream{ + {key: "a"}, + {key: "b", failures: []time.Time{now.Add(-20 * time.Second), now.Add(-10 * time.Second)}}, + {key: "c"}, + }, + config: ReverseProxyConfig{ + PassiveHealth: ReverseProxyPassiveHealthConfig{ + FailDuration: time.Minute, + MaxFails: 2, + }, + }, + } + + available := proxy.availableUpstreams(now, map[string]struct{}{"c": {}}) + if len(available) != 1 { + t.Fatalf("expected only one available upstream, got %d", len(available)) + } + if available[0].key != "a" { + t.Fatalf("expected upstream 'a', got %q", available[0].key) + } +} + var _ io.Writer = (*benchmarkResponseWriter)(nil) diff --git a/reverseproxy_lb.go b/reverseproxy_lb.go index d2d45ab..02895fb 100644 --- a/reverseproxy_lb.go +++ b/reverseproxy_lb.go @@ -137,18 +137,32 @@ func validateReverseProxyLBPolicy(policy ReverseProxyLBPolicy) error { func (p *reverseProxyHandler) selectUpstream(c *Context, excluded map[string]struct{}) (*reverseProxyUpstream, error) { now := time.Now() policy := p.config.LoadBalancing.Policy - candidates := p.availableUpstreams(now, excluded) + candidateBuf := reverseProxyCandidatePool.Get().(*[]*reverseProxyUpstream) + candidates := p.availableUpstreamsInto(now, excluded, *candidateBuf) if len(candidates) == 0 && len(excluded) > 0 { - candidates = p.availableUpstreams(now, nil) + candidates = p.availableUpstreamsInto(now, nil, candidates[:0]) } if len(candidates) == 0 { + *candidateBuf = candidates[:0] + reverseProxyCandidatePool.Put(candidateBuf) return nil, errReverseProxyNoAvailableUpstreams } - return p.selectUpstreamWithPolicy(c, candidates, policy), nil + selected := p.selectUpstreamWithPolicy(c, candidates, policy) + *candidateBuf = candidates[:0] + reverseProxyCandidatePool.Put(candidateBuf) + return selected, nil } func (p *reverseProxyHandler) availableUpstreams(now time.Time, excluded map[string]struct{}) []*reverseProxyUpstream { - candidates := make([]*reverseProxyUpstream, 0, len(p.upstreams)) + return p.availableUpstreamsInto(now, excluded, nil) +} + +func (p *reverseProxyHandler) availableUpstreamsInto(now time.Time, excluded map[string]struct{}, candidates []*reverseProxyUpstream) []*reverseProxyUpstream { + if cap(candidates) < len(p.upstreams) { + candidates = make([]*reverseProxyUpstream, 0, len(p.upstreams)) + } else { + candidates = candidates[:0] + } for _, upstream := range p.upstreams { if _, skip := excluded[upstream.key]; skip { continue