You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Go HTTP Client每次请求轮流用不同代理且复用TCP连接

解决方案:Go HTTP Client代理轮询+TCP连接复用

问题原因

http.Transport的连接池是基于目标主机+代理+TLS配置的组合键缓存连接的。原代码中,第一次请求调用Proxy函数选中代理后,后续同主机的请求会直接复用该代理的连接,不会再次触发Proxy函数,导致轮询策略失效。关闭DisableKeepAlives虽然能强制每次请求触发Proxy,但会完全失去TCP连接复用的优势,增加握手开销。

优雅解决方案:自定义RoundTripper

通过实现自定义RoundTripper,手动控制代理轮选逻辑,同时为每个代理维护独立的连接池,既实现轮询又保留连接复用。

完整代码示例

package main

import (
	"fmt"
	"net/http"
	"net/url"
	"sync"
)

var client *http.Client

func main() {
	roundRobin := NewRoundRobinProxy(
		"http://myproxy1:8888",
		"http://myproxy2:8888",
		"http://myproxy3:8888")

	// 基础Transport配置,保留连接复用相关参数
	baseTransport := &http.Transport{
		MaxConnsPerHost:   10,
		DisableKeepAlives: false,
	}

	// 使用自定义RoundTripper替换默认Transport
	client = &http.Client{
		Transport: &RoundRobinTripper{
			baseTransport:   baseTransport,
			proxy:           roundRobin,
			proxyTransports: sync.Map{},
		},
	}

	// 发起测试请求
	sendReq("https://www.binance.com")
	sendReq("https://www.binance.com")
	sendReq("https://www.binance.com")
	sendReq("https://www.binance.com")
}

func sendReq(urlStr string) {
	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		fmt.Printf("create request error: %v\n", err)
		return
	}

	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("send request error: %v\n", err)
		return
	}
	defer resp.Body.Close()

	fmt.Println("got resp from ", urlStr)
}

// RoundRobinProxy 实现轮询代理逻辑,保证并发安全
type RoundRobinProxy struct {
	urls   []*url.URL
	cursor int
	mu     sync.Mutex
}

func NewRoundRobinProxy(urls ...string) *RoundRobinProxy {
	p := &RoundRobinProxy{cursor: 0}
	for _, v := range urls {
		u, err := url.Parse(v)
		if err != nil {
			fmt.Printf("parse proxy url error: %v\n", err)
			continue
		}
		p.urls = append(p.urls, u)
	}
	return p
}

// Next 轮选下一个代理地址
func (p *RoundRobinProxy) Next() *url.URL {
	p.mu.Lock()
	defer p.mu.Unlock()
	if len(p.urls) == 0 {
		return nil
	}
	u := p.urls[p.cursor]
	p.cursor = (p.cursor + 1) % len(p.urls)
	fmt.Printf("selected proxy: %s\n", u.String())
	return u
}

// RoundRobinTripper 自定义RoundTripper,实现代理轮询+连接复用
type RoundRobinTripper struct {
	baseTransport   *http.Transport
	proxy           *RoundRobinProxy
	proxyTransports sync.Map // 缓存每个代理对应的Transport实例
}

func (rt *RoundRobinTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	proxyURL := rt.proxy.Next()
	if proxyURL == nil {
		return nil, fmt.Errorf("no available proxy")
	}
	proxyKey := proxyURL.String()

	// 获取或创建当前代理对应的Transport实例
	transport, ok := rt.proxyTransports.Load(proxyKey)
	if !ok {
		// 复制基础Transport配置,仅修改Proxy字段
		newTransport := rt.baseTransport.Clone()
		newTransport.Proxy = func(*http.Request) (*url.URL, error) {
			return proxyURL, nil
		}
		transport, _ = rt.proxyTransports.LoadOrStore(proxyKey, newTransport)
	}

	// 使用对应代理的Transport执行请求
	return transport.(*http.Transport).RoundTrip(req)
}

方案优势

  • 连接复用保留:每个代理对应独立的http.Transport实例,各自维护TCP连接池,避免重复握手
  • 轮询策略生效:每次请求都会主动轮选代理,不受连接池缓存影响
  • 并发安全:通过sync.Mutex和sync.Map保证多goroutine下的安全运行
  • 配置复用:基于基础Transport的配置创建代理专属Transport,无需重复设置参数

内容的提问来源于stack exchange,提问作者Владимир Савостин

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 15:35:49