TCP协议在IM场景中涉及核心问题以及解决方案梳理(由此深入理解TCP底层原理)

TCP工程问题扫盲先看下这个[TCP连接的状态详解以及故障排查_tcp 连接时断时续_hguisu的博客-CSDN博客]

核心:TCP保证可靠的前提是链接能正常保持,如果各种原因导致链接断开。那链接会重置,之前承载的业务需要业务方自己保证可靠性、一致性;另外,在此基础上TCP只保证传输层可靠性跟一致性,更上层也需要业务来保证。

  • TCP协议是一种可靠的协议,但是在实际应用中,仍然需要应用程序来处理一些细节,以确保数据能够按照预期的方式传输。一些业务可能需要在TCP层面之上实现自己的可靠性机制,例如在应用层实现消息确认机制,以确保每个消息都能够被正确地接收。为什么?怎么做?

        TCP协议确实是一种可靠的协议,它提供了许多功能来确保数据的可靠传输,例如序列号、确认号、重传机制等。然而,在实际应用中,由于网络环境的复杂性和不可预测性,TCP协议无法完全保证数据的可靠传输。具体原因如下:

        1. TCP只保证传输层消息可靠性,不保证应用层消息可靠(如:消息到了传输层,此时还没有交付到业务,突然内存或者cpu满了等各种原因导致进程意外退出或者服务挂了,没有收到消息)。
        
2. 当遇到断网或者断电等异常导致TCP链接断开了,等到链接恢复的时候业务已经失效了。试想此时如果业务没有重传数据包,那可不就丢了吗?

        3. 当网络出现拥塞或丢包时,TCP协议会启动重传机制来保证数据的可靠传输。但是,TCP协议并不知道应用程序期望的数据传输方式,例如应用程序可能期望数据的实时传输,重传机制可能会导致数据的延迟传输,从而影响应用程序的性能。

        应用层实现自己的可靠性机制可以使应用程序更加灵活和可靠,同时也可以根据应用程序的特殊需求来进行优化和定制。具体做法如:根据业务场景使用TCP的KeepAlive机制或者协商心跳机制制定策略。

  • tcp发出去的包可能经过不同的路由,到了接收端怎么合包并交付

    在TCP协议中,数据被拆分成多个数据包进行传输,每个数据包都有一个序号和校验和,接收端通过这些信息来确认数据包的完整性和顺序,并将它们合并成完整的数据流交付给应用程序。

    当TCP数据包经过不同的路由时,可能会出现乱序、丢失、重复等情况。为了解决这些问题,TCP协议使用了一些机制来保证数据传输的可靠性和有序性,例如:

    • 序号:每个TCP数据包都有一个序号,用于标识数据包在数据流中的位置。接收端通过检查序号来确认数据包的顺序,并将它们按照正确的顺序合并成完整的数据流。

    • 确认应答:当接收端收到一个TCP数据包时,会向发送端发送一个确认应答,告诉发送端已经成功接收到该数据包。如果发送端没有收到确认应答,就会重新发送该数据包,直到接收端发送确认应答为止。

    • 滑动窗口:TCP协议使用滑动窗口机制来控制发送端发送数据包的速度和接收端接收数据包的速度。发送端根据接收端返回的确认应答来调整滑动窗口大小,控制发送速度;接收端根据自己的处理能力和缓存大小来调整滑动窗口大小,控制接收速度

          1. 各种原因导致TCP断连、或者网络抖动、延时高就有可能丢包(是指在网络传输过程中出现了数据包丢失的情况,但是 TCP 协议会通过重传机制和校验机制来保证数据的可靠传输。

          2. (本质上不是TCP本身丢的包也不叫丢包)TCP只保证传输层消息可靠性,不保证应用层消息可靠(如:消息到了传输层,此时还没有交付到业务,突然内存或者cpu满了,或者没电了等各种原因导致进程意外退出,没有收到消息)。

        TCP 协议是一种可靠的传输协议,它通过多种机制来保证数据传输的可靠性。在 TCP 协议中,当发送方发送数据时,会将数据分割成多个数据包,并为每个数据包分配一个序列号。接收方收到数据包后,会向发送方发送一个 ACK 应答,确认已经接收到该数据包。如果发送方在一定时间内没有收到 ACK 应答,则会认为该数据包丢失,并触发重传机制。

        因此,当 TCP 发送方发送数据时,如果数据包丢失,会触发重传机制,直到接收方收到数据并发送 ACK 应答为止。即使在网络传输过程中出现了丢包情况,TCP 协议也会通过重传机制来保证数据的可靠传输。

        在 TCP 协议中,接收方也会采取一些措施来保证数据的可靠传输。例如,在接收方收到数据包时,会校验数据包的校验和,以确保数据包没有被篡改。如果发现数据包有误,则会向发送方发送 NACK 应答,请求重新发送该数据包。

        因此,TCP 所谓的丢包是指在网络传输过程中出现了数据包丢失的情况,但是 TCP 协议会通过重传机制和校验机制来保证数据的可靠传输。在 TCP 协议中,发送端和接收端都会采取一些措施来保证数据的可靠传输。

  • 业务ack背景

        没有ACK,数据传给TCP后就失控了,如果这时候出现掉线、断网等情况,很容易造成消息丢失,无法追溯,因此我们针对每种请求都增加ACK响应,解决了无法监控消息传输情况的问题。
        由于网络环境的不可预测性和复杂性,TCP协议无法完全避免数据的丢失或损坏,因此在实际应用中,业务常常需要通过ACK(确认消息)来确保数据的可靠传输。
        当发送方发送数据时,接收方需要向发送方发送一个ACK确认消息,告知发送方已经正确接收到了数据。如果发送方在一定时间内没有收到ACK确认消息,就会认为数据丢失了或者出现了别的问题,需要进行重传。因此,ACK确认消息可以帮助发送方及时发现数据的丢失或损坏,从而加强了数据的可靠传输。
        此外,业务需要ACK确认消息还可以用于解决网络延迟等问题。如果数据在传输过程中出现了延迟,ACK确认消息可以帮助发送方及时发现并对数据进行重传,从而避免了数据的丢失和延迟。

        总之,在TCP协议之上,业务需要使用ACK确认消息来保证数据的可靠传输,从而确保业务的正常运行。

  • 业务下行空洞原理、下行消息乱序的原因
  1. 服务端分布式存储,同一个Session的消息在同一个集群,但是可能分布在不同的机器。异常情况会有乱序、空洞的情况发生,所以需要SDK做兜底处理。
  2. 客户端掉线后最后一条收到的消息为x,当客户端重连时收到的消息可能时x+n(掉线过程中有很多消息没有成功接收),此时就造成了消息空洞。

  • TCP队列头阻塞核心原理以及为啥用QUIC

        队头阻塞主要体现在两个点:

                1. 滑动窗口导致的,接收端接网络拥塞严重或者缓冲区不足的情况下对窗口大小调整,导致发送端发送数据阻塞。

                2. 当发送端网络拥塞或者抖动严重的话,比如说滑动窗口发送某个包超时重传,那么窗口队列后面的包将被阻塞在发送端的缓冲区里面,直到前面的数据包发送成功后才能被发送,从而造成发送端阻塞。

                以上问题是长连接面临的最严重问题。正常的TCP连接请求中,由于TCP协议的可靠性和一致性,在弱网环境、丢包率较高的场景下,消息的延迟时间会非常大。即便网络恢复数据正常传输了,也会导致后续已到达的数据包延迟交付给业务层。

       
        对于QUIC而言,由于其采用UDP,完全不会出现上面的问题,其带宽占用相对 TCP 而言会变多,以一定的带宽消耗提高消息的实时性是完全可行的。另外,相较于TCP的流量控制,QUIC在 Connection和 Stream 两个级别分别进行流控。具体细节见【QUIC原理及使用_quic 重传_John_ToDebug的博客-CSDN博客

  • 批量ACK 逻辑
  1. 是为了减少上行实时ack消息对mars单TCP链路压力(影响到当前链路其他更高优先级消息实时性)
  2. 减少服务器压力

  • 单个端口可以承载多条tcp长链接

        单个端口可以同时承载数千个TCP连接,具体取决于服务器的硬件配置,操作系统的设置,网络带宽等因素。
        在实际应用中,可以通过调整服务器的网络设置、调整操作系统的参数等方式来优化服务器的性能,从而提高单个端口能够承载的TCP连接数。例如,可以调整TCP/IP协议的参数,优化网络带宽的使用效率,或者使用多线程或多进程的方式来处理连接请求,等等。
        此外,对于高并发的应用,也可以使用负载均衡技术来分担服务器的负载,从而进一步提高单个端口能够承载的TCP连接数。

  • TCP长连接为什么要时分多路复用?

        如果客户端为每个业务都创建一条TCP长连接的话,那随着业务数量的增加,需要维护的长连接会越来越多。对于某些业务,可能一天也传输不了几条消息,但是还要一直维持着长连接,这些长连接会消耗大量的电量、流量、带宽、内存等,这对于手机这类设备是非常不友好的,为了解决这类问题,我们开发了TCP长连接时分多路复用的技术,既在一条TCP长连接上多个业务同时共存,业务之间相互透明,数据互不干扰

  • 上行消息可靠性保障

        上行消息是指客户端发送到服务端的消息,对于上行消息,我们在SDK内部维护了一个发送缓冲区,在消息发送到服务端之前会先将该消息保存到缓冲区中,每一条消息我们都需要服务端返回ACK或者超过该消息的生命周期后才从缓冲区中将该消息删除,超时或者掉线未收到服务端响应的话,SDK会定期重传。

        有了消息重传那就必须要有消息去重,不然的话有可能会造成消息重复,为了避免出现消息重复的问题,我们的客户端SDK会维护一个单调递增的序列号,通过该序列号来去重。服务端在收到一条新消息后,会将该消息的序列号记录下来,当再次收到小于或等于该序列号的消息的时候,会认为这是一条重复的消息,直接丢弃,保证消息不重复。

        上行的每一条消息的发送结果都会返回给业务层,当消息发送失败后,业务层可以根据自己的需求来决定是否重传或者采取一些其他的补救措施,充分来保证上行消息的可靠性。

  • 下行消息可靠性保障

        下行消息是指服务端给客户端转发的消息,对于这类消息,我们设计了一套会话内局部有序的序列ID的机制,这里的会话是指一个聊天室、一组单聊等,在一组会话内会生成递增的独立的序列号,会话之间的序列号没有关系,这种方式可以大幅度提升服务端发号的性能,但是服务端下发的序列号不是连续递增的,有跳跃,为了解决这个问题,我们设计了一套类似单向链表的结构,与传统的链表相比,这个链表是逆序的,会话内的每条消息都保存了一个前序的SeqId,本条消息的SeqId,全局唯一消息Id(用于追踪消息)。客户端在收到会话内的第一条消息的时候,会记录下该消息的SeqId,后续收到该会话的新消息的时候,会检测新消息的SeqId,如果新消息的SeqId小于等于上一次收到的消息的SeqId,则证明这条消息重复了,否则的话则检测新消息的前序SeqId,如果新消息的前序SeqId和上次收到的消息的SeqId一致,则证明两条消息是连续的,直接将该消息返回给业务层。如果不一致,则说明两条消息之间有空洞,这时候客户端会一段时间,如果两条消息之间的空洞一直补不上的话,客户端还会主动去服务端拉取两条消息之间的内容,以保证消息有序,不丢失。

        掉线重连后,客户端也会将每个会话的最后收到的SeqId传给服务端,服务端会根据客户端传输的这些SeqId结合当前各个会话的情况,将该客户端掉线期间丢失的消息重传,保证在该客户端掉线期间的消息不会丢失。

  • 使用多条TCP长连接相对于单条TCP长连接的好处

    • 提高并发性能:使用多条TCP长连接可以使客户端在同一时间与服务器建立多个连接,从而提高并发处理能力。这对于高并发的场景非常有用,例如 Web 服务器、消息队列等

    • 减少延迟:使用多条TCP长连接可以避免单条TCP长连接中的队头阻塞问题,从而减少延迟。在单条TCP长连接中,如果前面的请求处理时间过长,会导致后面的请求被阻塞,从而增加整体延迟。使用多条TCP长连接可以将请求分散到多个连接中,避免队头阻塞问题,从而减少延迟。

    • 提高可靠性:使用多条TCP长连接可以提高系统的可靠性。如果单条TCP长连接出现故障,会导致整个系统不可用。而使用多条TCP长连接可以将请求分散到多个连接中,即使其中一条连接出现故障,也不会影响整个系统的可用性。

        需要注意的是,使用多条TCP长连接也会带来一些问题,例如增加服务器端的负担、增加网络带宽占用等。因此,在实际应用中需要根据具体情况进行权衡和选择。

        

Q&A

        Q: 网络资源是一定的

        A: 是的,网络资源是有限的,使用多条 TCP 长连接也会占用一定的网络资源。因此,在实际应用中,需要根据具体情况进行权衡和选择。

        如果同时使用多条 TCP 长连接,可能会导致网络拥塞和资源竞争,从而降低系统的性能和可靠性。此外,如果使用多条 TCP 长连接时,每个连接的数据量都很小,那么建立和关闭连接的开销可能会超过传输数据的开销,从而浪费网络资源。

        因此,在实际应用中,需要根据具体情况进行权衡和选择。如果需要提高系统的并发性能和可靠性,可以考虑使用多条 TCP 长连接;如果网络资源有限,可以考虑使用单条 TCP 长连接,并通过其他方式来提高系统的性能和可靠性,例如使用连接池、优化请求处理流程等。

        Q: 每条TCP链路拥塞是独立的吗

        A: 是的,每条 TCP 链路都是独立的,它们之间的拥塞控制是相互独立的。

        当网络出现拥塞时,TCP 协议会根据网络的拥塞程度和传输数据的速率来调整发送数据的速率。如果一个 TCP 链路出现拥塞,TCP 协议会减少该链路的发送速率,从而避免拥塞程度进一步加剧。此时,其他 TCP 链路的发送速率不会受到影响,它们仍然可以继续以最大速率发送数据。

        需要注意的是,虽然每条 TCP 链路都是独立的,但它们共享同一个网络资源。因此,当网络出现拥塞时,所有的 TCP 链路都可能受到影响,从而导致传输延迟增加、丢包率增加等问题。为了避免这种情况,需要采取一些措施来优化网络资源的利用,例如使用拥塞控制算法、合理设置发送数据的速率等。

        Q: 当网络拥塞的时候,不应该是每条tcp链路都会拥塞吗

        A: 是的,当网络出现拥塞时,每条 TCP 链路都可能受到影响,从而导致传输延迟增加、丢包率增加等问题。因此,在网络拥塞时,每条 TCP 链路都有可能出现拥塞现象。

        TCP 协议会根据网络的拥塞程度和传输数据的速率来调整发送数据的速率。当网络出现拥塞时,TCP 协议会减少发送速率,从而避免拥塞程度进一步加剧。由于每条 TCP 链路都是独立的,因此 TCP 协议会对每条链路进行拥塞控制,以避免链路出现拥塞现象。

        需要注意的是,当网络出现拥塞时,所有的 TCP 链路都共享同一个网络资源。因此,在网络拥塞时,所有的 TCP 链路都可能受到影响。为了避免这种情况,需要采取一些措施来优化网络资源的利用,例如使用拥塞控制算法、合理设置发送数据的速率等。

        Q: 什么情况会出现tcp链路a拥塞,tcp链路b不拥塞的情况

        A: 在网络拥塞的情况下,每条 TCP 链路都可能出现拥塞现象。然而,由于每条 TCP 链路的传输特性和传输数据的特点可能不同,因此在实际情况下,可能会出现一条 TCP 链路出现拥塞,而另一条 TCP 链路没有出现拥塞的情况。

        例如,假设 TCP 链路 A 和 B 分别传输的数据量不同,链路 A 传输的数据量更大,链路 B 传输的数据量较小。当网络出现轻微的拥塞时,链路 A 可能会出现拥塞现象,而链路 B 可能不会受到影响。此时,TCP 协议会对链路 A 进行拥塞控制,减少其发送速率,从而避免拥塞程度进一步加剧。而链路 B 的发送速率不会受到影响,因为它没有出现拥塞现象。

        需要注意的是,当网络出现严重的拥塞时,所有的 TCP 链路都可能受到影响,从而导致传输延迟增加、丢包率增加等问题。因此,在实际应用中,需要采取一些措施来优化网络资源的利用,例如使用拥塞控制算法、合理设置发送数据的速率等。

        Q: TCP发送完数据一直收不到对端的ack,并且多次重试完毕后,会丢掉当前数据包,继续发送下一条消息吗

        A: 当 TCP 发送完数据后,如果一直没有收到对端的 ACK 应答,并且多次重试完毕后仍然没有收到 ACK 应答,TCP 协议会认为该数据包已经丢失,并触发重传机制。TCP 协议会进行多次重传,如果多次重传仍然失败,则会放弃该数据包的发送,并继续发送下一条消息。

在 TCP 协议中,当发送方发送数据时,会等待接收方的 ACK 应答。如果在一定时间内没有收到 ACK 应答,则会重新发送数据。如果多次重传仍然没有收到 ACK 应答,则会放弃该数据包的发送,并继续发送下一条消息。这样可以确保数据的可靠传输,同时也能够避免网络拥塞和负载过大等问题。

        Q: 当 TCP 发送完数据后,如果一直没有收到对端的 ACK 应答,并且多次重试完毕后仍然没有收到 ACK 应答,TCP 协议会认为该数据包已经丢失,并触发重传机制。TCP 协议会进行多次重传,如果多次重传仍然失败,则会放弃该数据包的发送,并继续发送下一条消息。这样的话,到达接受端的数据可能存在空洞的情况

        A: 是的,当 TCP 发送方发送数据时,如果数据包丢失,会触发重传机制,直到接收方收到数据并发送 ACK 应答为止。如果发送方多次重传仍然没有收到 ACK 应答,则会放弃该数据包的发送,并继续发送下一条消息。这种情况下,接收方可能会出现消息空洞的情况。

        当 TCP 协议中出现消息空洞时,接收方可以使用 TCP 协议的滑动窗口机制来处理。滑动窗口机制可以控制接收方的缓存空间,确保接收方只接收有序的数据包,并且可以缓存一定数量的数据包,以便后续接收。此外,TCP 协议还可以使用序列号来保证数据包的有序性,确保接收方按照正确的顺序接收数据包。

        如果发送方在重传多次后仍然没有收到 ACK 应答,则可能是由于网络故障或者接收方已经关闭连接等原因导致。在这种情况下,发送方可以根据具体情况选择重新建立连接或者放弃数据包的发送。

おすすめ

転載: blog.csdn.net/John_ToStr/article/details/130305411