涤生

1 分钟阅读

介绍

  • 长连接

首先这里所说的连接是指网络传输层的使用 TCP 协议经过三次握手建立的连接;长连接是指建立的连接长期保持,不管此时有无数据包的发送;有长连接自然也有短连接,短连接是指双方有数据发送时,就建立连接,发送几次请求后,就主动或者被动断开连接。

  • 心跳

心跳这个名字比较形象,就像人体心跳一样,是用来检测一个系统是否存活或者网络链路是否通畅的一种方式,其一般做法是定时向被检测系统发送心跳包,被检测系统收到心跳包进行回复,收到回复说明对方存活。

心跳和长连接在一起介绍的原因是,心跳能够给长连接提供保活功能,能够检测长连接是否正常(这里所说的保活不能简单的理解为保证活着,具体来说应该是一旦链路死了,不可用了,能够尽快知道,然后做些其他的高可用措施,来保证系统的正常运行)。

优势

长连接的优势

  • 减少连接建立过程的耗时

大家都知道 TCP 连接建立需要三次握手,三次握手也就说需要三次交互才能建立一个连接通道,同城的机器之间的大概是ms级别的延时,影响还不大,如果是北京和上海两地机房,走专线一来一回大概需要30ms,如果使用长连接,这个优化还是十分可观的。

  • 方便实现 push 数据

数据交互-推模式实现的前提是网络长连接,有了长连接,连接两端很方便的互相push数据,来进行交互。

疑问

  • TCP 连接到底是什么?

所谓的 TCP 连接不是物理的连接,是为了实现数据的可靠传输由通信双方进行三次握手交互而建立的逻辑上的连接,通信双方都需要维护这样的连接状态信息。比如 netstat 经常看到连接的状态为 ESTABLISHED,表示当前处于连接状态。(这里需要注意的是这个 ESTABLISHED 的连接状态只是操作系统认为当前还处在连接状态)

  • 是不是建立了长连接,就可以高枕无忧了呢?

建立好长连接,两端的操作系统都维护了连接已经建立的状态,是不是这时向对端发送数据一定能到达呢? 答案是否定的。 可能此时链路已经不通,只是 TCP 层还没有感知到这一信息,操作系统层面显示的状态依然是连接状态,而且因为 TCP 层还认为连接是 ESTABLISHED,所以作为应用层自然也就无法感知当前的链路不通。 这种情况会导致什么问题? 如果此时有数据想要传输,显然,数据是无法传送到对端,但是 TCP 协议为了保证可靠性,会重传请求,如果问题只是网线接头松了,导致网络不通,此时如果及时将网线接头接好,数据还是能正常到达对端,且 TCP 的连接依然是 ESTABLISHED,不会有任何变化。但不是任何时候都这么巧,有时就是某段链路瘫痪了,或者主机挂了,系统异常关闭了等。这时候如果应用系统不能感知到,是件很危险的事情。

  • 长连接怎么保活?

TCP 协议实现中,是有保活机制的,也就是 TCP的 KeepAlive 机制(此机制并不是 TCP 协议规范中的内容,由操作系统去实现),KeepAlive 机制开启后,在一定时间内(一般时间为 7200s,参数 tcp_keepalive_time)在链路上没有数据传送的情况下,TCP 层将发送相应的 KeepAlive 探针以确定连接可用性,探测失败后重试 10(参数 tcp_keepalive_probes)次,每次间隔时间 75s(参数 tcp_keepalive_intvl),所有探测失败后,才认为当前连接已经不可用。这些参数是机器级别,可以调整。

  • 应用层需要做点什么吗?

按照 TCP 的 KeepAlive 机制,默认的参数,显然不能满足要求。那是不是调小点就可以了呢? 调整参数,当然是有用的,但是首先参数的机器级别的,调整起来不太方便,更换机器还得记得调整参数,对系统的使用方来说,未免增加了维护成本,而且很可能忘记;其次由于 KeepAlive 的保活机制只在链路空闲的情况下才会起到作用,假如此时有数据发送,且物理链路已经不通,操作系统这边的链路状态还是 ESTABLISHED,这时会发生什么?自然会走 TCP 重传机制,要知道默认的 TCP 超时重传,指数退避算法也是一个相当长的过程。因此,一个可靠的系统,长连接的保活肯定是要依赖应用层的心跳来保证的。

这里应用层的心跳举个例子,比如客户端每隔 3s 通过长连接通道发送一个心跳请求到服务端,连续失败 5 次就断开连接。这样算下来最长 15s 就能发现连接已经不可用,一旦连接不可用,可以重连,也可以做其他的 failover 处理,比如请求其他服务器。 应用层心跳还有个好处,比如某台服务器因为某些原因导致负载超高,CPU 飙高,或者线程池打满等等,无法响应任何业务请求,如果使用 TCP 自身的机制无法发现任何问题,然而对客户端而言,这时的最好选择就是断连后重新连接其他服务器,而不是一直认为当前服务器是可用状态,向当前服务器发送一些必然会失败的请求。

设计误区

  • 无心跳

无心跳的设计,也是很常见的,为了省事,长连接断开,TCP 传输层有通知,应用程序只要处理这种通知,一旦发现连接异常,就重连。但是此类通知可能来的特别晚,比如在机器奔溃,应用程序异常退出,链路不通等情况下。

  • 被连接方检测心跳

心跳的实现分为心跳的发送和心跳的检测,心跳由谁来发都可以,也可以双方都发送,但是检测心跳,必须由发起连接的这端进行,才安全。因为只有发起连接的一端检测心跳,知道链路有问题,这时才会去断开连接,进行重连,或者重连到另一台服务器。 例如,client 去连接 server,client 定时发送心跳到 server,server 检测心跳,发现一段时间 client 没有传心跳过来,认为与 client 的链路已经出了问题或者 client 自身就已经出了问题。粗看上去貌似没什么问题,但是如果只是 client 与当前这个 server 之间的链路出了问题,作为一个高可用的系统,是不是应该还有另一个 server 作为备选,问题出在短时间内client根本不知道自己和第一个 server 出了问题,所以也不会主动去连接第二个 server。

  • 第三方心跳

还有一类心跳,使用第三方保活,也就是除了客户端和服务端之外,还有另一台机器,定时发送心跳去探测服务端的存活。这类探活方法使用在检测系统的存活与否的问题上是没有问题的,但是这类设计是无法用来检测客户端和服务端之间链路的好坏。

参考方案

  • 方案一

最简单的策略当然是客户端定时 n 秒发送心跳包,服务端收到心跳包后,回复客户端的心跳,如果客户端连续 m 秒没有收到心跳包,则主动断开连接,然后重连,将正常的业务请求暂时不发送的该台服务器上。

  • 方案二

可能有人觉得,这样是不是传送一些无效的数据包有点多,是不是可以优化下,说实话,个人认为其实一点也不多。当然是可以做些优化的,因为心跳就是一种探测请求,业务上的正常请求除了做业务处理外,还可以用作探测的功能,比如此时有请求需要发送到服务端,这个请求就可以当作是一次心跳,服务端收到请求,处理后回复,只要服务端有回复,就表明链路还是通的,如果客户端请求比较空闲的时候,服务端一直没有数据回复,就使用心跳进行探测,这样就有效利用了正常的请求来作为心跳的功能,减少无效的数据传输。


涤生的博客。

转载请注明原创出处,谢谢!

欢迎关注我的微信公众号:「涤生的博客」,获取更多技术分享。

涤生-微信公共号