因为业务需要发送大量的http的请求,会有很多的302跳转,使用的是go,对go中的httpClient做了一些重新配置,代码如下:

var client *http.Client

func init() {
    client = &http.Client{
        Transport: &http.Transport{
            ReadBufferSize:         2048, //tcp 读buffer
            WriteBufferSize:        2048, //tcp 写buff
            MaxResponseHeaderBytes: 4096, //响应最大header头部,默认是10M,减少头部大小可以堆内存
            DialContext: (&net.Dialer{
                Timeout:   10 * time.Second,
                KeepAlive: 30 * time.Second,
                DualStack: true,
            }).DialContext,
            ForceAttemptHTTP2: true,             //启用http2 性能提升2倍,tcp 连接数减少2/3
            MaxIdleConns:      1000,             // 最大空闲连接数
            IdleConnTimeout:   10 * time.Second, // 空闲连接超时时间

            TLSHandshakeTimeout: 10 * time.Second, // TLS握手超时
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify:     true,
                SessionTicketsDisabled: false,
                ClientSessionCache:     &sessionCache{cache: sync.Map{}}}, //InsecureSkipVerify用来控制客户端是否证书和服务器主机名。如果设置为true, 则不会校验证书以及证书中的主机名和服务器主机名是否一致。
            MaxIdleConnsPerHost: 1000, // 使用长连接,需要调高该值 每个主机的最大空闲连接数
            MaxConnsPerHost:     5000,
            DisableKeepAlives:   false,
        },
        Timeout: 10 * time.Second, // 请求超时时间
    }
}

一般情况下,这样的配置应该会有不错的效果,但是部署到线上之后出现了,cpu稍微有点高(与优化后的对比),峰值30M的带宽全部吃满,导致很多请求其实发不出去了。

着重观察10点13之前的cpu与带宽情况,cpu不太高,但是带宽使用很大 重点是发送http请求使用时长,有一些已经超过20秒了,即便每次是500个请求,开启了500个协程,使用了连接池,依然花费巨大的时间。 前前后后几个月一直在分析具体原因,一次正常工作的下午,说再细细看一下这块的情况,仔细分析了当前场景与http发送的关系,结合机器的流量观察,发现有很多的与第三方建立的链接没有释放, 这不符合我们的逻辑,理论建立连接发送数据后就会断开,但是结果不是这样,优化了一下,禁止重定向,到达我们预定义的地址后就不再请求下次链接,修改后的代码如下:

client = &http.Client{
        Transport: &http.Transport{
            ReadBufferSize:         2048, //tcp 读buffer
            WriteBufferSize:        2048, //tcp 写buff
            MaxResponseHeaderBytes: 4096, //响应最大header头部,默认是10M,减少头部大小可以堆内存
            DialContext: (&net.Dialer{
                Timeout:   10 * time.Second,
                KeepAlive: 30 * time.Second,
                DualStack: true,
            }).DialContext,
            ForceAttemptHTTP2: true,             //启用http2 性能提升2倍,tcp 连接数减少2/3
            MaxIdleConns:      1000,             // 最大空闲连接数
            IdleConnTimeout:   10 * time.Second, // 空闲连接超时时间

            TLSHandshakeTimeout: 10 * time.Second, // TLS握手超时
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify:     true,
                SessionTicketsDisabled: false,
                ClientSessionCache:     &sessionCache{cache: sync.Map{}}}, //InsecureSkipVerify用来控制客户端是否证书和服务器主机名。如果设置为true, 则不会校验证书以及证书中的主机名和服务器主机名是否一致。
            MaxIdleConnsPerHost: 1000, // 使用长连接,需要调高该值 每个主机的最大空闲连接数
            MaxConnsPerHost:     5000,
            DisableKeepAlives:   false,
        },
        Timeout: 10 * time.Second, // 请求超时时间
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            //这里面添加业务自定的逻辑比如
            //超过最大跳转次数 我们就返回错误 或者返回 return http.ErrUseLastResponse
            if check.MaxRedirect(len(via)) {
                return errors.New("max redirect") // return http.ErrUseLastResponse
            }
            return http.ErrUseLastResponse
        },
    }
  1. 让禁止重定向,让CheckRedirect返回一个http.ErrUseLastResponse,http.ErrUseLastResponse的结果会返回上一次跳转成功的结果,根据自己的业务去判断需要返回什么
  2. 与此同时,修改入参的请求为类似mq方式,接收请求,做了一个1500大小的chan作为延迟队列,启动协程去消费chan中的数据,再去发送 简单的代码逻辑如下:

接收请求

for _, item := range reqList {
        if err := RequestAsyncProducer(item); err != nil {
            msg = err.Error()
            break
        }
    }
    return msg

请求异步发送到一个chan

func RequestAsyncProducer(data Data) error {
    select {
    case topicRequests <- &data:
        return nil
    default:
        return errors.New("too busy")
    }
}

消费chan中的数据

func consumer(parallel int) {
    ch := make(chan struct{}, parallel)
    for i := 0; i < parallel; i++ {
        ch <- struct{}{}
    }
    for {
        <-ch
        go func() {
            defer func() {
                if err := recover(); err != nil {
                    ch <- struct{}{}
                    fmt.Println("error", err)
                }
            }()
            var request *Data
            ticker := time.Tick(time.Duration(3) * time.Second)
            for {
                select {
                case request = <-topicRequests:
                    toSend(*request)
                case <-ticker:
                    // wait to close
                }
            }
        }()
    }
}

经过优化后的效果入最开的图,响应时间如下,异步消费了当然会很快了,带宽,cpu如最开始的10点13之后的,下降很大,现在带宽已不是问题了。