resty 客户端 - meetbill/chi GitHub Wiki

1 每次请求时 request := client.R(),而不是多个协程共用 client.R() 吧

是的,不建议多个协程共用同一个 client.R() 创建的 *resty.Request 实例,而应该每个请求(或每个协程内的请求)单独通过 client.R() 创建新的 Request 实例。原因如下:

1. *resty.Request 不是为并发复用设计的

client.R() 返回的 *resty.Request 是单次请求的上下文对象,其内部维护了当前请求的状态(如 URL、Body、Header、参数等)。当多个协程同时调用该实例的 SetBody、SetHeader、Post 等方法时,可能导致:

  • 状态覆盖:协程 A 设置的 Body 可能被协程 B 覆盖,导致请求内容混乱。
  • 竞态条件:虽然 resty 内部可能对部分操作加锁,但官方并未保证 Request 实例的并发安全性,复用存在潜在风险。

2. 正确的做法:每个请求创建独立的 Request

// 1. 全局创建并复用 Client(Client 是协程安全的)
var client = resty.New().
    SetTimeout(DefaultHTTPTimeout).
    SetLogger(xagent_log.Logger).
    // ... 其他客户端配置

// 2. 每个协程/每次请求时,通过 client.R() 创建新的 Request
func sendRequest(url string, data interface{}) (*Response, error) {
    // 为当前请求创建独立的 Request 实例
    req := client.R()
    req.SetBody(data)
    req.SetHeader("Content-Type", "application/json")
    // 执行请求
    resp, err := req.Post(url)
    return resp, err
}

// 3. 多协程调用(安全)
for i := 0; i < 10; i++ {
    go func() {
        sendRequest("https://example.com", map[string]string{"key": "value"})
    }()
}

安全性:每个 req 是独立的,状态仅在当前请求中生效,不会被其他协程干扰。 性能:client.R() 是轻量级操作,创建新实例的开销极小,不会影响性能。

3. 为什么 Client 可以共用,而 Request 不行?

*resty.Client:存储全局配置(超时、重试、拦截器等),内部通过同步机制保证协程安全,设计为可被多个请求共享。 *resty.Request:存储单次请求的临时状态(URL、参数、Body 等),这些状态是请求级别的局部信息,无需也不应该被共享。

结论

多个协程不能共用同一个 client.R() 创建的 Request 实例,而应在每个协程或每次请求时通过 client.R() 新建 Request。这样既能保证并发安全,又能避免状态混乱,是 resty 推荐的使用方式。