灏天阁

长短连接的区别

· Yin灏

1.澄清概念

在回答这个问题之前,我们需要先澄清概念,才能更有效的回答问题,那么什么是长连接,什么是短连接呢?

  • 长连接:“长”的字面意思是维持时间长,「连接长期存在,也就是说程序可以复用这条连接,不用每次发起请求都重新建立连接」。这就好比你要过河,然后修建了一座桥,大家都可以使用这座桥,这座桥被大家复用了。
  • 短连接:“短”的字面意思是维持时间短,「连接在请求结束之后就被释放了,也就是说程序无法复用这条连接,每次发起请求都要建立新的连接」。这就好比红蓝两军在交战,红军在追击蓝军,蓝军过河时搭建了一座桥,通过之后立马把桥炸毁,红军要过河只能再重新建桥。

长短连接的优缺点如表 1-1 所示。

表 1-1 长短连接优缺点

优点 缺点
长连接 连接复用,每次请求耗时更短,性能更佳 维护成本大:需要对连接进行保活,并维护好连接池
短连接 每次都建立新连接,实现简单,代码易于理解和维护 请求耗时上涨,连接无法复用,容易把下游服务的连接打满

2.区别仅此而已?

如果你的回答仅仅只有上面的内容的话,「那么你无法从众多的候选人中脱颖而出,从而得到面试官的青睐」。下面从 4 个不同方面深入回答这个问题。

2.1 如何量化耗时上涨

使用长连接完成请求,请求耗时会上涨,「那么上涨多少毫秒,这个是由什么决定的?具体该如何量化呢?」别着急,让我们先来看看一个连接的建立和释放的过程。

连接建立

连接建立过程如图 2-1 所示。

图 2-1 连接建立

  • 服务端执行被动打开操作,在某个 IP 地址和端口上监听 TCP 连接请求,服务端的 TCP 连接状态从 CLOSED 变为 LISTEN。

  • 客户端执行主动打开操作,向服务端发送一个带有 SYN 标志位的 TCP 报文段,客户端的 TCP 连接状态从 CLOSED 变为 SYN_SENT。

  • 第一次握手:服务端接收到带有 SYN 标志位的 TCP 报文段后,回复一个带有 SYN 和 ACK 标志位的 TCP 报文段给客户端,服务端的 TCP 连接状态从 LISTEN 变为 SYN_RCVD。

  • 第二次握手:客户端接收到带有 SYN 和 ACK 标志位的 TCP 报文段后,向服务端回复一个带有 ACK 标志位的 TCP 报文段,客户端的 TCP 连接状态从 SYN_SENT 变为 ESTABLISHED。

  • 第三次握手:服务端接收到带有 ACK 标志位的 TCP 报文段后,服务端的 TCP 连接状态从 SYN_RCVD 变为 ESTABLISHED。

连接释放

连接释放过程如图 2-2 所示。

图 2-2 连接释放

  • 客户端和服务端正常进行双向数据传输,客户端请求结束后,主动关闭连接。此时,客户端和服务端的 TCP 连接状态都为 ESTABLISHED。

  • 第一次挥手:客户端主动调用 close 函数关闭连接,发送一个带 FIN 标志位的 TCP 报文段给服务端。客户端的 TCP 连接状态从 ESTABLISHED 进入 FIN_WAIT_1。

  • 第二次挥手:服务端在接收到带 FIN 标志位的 TCP 报文段后,回复一个带 ACK 标志位的 TCP 报文段给客户端。服务端的 TCP 连接状态从 ESTABLISHED 进入 CLOSE_WAIT,而客户端接收到带 ACK 标志位的 TCP 报文段后,TCP 连接状态从 FIN_WAIT_1 进入 FIN_WAIT_2。

  • 第三次挥手:服务端被动调用 close 函数关闭连接,向客户端发送一个带 FIN 标志位的 TCP 报文段。服务端的 TCP 连接状态从 CLOSE_WAIT 进入 LAST_ACK。

  • 第四次挥手:客户端接收到带 FIN 标志位的 TCP 报文段后,TCP 连接状态从 FIN_WAIT_2 进入 TIME_WAIT,并向服务端回复一个带 ACK 标志位的 TCP 报文段。服务端接收到带 ACK 标志位的 TCP 报文段后,TCP 连接状态从 LAST_ACK 进入 CLOSED。

  • 客户端等待 2MSL(Maximum Segment Lifetime,在网络中 TCP 报文段最大的生存时间)的时间后,TCP 连接状态从 TIME_WAIT 进入 CLOSED。

增涨的耗时分析

从上面连接建立和连接释放过程中我们可以发现,「使用短连接的请求需要额外付出连接建立和连接释放的成本」,我们再分别分析一下这两部分的耗时分别是多少。

  • 连接建立,需要三次握手,需要的时间成本至少为「1 个 RTT 时间」。

  • 连接释放,需要四次挥手,需要的时间成本至少为「1 个 RTT 时间,但是因为 close 调用默认是立即返回的,连接释放不会阻塞进程,所以这个成本可以忽略不计」。

综上所述,「使用短连接的请求需要多付出至少一个 RTT 的时间」。那什么是 RTT 时间,如何测量 RTT 时间呢?

RTT 测量

这个 RTT 时间是计算机网络中两台计算机之间 TCP 报文的往返时间,「而这是由物理距离来决定的,距离越远,RTT 时间越长,例如上海 IDC 和深圳 IDC 之间的 RTT 就有 50ms」。

我们可以使用 ping 命令来测量两台计算机之间的 RTT 时间。例如,我在本地 Linux 主机上执行 ping 命令来测量本地主机到百度搜索服务器的 RTT 时间。


[root@VM-114-245-centos ~]# ping www.baidu.com

PING www.a.shifen.com (14.119.104.254) 56(84) bytes of data.

64 bytes from 14.119.104.254: icmp_seq=1 ttl=52 time=3.89 ms

64 bytes from 14.119.104.254: icmp_seq=2 ttl=52 time=3.79 ms

64 bytes from 14.119.104.254: icmp_seq=3 ttl=52 time=3.95 ms

64 bytes from 14.119.104.254: icmp_seq=4 ttl=52 time=3.88 ms

64 bytes from 14.119.104.254: icmp_seq=5 ttl=52 time=4.14 ms

64 bytes from 14.119.104.254: icmp_seq=6 ttl=52 time=3.83 ms

64 bytes from 14.119.104.254: icmp_seq=7 ttl=52 time=4.03 ms

64 bytes from 14.119.104.254: icmp_seq=8 ttl=52 time=3.84 ms

64 bytes from 14.119.104.254: icmp_seq=9 ttl=52 time=3.90 ms

64 bytes from 14.119.104.254: icmp_seq=10 ttl=52 time=4.04 ms

64 bytes from 14.119.104.254: icmp_seq=11 ttl=52 time=3.81 ms

^C

--- www.a.shifen.com ping statistics ---

11 packets transmitted, 11 received, 0% packet loss, time 10014ms

rtt min/avg/max/mdev = 3.799/3.922/4.143/0.131 ms

[root@VM-114-245-centos ~]

从上面的输出可以看到,我本地主机到百度搜索服务器的 RTT 时间为 3ms 左右。

我们可以更近一步的思考,「RTT 的测量是如何实现的呢?

ICMP 回显请求

ICMP(网际控制报文协议)是 TCP/IP 协议栈中介于 IP 层和运输层之间的协议。其中,ICMP 有一种请求类型为回显请求,而 RTT 就是通过 ICMP 的回显请求来实现的。

在 ping 程序中,每次发送一个 ICMP 回显请求报文,「这个请求中携带发送时的本地机器时间戳」。目的主机在收到这个请求之后,会回复一个 ICMP 的回显应答报文。ping 程序在接收到这个报文之后,取出之前写入的发送时间戳,就可以计算 RTT 时间了。

由于篇幅的原因,这里就不展示具体的实现程序了。春哥计划单独另起一篇文章专门和大家一起聊聊该如何自己实现 ping 程序,大家记得关注春哥,下期分享不迷路。

2.2 连接如何保活

我们知道网络中的连接本质上是虚拟的连接,网络中的设备会启启停停,甚至连接两端的机器都可能随时启停或者被拔网线,因此连接不是建立了就万事大吉,连接是需要保活的。目前主流有 3 种连接保活策略。

  • 第 1 种:在运输层开启 tcp 协议的 keep alive 特性。

  • 第 2 种:在应用层使用应用层协议,做定期的心跳请求做连接保活,原理和 tcp 的 keep alive 特性类似,但是应用层的心跳保活可以探测到对端服务服务过载的情况,也更可控,也更及时。

  • 第 3 种:空闲连接释放,当发现连接空闲时间超过一定阈值时,则释放连接。

通常来说,第 2 种和第 3 种策略应用的比较多。

2.3 如何设置连接池大小

通常来说,我们可以通过系统的日常负载来评估连接池的大小。举个例子,如果你的系统日常的负载峰值为 2000qps,接口平均耗时为 50ms,那么连接池的大小就可以通过以下公式计算得出:2000 / (1000 / 50) = 100。这个公式的意思是,「单个连接一秒可以处理 20(1000ms / 50ms)个请求,因此只需要 100 个连接就可以在 1 秒内处理 2000 个请求」。

然而,还有一个问题需要考虑,就是日常的平均负载可能都达不到 1000qps。在这种情况下,创建 100 个连接的连接池会导致资源的浪费。为了避免这种浪费,「我们可以实时统计当前连接池中正在被使用的连接数的 pct99 的指标值,并使用这个指标值来评估在连接被归还给连接池时是否要释放连接」。这样就可以避免资源的浪费,提高系统的性能和效率。

2.4 连接池满了之后改如何分配连接

当服务负载峰值大于评估值时,连接池就会被耗尽,这个时候如何再分配连接呢?通常有 2 种策略。

  • 第 1 种:直接返回失败,无法分配连接,显然这种方式不够友好。

  • 第 2 种:挂起当前的连接分配请求,并设置一个超时时间。在超时时间内只要有连接被归还给连接池,就唤醒之前被挂起的连接分配请求,尝试分配连接。如果超时时间到了,还没分配到连接,再返回分配连接失败。

显然第 2 种策略更优,第 2 种策略尝试了尽最大努力来分配连接。

3.总结

按照春哥上面梳理的思路,我们可以从短连接耗时上涨的量化分析开始,逐步深入到 RTT 测量、ICMP 协议的回显请求、连接保活、连接池大小的评估,以及连接池爆满的异常处理策略。这样的全面分析和深入探讨,必然可以让面试官眼前一亮,让你在众多候选人中脱颖而出。

- Book Lists -