选择器可以完美轮转,却始终选中第一个代理。问题出在每处理一个请求就无意中创建新选择器,导致计数器在还没轮到第二个选择时就被重置。

只构造一次选择器,并让它随 transport 一起保留。HTTP 代理列表可以在请求允许公共中间人时为这组 URL 提供公共 HTTP 候选。请从一份已验证过、非空的代理 URL 列表开始:

func rotatingProxy(urls []*url.URL) func(*http.Request) (*url.URL, error) {
    var mu sync.Mutex
    next := 0
    return func(*http.Request) (*url.URL, error) {
        mu.Lock()
        selected := urls[next]
        next = (next + 1) % len(urls)
        mu.Unlock()
        return selected, nil
    }
}

外层应用必须拒绝空列表、校验 URL,并使用常规的 net/http、net/url 和 sync 导入。在此选择器使用该列表期间,列表应保持不可变,或者通过另行同步的设计整体替换。

把返回的函数挂到一个被保留的 http.Transport 上。互斥锁在并发回调之间保护选择顺序。它不要求在慢速网络请求完成期间一直持锁。

不要只为打印而消耗一条路由

为诊断消息再次调用选择器会再次推进计数器。那样输出的可能是下一个候选,而不是你正在排查的请求实际用过的候选。应在真实回调内部用安全的端点标识符记录这次选择。

回调被调用并不证明请求已成功送达。重定向或额外的传输尝试会让“一次应用调用、一次选择”这个简单假设变得复杂。如果工作流需要在多个操作中保持同一条路由,请在工作流边界上选定这条路由,而不要使用全局轮转回调。

轮询同样不保证不同的公共出口。它调度的是端点。最终身份仍然是各端点所提供路径的属性。

相关文章