您现在的位置是:首页 >技术杂谈 >TCP协议在IM场景中涉及核心问题以及解决方案梳理(由此深入理解TCP底层原理)网站首页技术杂谈
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就一定不会丢包吗?_哔哩哔哩_bilibili]
1. 各种原因导致TCP断连、或者网络抖动、延时高就有可能丢包
2. (本质上不是TCP本身丢的包也不叫丢包)TCP只保证传输层消息可靠性,不保证应用层消息可靠(如:消息到了传输层,此时还没有交付到业务,突然内存或者cpu满了,或者没电了等各种原因导致进程意外退出,没有收到消息)。
- 业务ack背景
没有ACK
,数据传给TCP
后就失控了,如果这时候出现掉线、断网等情况,很容易造成消息丢失,无法追溯,因此我们针对每种请求都增加ACK
响应,解决了无法监控消息传输情况的问题。
由于网络环境的不可预测性和复杂性,TCP协议无法完全避免数据的丢失或损坏,因此在实际应用中,业务常常需要通过ACK(确认消息)来确保数据的可靠传输。
当发送方发送数据时,接收方需要向发送方发送一个ACK确认消息,告知发送方已经正确接收到了数据。如果发送方在一定时间内没有收到ACK确认消息,就会认为数据丢失了或者出现了别的问题,需要进行重传。因此,ACK确认消息可以帮助发送方及时发现数据的丢失或损坏,从而加强了数据的可靠传输。
此外,业务需要ACK确认消息还可以用于解决网络延迟等问题。如果数据在传输过程中出现了延迟,ACK确认消息可以帮助发送方及时发现并对数据进行重传,从而避免了数据的丢失和延迟。
总之,在TCP协议之上,业务需要使用ACK确认消息来保证数据的可靠传输,从而确保业务的正常运行。
- 业务下行空洞原理、下行消息乱序的原因
- 服务端分布式存储,同一个Session的消息在同一个集群,但是可能分布在不同的机器。异常情况会有乱序、空洞的情况发生,所以需要SDK做兜底处理。
- 客户端掉线后最后一条收到的消息为x,当客户端重连时收到的消息可能时x+n(掉线过程中有很多消息没有成功接收),此时就造成了消息空洞。
- TCP队列头阻塞核心原理以及为啥用QUIC
这个问题是长连接面临的最严重问题。在弱网环境、丢包率较高的场景下,消息的延迟时间会非常大。其主要原因是,在我们正常的TCP连接请求中,由于TCP协议的可靠性和一致性保障,一个请求中某一个包的丢失,会导致后续已到达的数据包延迟交付给业务层(指的是接收端需要保证消息有序所以等待导致延迟交付,跟发送端无关),直到丢失包经过重传恢复。
对于QUIC而言,由于其采用UDP,完全不会出现上面的问题,其带宽占用相对 TCP 而言会变多,以一定的带宽消耗提高消息的实时性是完全可行的。另外,相较于TCP的流量控制,QUIC在 Connection和 Stream 两个级别分别进行流控。
- 批量ACK 逻辑
- 是为了减少上行实时ack消息对mars单TCP链路压力(影响到当前链路其他更高优先级消息实时性)
- 减少服务器压力
- 单个端口可以承载多条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
结合当前各个会话的情况,将该客户端掉线期间丢失的消息重传,保证在该客户端掉线期间的消息不会丢失。