文章目录
- 一、TCP长连接和短连接区别
- 1.1 长连接、短连接概念
- 1.2 长连接、短连接的传输过程区别
- 1.3 长连接与短连接的优缺点
- 二、TCP长连接和短连接应用场景
- 2.1 长连接应用场景
- 2.2 短连接应用场景
- 三、Golang HTTP连接池
- 3.1 问题引入
- 3.2 golang连接池原理
- 3.3 连接获取与回收
- 3.4 空闲连接超时关闭
- 3.5 排队队列怎么实现
- 3.6 tranport连接池总结
- 四、初始化HTTP长连接池
- 4.1 net/http client使用
- 4.2 Transport连接池使用
- 4.3 各个超时时间设置
- 五、为什么需要response.Body.Close()
- 5.1 Do 方法
- 5.2 为什么需要response.Body.Close()
- 六、golang短连接使用
- 6.1 关闭连接
- 6.2 使用 Transport 取消 HTTP利用连接
- 七、总结
TCP/IP是个协议组,可分为三个层次:网络层、传输层和应用层。
在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
在传输层中有TCP协议与UDP协议。
在应用层有FTP、HTTP、TELNET、SMTP、DNS等协议。
一、TCP长连接和短连接区别
1.1 长连接、短连接概念
-
所谓长连接,指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接,一般需要自己做在线维持。
-
短连接(short connnection)是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时,才去建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送。
比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。
其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。
1.2 长连接、短连接的传输过程区别
通常的短连接操作步骤是: 连接→数据传输→关闭连接;
而长连接通常就是:
连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接;
这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,短连接在没有数据传输时直接关闭就行了。
1.3 长连接与短连接的优缺点
长连接
- 优点
- 省去了较多的TCP的建立与关闭的时间
- 性能比较好(因为一直保持连接的状态)
- 缺点
- 当连接越来越多会压垮服务器
- 连接管理难度较大
- 安全性能差
短连接
- 优点
- 管理服务简单,存在的连接都是有效连接,不需要额外的控制手段
- 缺点
- 如果请求频繁,不断的连接以及关闭连接,浪费时间
二、TCP长连接和短连接应用场景
2.1 长连接应用场景
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,再次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
2.2 短连接应用场景
像WEB网站的http服务一般都用短连接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连接好。
三、Golang HTTP连接池
参考文章:
详解golang net之transport:https://www.cnblogs.com/charlieroro/p/11409153.html
3.1 问题引入
作为一名Golang开发者,你可能会遇到线上环境遇到连接数暴增问题。
纠其原因,Golang作为常驻进程,请求第三方服务或者资源完毕后,需要手动关闭连接,否则连接会一直存在。而很多时候,开发者不一定记得关闭这个连接。
那么你可能会问,我可以在程序中defer主动关闭连接啊!需要知道的是,连接相对于其他对象,创建成本较高,资源也有限。如果没有连接池,在高并发场景下,连接关闭又新建,很快就会因为过多的TIME_WAIT(连接主动关闭方)导致无法创建更多连接了,程序被压死。
那么这样是不是很麻烦很头疼?于是有了连接池。顾名思义,连接池就是管理连接的;我们从连接池获取连接,请求完毕后再将连接还给连接池;连接池帮我们做了连接的建立、复用以及回收工作。
在设计与实现连接池时,我们通常需要考虑以下几个问题:
- 连接池的连接数目是否有限制,最大可以建立多少个连接?
- 当连接长时间没有使用,需要回收该连接吗?
- 业务请求需要获取连接时,此时若连接池无空闲连接且无法新建连接,业务需要排队等待吗?
- 排队的话又存在另外的问题,队列长度有无限制,排队时间呢?
3.2 golang连接池原理
Transport:为http.RoundTripper接口,定义功能为负责http的请求分发。实际功能由结构体net/http/transport.go中的Transport struct继承并实现,除了请求发分还实现了对空闲连接的管理。如果创建client时不定义,就用系统默认配置。
Transport结构定义如下:
type Transport struct { //操作空闲连接需要获取锁 idleMu sync.Mutex //空闲连接池,key为协议目标地址等组合 idleConn map[connectMethodKey][]*persistConn // most recently used at end //等待空闲连接的队列,基于切片实现,队列大小无限制 idleConnWait map[connectMethodKey]wantConnQueue // waiting getConns //排队等待建立连接需要获取锁 connsPerHostMu sync.Mutex //每个host建立的连接数 connsPerHost map[connectMethodKey]int //等待建立连接的队列,同样基于切片实现,队列大小无限制 connsPerHostWait map[connectMethodKey]wantConnQueue // waiting getConns //tls client用于tls协商的配置 TLSClientConfig *tls.Config //tls协商的超时时间 TLSHandshakeTimeout time.Duration //是否取消长连接,默认使用长连接 DisableKeepAlives bool //是否取消HTTP压缩 DisableCompression bool //所有host的连接池最大连接数量,默认无穷大 MaxIdleConns int //每个目标host最大空闲连接数;默认为2(注意默认值) MaxIdleConnsPerHost int //对每个host可建立的最大连接数量,0表示不限制 MaxConnsPerHost int //连接多少时间没有使用则被关闭 IdleConnTimeout time.Duration //发送完request后等待serve response的时间 ResponseHeaderTimeout time.Duration //限制客户端在发送一个包含:100-continue的http报文头后,等待收到一个go-ahead响应报文所用的时间。 ExpectContinueTimeout time.Duration //在tls协商带NPN/ALPN的扩展后,transport如何切换到其他协议。指tls之上的协议(next指的就是tls之上的意思) TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper //在CONNECT请求时,配置request的首部信息,可选 ProxyConnectHeader Header //指定server响应首部的最大字节数 MaxResponseHeaderBytes int64 //写bufffer的大小,默认为4096。 WriteBufferSize int //读bufffer的大小,默认为4096。 ReadBufferSize int //是否启用HTTP/2,默认为启用 ForceAttemptHTTP2 bool}
需要特别注意的是,MaxIdleConnsPerHost默认等于2,即与目标主机最多只维护两个空闲连接。这会导致什么呢?
如果遇到突发流量,瞬间建立大量连接,但是回收连接时,由于最大空闲连接数的限制,该联机不能进入空闲连接池,只能直接关闭。结果是,一直新建大量连接,又关闭大量连,业务机器的TIME_WAIT连接数随之突增。
最后,Transport也提供了配置DisableKeepAlives,禁用长连接,使用短连接访问第三方资源或者服务。
3.3 连接获取与回收
Transport结构提供下面两个方法实现连接的获取与回收操作。
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {}func (t *Transport) tryPutIdleConn(pconn *persistConn) error {}
连接的获取主要分为两步走:
1)尝试获取空闲连接;
2)尝试新建连接:
//getConn方法内部实现ifdelivered := t.queueForIdleConn(w); delivered { returnpc, nil} t.queueForDial(w)
当然,可能获取不到连接而需要排队,此时怎么办呢?当前会阻塞当前协程了,直到获取连接为止,或者httpclient超时取消请求:
select { case<-w.ready: returnw.pc, w.err //超时被取消 case<-req.Cancel: returnnil, errRequestCanceledConn ……}var errRequestCanceledConn = errors.New("net/http: request canceled while waiting for connection") // TODO: unify?
排队等待空闲连接的逻辑如下:
func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) { //如果配置了空闲超时时间,获取到连接需要检测,超时则关闭连接 ift.IdleConnTimeout > 0{ oldTime = time.Now().Add(-t.IdleConnTimeout) } iflist, ok := t.idleConn[w.key]; ok { forlen(list) > 0&& !stop { pconn := list[len(list)-1] tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime) //超时了,关闭连接 iftooOld { go pconn.closeConnIfStillIdle() } //分发连接到wantConn delivered = w.tryDeliver(pconn, nil) } } //排队等待空闲连接 q := t.idleConnWait[w.key] q.pushBack(w) t.idleConnWait[w.key] = q}
排队等待新建连接的逻辑如下:
func (t *Transport) queueForDial(w *wantConn) { //如果没有限制最大连接数,直接建立连接 ift.MaxConnsPerHost <= 0{ go t.dialConnFor(w) return } //如果没超过连接数限制,直接建立连接 ifn := t.connsPerHost[w.key]; n < t.MaxConnsPerHost { go t.dialConnFor(w) return } //排队等待连接建立 q := t.connsPerHostWait[w.key] q.pushBack(w) t.connsPerHostWait[w.key] = q}
连接建立完成后,同样会调用tryDeliver分发连接到wantConn,同时关闭通道w.ready,这样主协程就接触阻塞了。
func (w *wantConn) tryDeliver(pc *persistConn, err error) bool { w.pc = pc close(w.ready)}
请求处理完成后,通过tryPutIdleConn将连接放回连接池;这时候如果存在等待空闲连接的协程,则需要分发复用该连接。另外,在回收连接时,还需要校验空闲连接数目是否超过限制: