V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
daoqiongsi1101
V2EX  ›  Go 编程语言

http1.1 长连接与 golang 并发请求的疑问

  •  
  •   daoqiongsi1101 · 2022-02-13 22:00:14 +08:00 · 2889 次点击
    这是一个创建于 1015 天前的主题,其中的信息可能已经有所发展或是发生改变。

    浏览器对于同一个域名的请求,比如 Chrome 并发连接数是 6 个,假设是 http1.1 协议,那么这 6 个请求是复用一个 tcp 连接并发请求(响应还是按顺序),超出了就会有“队头阻塞”的问题。

    问题

    使用 golang 写一个程序,开 10 个 goroutine 并发请求同一个域名的资源,那么这 10 个请求是用一个 tcp 连接吗?如果是,是否也存在“队头阻塞”问题?那这样跟浏览器并发请求有何区别?

    谢谢。

    第 1 条附言  ·  2022-02-13 22:43:54 +08:00
    使用 net/http 包发 http 请求,都用默认设置。
    第 2 条附言  ·  2022-02-14 00:20:27 +08:00
    更正:这 6 个并行的请求,是 6 个独立的 tcp 连接,后面更多的请求会复用前面的连接。
    17 条回复    2022-02-14 21:30:53 +08:00
    sagaxu
        1
    sagaxu  
       2022-02-13 22:09:50 +08:00 via Android
    10 个请求并发就是 10 个连接
    daoqiongsi1101
        2
    daoqiongsi1101  
    OP
       2022-02-13 22:10:15 +08:00
    @sagaxu 跟浏览器的机制不一样么
    sagaxu
        3
    sagaxu  
       2022-02-13 22:14:54 +08:00 via Android   ❤️ 1
    @daoqiongsi1101 浏览器只服务一个用户,同一个域名做个排队,对体验影响不大。服务端不一样,100 个请求也许是 100 个不同用户发起的,如果排队,后面的用户只能超时了。

    服务端也有连接池大小限制,一般几百到几千,超出大小了,要么排队等,要么临时创建不受限制的连接但用完就关。
    qwq11
        4
    qwq11  
       2022-02-13 22:29:17 +08:00
    10 个 goroutine 就是同时(分时复用)调用 10 次创建 socket ,自然就是会是 10 个连接,分别占用 10 个端口。

    浏览器的实现是为了性能考虑,如果加载 100 个资源,从并行角度来讲,那当然是开 100 个连接好。但是如果我开了 10 个窗口,就要有 1000 个连接,每次连接都做一次 TCP TLS handshake ,就太非资源了
    daoqiongsi1101
        5
    daoqiongsi1101  
    OP
       2022-02-13 22:43:06 +08:00
    @sagaxu 这个程序是本地运行的,go run main.go 启动,用 net/http 包发请求,默认配置也有连接池,既然有连接池,那应该会复用连接
    jinliming2
        6
    jinliming2  
       2022-02-13 23:16:32 +08:00
    等下,HTTP/1.1 的请求,并行不是复用同一个 TCP 啊?串行才是复用同一个 TCP 的啊!队头阻塞也是复用同一个 TCP 连接的串行 HTTP ,后面的请求要等待前面的请求响应啊。
    Chrome 并发连接数限制是指创建的 TCP 的连接数啊,并且仅限 HTTP/1 。

    HTTP/1 还没有分帧并发的技术,所有数据都是串行发送的,不存在你说的“并发请求、顺序响应”。要实现并发,就得建立多条 TCP ,在多个 TCP 连接上同时请求。
    HTTP/1 的 keep-alive 是指一轮 请求-响应 结束后,不关闭 TCP 连接,可以直接在当前 TCP 连接上进行新的 HTTP 请求-响应。
    Aoang
        7
    Aoang  
       2022-02-13 23:22:49 +08:00 via iPhone
    具体的情况得结合源码及你具体的代码才能知道。

    http.Client 是可以配置最大连接数及其他的一些配置的。

    默认配置下,如果多个协程共用的一个 http.Client ,那么可能是会出现连接复用的。
    如果不想连接复用,可以每个协程使用单独的 http.Client ,不过不建议这么做。

    对于调用非常频繁的服务,即使连接复用也还是可能出现问题。对于这种还是用 rpc 比较好,http 不适合。
    yin1999
        8
    yin1999  
       2022-02-14 00:13:07 +08:00 via iPad
    http.Client 默认使用的 transport 默认会复用 tcp 连接,但需要在每次请求后 io.ReadAll 一下 response.Body 并 close ,这样能够保证连接复用。当然,这个是串行复用的,就是在一次请求结束后,不立刻关闭 tcp 连接,在后面连接有效且有相应请求时,不再需要重新建立 tcp 连接。
    daoqiongsi1101
        9
    daoqiongsi1101  
    OP
       2022-02-14 00:19:37 +08:00
    @jinliming2 没错没错,这 6 个是 6 个不同的 tcp 连接,后面串行的才是复用前面的连接。
    wangyu17455
        10
    wangyu17455  
       2022-02-14 00:38:46 +08:00   ❤️ 1
    浏览器的复用指的是:假如从同一个域名下加载 100 个资源,如果用的是 http1.1 ,同一时刻只会存在 6 个到这个域名的 tcp 连接,最初的 6 个资源请求完毕空闲出的连接才会被复用进行下一次请求。
    jinliming2
        11
    jinliming2  
       2022-02-14 01:49:14 +08:00   ❤️ 1
    @daoqiongsi1101 所以啊,你只要想一想,你 10 个 goroutine 请求是同时请求的,还是有先后顺序的?肯定是同时的啊。

    实际上,同时并发请求数受到操作系统的限制(端口数、ulimit 、maxfiles 之类的),为了避免达到操作系统限制,同时降低建立 TCP 的开销,所以浏览器才会实现较小的并发限制。
    实际上,具体是看你 golang 代码怎么写的。多个 goroutine 是用的同一个 http.Client 还是不同的,单域名连接数配置的多少。
    如果是使用默认的 http.Client 来请求的话,那么走的就是默认的 http.Transport 配置,里面指定的单域名连接数 MaxConnsPerHost 默认是 0 ,也就是不限制。那么你只要没有达到操作系统限制,并发就是能建立多少 TCP 就建立多少 TCP 来并发请求,所以 10 个 goroutine 就是 10 个 TCP 。
    而另外,可以针对单个 http.Client 对象来限制连接数,那么使用同一个实例来请求就会受到限制,达到限制就会阻塞等待。

    另外注意,这个连接数跟连接池关系不大。默认的 http.Transport 的连接池有两个配置:全部连接池大小 MaxIdleConns 默认是 100 、单域名连接池大小 DefaultMaxIdleConnsPerHost 默认是 2 。这两个配置不影响并发请求的 TCP 连接数。

    假设现在是单域名,单域名连接池大小 DefaultMaxIdleConnsPerHost 是 2 ,MaxConnsPerHost 并发数设置了 5 。那么此时你开了 10 个 goroutine 并发,并且每个请求都是 1 秒响应,则会发生这样的情况(实际情况可能受各种竞争因素影响):
    1 ,建立 5 个 TCP 连接并发,剩余 5 个连接阻塞等待。
    2 ,5 个连接请求结束,只有 2 个 TCP 连接进入连接池复用,剩余 3 个 TCP 连接被 close 。
    3 ,剩下的 5 个连接开始请求,其中 2 个复用之前连接池中的 TCP 连接,另外 3 个则建立新的 TCP 连接进行请求。
    4 ,所有请求都结束,连接池中持有 2 个连接等待复用,其余连接都被 close 。
    5 ,如果没有更多请求了,则连接池中的连接在超时( IdleConnTimeout )后被自动 close 。或者等待 TCP 自己的 keepalive 超时规则( tcp_keepalive_time 、tcp_keepalive_intvl 、tcp_keepalive_probes )来关闭连接。
    rrfeng
        12
    rrfeng  
       2022-02-14 08:41:09 +08:00 via Android
    问题之前:浏览器的描述不对,你并不知道浏览器建立了几个链接。最多 6 个是连接池的限制。

    问题本身:net/http 默认 client 有连接池的,所以你也并不知道用了几个。除非每个 goroutine 里自己创建了独立的 client 。
    NeoZephyr
        13
    NeoZephyr  
       2022-02-14 11:08:18 +08:00
    所以说,有些时候 http1.1 是不是会比 http2.0 要好。
    daoqiongsi1101
        14
    daoqiongsi1101  
    OP
       2022-02-14 14:10:17 +08:00
    @NeoZephyr Nginx upstream 不支持 http2 ,就是说某些情况下 http1.1 更好,但没想到具体说明场景。
    @jinliming2 请问你如何理解这个呢? https://trac.nginx.org/nginx/ticket/923
    daoqiongsi1101
        15
    daoqiongsi1101  
    OP
       2022-02-14 14:31:31 +08:00
    @jinliming2 很详尽,感谢!
    shyling
        16
    shyling  
       2022-02-14 21:03:37 +08:00
    你写代码请求时跟浏览器关系不大。。。

    如果用了连接池就是受限于连接池,连接用完后再请求会等前面连接释放

    直接新连接那就是系统的限制了
    jinliming2
        17
    jinliming2  
       2022-02-14 21:30:53 +08:00   ❤️ 1
    @daoqiongsi1101 http://nginx.org/en/docs/http/ngx_http_upstream_module.html#server 对于 HTTP/1 upstream ,可以通过 max_conns 限制连接数、keepalive 限制连接池。

    先说一下 HTTP/1 、HTTP/2 、HTTP/3:
    HTTP/1 在单个 TCP 上以 keep-alive 的形式复用,按照“请求-响应-请求-响应”的顺序进行,后面的请求会受到前面请求的阻塞,叫做“线头阻塞 Head-of-line blocking” https://en.wikipedia.org/wiki/Head-of-line_blocking
    HTTP/2 在单个 TCP 上分帧复用连接,多个请求可以同时发送、同时接收。但这个“同时”只是在应用层的 HTTP 协议眼中看起来是“同时”的,而在传输层的 TCP 眼中来看还是串行的。就是将多个 HTTP 请求响应拆成小片,然后在单个 TCP 连接中交替(也不一定是交替)传输。所以仍然存在“线头阻塞”,只不过是 TCP 层面的线头阻塞。
    HTTP/3 将传输层协议给替换掉了,改成了基于 UDP 的 QUIC ,由于 UDP 属于无状态的,所以传输层的包也是同时发送了,这就消除了传输层的线头阻塞。

    回到你这个链接里讨论的不支持 HTTP/2 ,总结一下原因:
    1 ,支持 HTTP/2 作为 upstream ,那么所有连接复用同一个 TCP ,会受到 TCP 线头阻塞和拥塞控制问题的影响,对 nginx 来说,会使得事情变得复杂
    2 ,用单个连接传输,几乎消除了连接数的限制,但是实际上 nginx 本身也没有主动做这样的限制(除非你自己指定 max_conns ),所以这个没啥意义
    3 ,(感觉也是最主要的一点)实现 HTTP/2 进行单路复用,需要对 upstream 模块做重大修改,风险比较高
    If you still think that talking to backends via HTTP/2 is something needed - feel free to provide patches.(翻译:老子嫌麻烦不想做,你要觉得有必要,你做好了把 patch 提交上来)

    所以,风险大于收益,他们觉得没有实现的必要。
    这也是 2015 、2016 年的帖子了,那时候 HTTP/2 才刚起步。
    现在 HTTP/3 的时代快来了,云厂商都在部署 h3 了,不知道是不是 nginx 的时代已经快过去了?(瞎说的)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5365 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 09:38 · PVG 17:38 · LAX 01:38 · JFK 04:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.