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

Go协程并发4万次HTTP请求遇dial tcp地址分配失败问题求助

问题描述

使用Go协程并发发起HTTP请求时,迭代次数达到40000次会报错:dial tcp 127.0.0.1:3333: can't assign requested address,20000-30000次可正常运行,期望支持100万次并发迭代。

相关代码

func TestHttp(c *gin.Context) {
    limit, _ := strconv.Atoi(c.Param("limit"))
    for i := 1; i <= limit; i++ {
        url := "http://hostname/merchant" + strconv.Itoa(i) + "/print-test?p=" + strconv.Itoa(i)
        go MakeRequest2(url, i)
    }
}

/*
 * Function to hit url with get method and close the response body
 */
func MakeRequest2(url string, i int) {
    client := &http.Client{}
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    req.Close = true
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
}

func PrintTest(c *gin.Context) {
    a := make(map[string]int)
    for i := 1; i <= 10; i++ {
        a[strconv.Itoa(i)] = i
    }
    fmt.Println(a)
}

报错信息

Get "http://hostname/merchant34096/print-test?p=34096": dial tcp hostname: connect: cannot assign requested address
解决方案

错误原因

这个报错是因为系统的临时端口耗尽。每个TCP客户端连接都会占用一个本地临时端口,默认情况下系统临时端口范围有限(比如Linux通常是32768-60999,共约2.8万个)。当并发发起4万次请求时,短时间内创建的大量TCP连接会占满所有临时端口,后续请求无法分配新端口,从而触发报错。

另外代码里的两个操作会加重问题:

  1. 每次请求都新建http.Client,没有复用连接池,每个请求都创建全新TCP连接,加速端口消耗。
  2. 设置req.Close = true,强制请求完成后关闭连接,无法利用HTTP长连接复用端口。

解决步骤

  1. 复用全局http.Client连接池:
    Go的http.Client默认自带连接池,全局只初始化一次,所有请求复用它,这样可以重复利用TCP连接,大幅减少端口占用。

  2. 移除req.Close = true:
    该设置会让连接在请求结束后立即关闭,无法进入连接池复用,删除后连接会保持活跃,可被后续请求复用。

  3. 控制并发数:
    即使复用连接池,100万次直接并发也会对系统和目标服务造成极大压力,用带缓冲的通道做并发限流器,比如限制同时运行的协程数为1000(可根据服务器配置调整),避免瞬间请求量过载。

  4. 调整系统临时端口参数(可选):
    如果需要更大的并发量,可以修改系统参数扩大临时端口范围并加快连接回收:

    • Linux系统执行以下命令:
      # 扩大临时端口范围
      echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.conf
      # 加快TIME_WAIT状态连接回收
      echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
      echo "net.ipv4.tcp_tw_recycle = 1" >> /etc/sysctl.conf
      # 生效配置
      sysctl -p
      

修改后的示例代码

import (
    "net/http"
    "strconv"
    "sync"
    "time"
)

// 全局复用的http.Client,仅初始化一次
var httpClient = &http.Client{
    Transport: &http.Transport{
        MaxIdleConns:        1000,    // 全局最大空闲连接数
        MaxIdleConnsPerHost: 100,     // 每个主机的最大空闲连接数
        IdleConnTimeout:     30 * time.Second, // 空闲连接超时时间
    },
}

func TestHttp(c *gin.Context) {
    limit, _ := strconv.Atoi(c.Param("limit"))
    // 设置并发限制,这里限制为1000个协程同时运行
    sem := make(chan struct{}, 1000)
    var wg sync.WaitGroup
    wg.Add(limit)
    
    for i := 1; i <= limit; i++ {
        sem <- struct{}{}
        // 传入循环变量i的副本,避免协程引用同一变量
        go func(i int) {
            defer func() {
                <-sem
                wg.Done()
            }()
            url := "http://hostname/merchant" + strconv.Itoa(i) + "/print-test?p=" + strconv.Itoa(i)
            MakeRequest2(url, i)
        }(i)
    }
    wg.Wait()
}

func MakeRequest2(url string, i int) {
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    // 移除req.Close = true,让连接进入连接池复用
    resp, err := httpClient.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
}

func PrintTest(c *gin.Context) {
    a := make(map[string]int)
    for i := 1; i <= 10; i++ {
        a[strconv.Itoa(i)] = i
    }
    fmt.Println(a)
}

内容的提问来源于stack exchange,提问作者sahil garg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:10:19