从生产环境遇到的问题聊聊TCP设计思路

简介: 从生产环境遇到的问题聊聊TCP设计思路

当我们在学校学习网络和网络传输层时,我们可能总是感到枯草乏味、枯燥难懂。但事实上,在生产环境中,这是一个非常常见的问题,如果你不明白,可能会更困惑。


生产环境遇到的问题


谈谈我今年遇到的TCP层的几个问题。


  • 问题1:长短连接的选择?
  • 问题2:连接超时了,为什么超时的时间是128s左右
  • 问题3:系统不可达,80端口连不通了,可是本地查看80端口是正常的,这是为什么?
  • 问题4:客户端连接池很多处于CLOSE-WAIT?


传输层


要充分解释这些问题,我们需要对传输层的协议有非常深入的了解。网络层可能离软件开发人员有点远,但传输层,特别是广泛使用的TCP,与我们的工作密切相关。


传输层的目的


  • 从上到下依次包括 应用层、传输层、网络层、链路层、物理层。
  • 应用层就是对应不同的数据
  • 通过网络层,包已经能够正确的被路由到对应的主机

image.png

我们需要有机制将不同的应用程序映射到同一主机的网络层,并确保数据的准确到达。


区别不同的应用-UDP

image.png

  • 每个应用程序对应一个端口,端口信息也封装在包中,以确定数据包属于哪个应用的。
  • UDP的报文格式为传输层的简单协议,也很简单。

image.png

保证传输的质量-TCP


UDP不保证数据的准确传输和质量保证。如果包丢失,网络层不会重新传输。因此,传输层还设计了一种新的协议TCP。除了端口映射外,还在应用层和网络层之间增加了一些处理机制,以确保传输质量。


传输的质量对应用而言实际上就2个方面:

  • 收到了对端发出的所有数据。
  • 对端发出的所有数据都按顺序收到。

image.png

  • C1.C2表示两个客户端与app1有数据交互。
  • 假设app1向C2发送数据,app1的传输层应确保所有数据都发送到C2,以确保没有丢包事件。
  • 不丢包不是真的不丢包,而是如果丢包还能重传,保证数据最终收到。
  • 假设C1向app1发送数据,则需要按顺序正确接收发送的数据。

核心:Tcp的具体运行机制


TCP要干什么


依据以上的描叙,TCP主要的要做2件事:

  • 防止丢包

     a.丢包重传

       一般情况下,收到包后会向发送者发送ack信号。

       超时未收到会使用重传机制。

     b. 减少丢包

       告诉我你的窗口:感受对端的处理能力。

       丢包原因及优化:感受网络拥塞,控制传输速率。

  • 保证包到达的顺序。

       使用序列号:确保数据包按正确顺序交付。


基本试探-建立连接


正如上面提到的,TCP首先要试探两个对端的收发能力,试探过程如下:

image.png

主要是试探并确认了:

  • 确认A发送数据的能力
  • 确认B接收数据的能力
  • 确认B发送数据的能力
  • 确认A接收数据的能力


这个试探过程也叫做三次握手,通过三次握手两个应用程序之间建立了一条TCP连接。


具体的过程和状态


TCP连接是内核抽象给应用程序使用的。

我们可以更具体地看看这个过程,包括建立连接之前、中间和之后。

三次握手的状态随时间变迁图如下:

image.png

三次握手就是三次发包的过程:


  • 1、发起端发送SYNC包:SYN,seq=x,自己进入Sync-Sent状态
    注意:seq=x,下面还会有详细描述
  • 2、监听端收到SYNC包,发送SYN,ACK,seq=y,ack=x+1,进入Sync-RCVD状态
  • 3、发起端收到SYNC,ACK包,发送           ACK,seq=x+1,ack=y+1,进入Established状态
    a、RTT表示发送数据到收到ACK的时间
  • 4、监听端收到ACK包,进入Established状态


accept和LISTEN


服务器的内核使用accept系统调用来帮助建立连接。服务器调用accep函数后,处于LISTEN状态。可以等待客户端建立连接,并使用netstat查看LISTEN状态的连接。

# netstat -alpnt
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:11110           0.0.0.0:*  
  • *0.0.0.0:**表示接受网络上所有端口的连接。
  • 内核使用socket作为真正的tcp连接对象,但accept的socket是特殊的,没有建立tcp连接。


ESTABLISHED


三次握手成功后,这个连接将保存在内存中。

# netstat -alpnt
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 9.13.73.14:38604      9.13.39.104:3306       ESTABLISHED 26100/java          
tcp        0      0 9.13.73.14:32926      9.21.210.17:8080       ESTABLISHED 26100/java    
  • ESTABLISHED:已建立连接。
  • 连接的信息包括本地IP端口地址和远程IP端口。
    a.本地9.13.73.14:38604与远程9.13.39.104:3306建立连接。
    b.本地9.13.73.14:32926与远程9.21.210.17:8080建立连接。


对于问题3的回答


有一次,我们的服务无法到达,即80端口无法连接,但登录机器并发送80端口仍然是正常的列表状态,但我们发现许多连接处于Sync-RCVD状态。查看日志,我们发现系统已经运行,完全有理由怀疑服务建立连接的过程是由内存引起的。


因此,尽管80端口的LISTEN状态正常,但外部无法正常连接。


TCP报文格式和信息交换


三次握手中发的数据报都是TCP报文,TCP报文格式如下:

image.png

从这个报文格式和上面三次握手的过程可以看出:


  • SYN标记位为1说明这个报文是一个SYN类型的包,用于握手
  1. 发起端发送SYNC包:SYN,seq=x
  2. 监听端收到SYNC包后,也发送自己的SYN包:SYN,seq=y
  3. 发起端和监听端的起始序号x和y是32位序号,它们是系统随机生成的
  4. 在SYN报文中交换了初始序列号之后,这个序列号就一直单调递增
  5. 初始序列号ISN还用于关闭连接的吗?


  • ACK标记位为1说明这个报文是一个ACK类型的包
  1. 32位确认号
  2. 监听端收到SYNC包,还发送ACK,ack=x+1,小于x+1的全部字节已经收到,**期待下一次收到seq=x+1的包
  3. 发起端收到SYNC,发送ACK,ack=y+1,小于y+1的全部字节已经收到,期待下一次收到seq=y+1的包**


  • 用于SYN和ACK连接的包的数据为空


另外报文格式中还包含下面信息:


  1. 接收到的SYN包的窗口大小代表对方的接收窗口的大小
  2. RST标记位为1: 可以用于强制断开连接
  3. PSH标记位为1:告知对方这些数据包收到后应该马上交给上层的应用
  4. 选项中的MSS:TCP允许的从对方接收的最大报文段。


连接建立后,传输数据


连接建立后,数据可以传输。

回顾上述TCP主要保证的两件事和基本思路:

image.png

  • 使用序列号seq确保数据包按正确的顺序交付。
  • ack信号和超时重传机制防止丢包。
  • 用窗户减少丢包。


建立连接的过程为这件事情做好了铺垫。让我们来看看具体的运行机制。


超时重传和ACK深层含义


  • 发送出去的数据如果一直没收到ack就重传,需要确定多久时间没收到就重传

image.png

  • 接收端如果多次收到同一个seq的数据就丢弃
  • 需要大于RTT,又不能太大
  • RTT是在动态变化的,内核采样,使用RTO来计算


每当遇到一次超时重传时,都会将下一次超时间隔将设置为以前值的两倍。多次超时,表明网络环境差,不宜频繁重复发送,就会每次将成为原来的两倍,可设置最大重传次数。


现在就可以回答问题2了


问题2:连接超时了,为什么超时的时间是128s左右


初始1S超时,然后因为系统设置的重传次数是6,重传了6次,1+2+4+8++16+32+64加起来大约是128s左右。


可以批量提交ack

image.png

seq=4的ack没有正确的收到, 但如果在超时时间内收到了ack=6 则表示6之前的seq都已经正确接收到了 seq=4的数据也不会重传


滑动的窗口和右移的指针


发送端的socket缓冲区,示意图如下:

image.png

  • 已发送并收到ACK确认的数据
  • 已发送但未收到ACK确认的数据
  • 未发送但总大小在接收方处理范围内
  • 未发送但总大小超过接收方处理范围


窗口右移


  • 图示的发送窗口和收到ack报文中的window大小相关
  • ack指针右移则可用窗口变大
  • 发送seq指针右移则可用窗口变小


接收端的socket缓冲区,示意图如下:

image.png

  • ACK包,ack=16,且(window=16)
  • 已成功接收并确认的数据
  1. ack指针右移则可用窗口还是右移
  • 接收窗口是未收到数据但可以接收的数据
  1. 可用窗口和应用程序获取数据的能力有关,如果应用程序一直不从socket缓冲区获取数据,则接收窗口也会变得越来越小
  2. 滑动窗口并不是一成不变的。当接收方的应用读取数据的速度非常快的话,接收窗口可以很快的空缺出来。
  3. 通过使用窗口可以起到流控的作用,可以减少不必要的丢包,并减少网络拥塞。


顺序的保证


如下图:

image.png

假设接收端未收到16,17的报文, 儿后面18-27的数据都收到了, 则并不会发ack给发送方 当发送端超时重传16,17之后,且接收端收到之后 则接收端返回ack=28的报文给发送端。


  • ack指针只能右移,不能往回走
  • 这样可以保证顺序交付


传输的总结

TCP的主要功能是防止包丢失,保证包到达的顺序。本节通过描述TCP的具体工作机制证明了TCP确实达到了这一目的。


高并发系统和关闭连接的设计


最后,我完成了TCP连接建立和传输的基本原理和过程,认为我可以松一口气。


关闭连接是TCP的一部分,但与TCP相比,这并不重要。


但最近发现,在生产活动中,大家也非常关注TCP四次挥手的过程。主要原因是系统并发量大。


TCP连接是衡量系统并发量的重要因素,如果关闭连接异常,必然会影响系统的运行。


对于连接对并发量的影响,首先可以分析开头提出的问题。


长连接 VS 短链接


  • 短连接一般只会在 client/server间传递一次请求操作


  1. 这时候双方任意都可以发起close操作
  2. 短连接管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。
  3. 通常浏览器访问服务器的时候一般就是短连接。


  • 长连接
  1. Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
  2. 所以一条连接保持几天、几个月、几年或者更长时间都有可能,只要不出现异常情况或由用户(应用层)主动关闭。
  3. 长连接可以省去较多的TCP建立和关闭的操作,减少网络阻塞的影响,
  4. 减少CPU及内存的使用,因为不需要经常的建立及关闭连接。
  5. 连接数过多时,影响服务端的性能和并发数量。


所以,长短连接怎么选择呢?


  • 所以对于并发量大,请求频率低的,建议使用短连接。
  1. 对于服务端来说,长连接会耗费服务端的资源
  2. 如果有几十万,上百万的连接,服务端的压力会非常大,甚至会崩溃


  • 对于并发量小,性能要求高的,建议选择长连接
  1. 比如mysql连接池


TCP关闭连接


长短连接的选择如此重要,如果连接不能正确关闭,就会造成很大的麻烦。TCP设计了完善的关闭机制,关闭连接过程如下:

image.png

  • 关闭连接发起方 发起第一个FIN,处于FIN-WAIT1
  • 关闭连接被动方的内核代码回复ACK,此时还可以发送数据,处于Close-WAIT状态
  1. 关闭连接被动方等待应用程序发送FIN,如果上层应用一直不发FIN,就还可以继续发送数据


  • 关闭连接发起方收到ACK后处于FIN-WAIT2状态,还可以接收数据自己不再发送数据
  • 关闭连接被动方直到应用程序发出FIN,处于LAST-ACK状态
  • 关闭连接发起方收到FIN,会发送ACK,自己会处于TIME-WAIT状态,此时
  1. 若是关闭连接被动方收到ack,就close连接
  2. 若是是关闭连接被动方没收到ack,则会重传FIN


TIME-WAIT等多长时间


MSL是报文最大的生存时间。它是任何报文在网络上存在的最长时间。超过这个时间,报文将被丢弃,即MSL的两倍。TCP的TIME_WAIT状态也称为2MSL等待状态。

image.png

等待2MSL时间的主要目的是害怕对方没有收到最后一个ACK包,所以对方会在超时后重发第三次握手的FIN包。

image.png

若之前交互异常,收到重传的FIN最多使用2MSL,因此结论是等待2MSL的时间。


最后一个问题


有一次,同步数据的应用从一个服务器并发同步大量数据,这个过程比较缓慢,所以考虑如何加快同步。


  • 首先查看网络连接,发现20个连接的连接池中有很多处于close_wait状态的连接。


通过以上分析,我们知道Close-WAIT是对方可能认为连接空闲时间太长而关闭的连接,但我在这里使用的连接池还没有发送FIN释放包。


可见看出,连接的数量并没有成为系统的瓶颈,我们可以继续增加并发线程的数量,以增加并发量。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
Ubuntu
ubuntu报错:OpenSSL is not properly installed on your system.
ubuntu报错:OpenSSL is not properly installed on your system.
318 0
|
存储 负载均衡 算法
什么是Minio?如何搭建Minio集群?
MinIO 是高性能的对象存储,是为海量数据存储、人工智能、大数据分析而设计的,它完全兼容Amazon S3接口,单个对象最大可达5TB,适合存储海量图片、视频、日志文件、备份数据和容器/虚拟机镜像等。MinIO主要采用Golang语言实现,,客户端与存储服务器之间采用http/https通信协议。
2754 0
什么是Minio?如何搭建Minio集群?
|
9月前
|
Java 关系型数据库 数据库连接
简单易懂的 MyBatis 分库分表方案
本文介绍了一种基于 MyBatis 框架的数据库分库分表方案——shardino。不同于复杂插件方式,该方案通过客户端代码包装实现简便易懂的操作,显式处理分库分表逻辑,确保开发者清晰了解数据分布。项目地址:[https://github.com/pyloque/shardino](https://github.com/pyloque/shardino)。方案中,帖子表按 userId 字段 hash 分为 64 张表,平均分配到多个主从库中,配置文件管理 MySQL 组对象,支持读写分离和权重随机选择从库。代码示例展示了如何计算 partition number 并进行具体操作。
260 22
简单易懂的 MyBatis 分库分表方案
|
7月前
|
存储 人工智能 算法
通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
阿里云向量检索服务Milvus 2.5版本在全文检索、关键词匹配以及混合检索(Hybrid Search)方面实现了显著的增强,在多模态检索、RAG等多场景中检索结果能够兼顾召回率与精确性。本文将详细介绍如何利用 Milvus 2.5 版本实现这些功能,并阐述其在RAG 应用的 Retrieve 阶段的最佳实践。
1436 1
通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
|
关系型数据库 MySQL Nacos
nacos启动报错 load derby-schema.sql error
这篇文章描述了作者在使用Nacos时遇到的启动错误,错误提示为加载derby-schema.sql失败,作者通过将数据库从Derby更换为MySQL解决了问题。
nacos启动报错 load derby-schema.sql error
|
存储 SQL 关系型数据库
MySQL中的update操作与锁机制
本文探讨MySQL中`UPDATE`操作的自动加锁机制及其对数据一致性的保障作用。尤其在InnoDB存储引擎下,系统会在涉及索引的更新操作中加行锁或间隙锁,防止多事务并发修改同一条记录。通过福利码兑换系统的实例展示,当线程A开启事务更新库存时,线程B试图更新相同记录会被阻塞,直至线程A提交。此外,文章还介绍了乐观锁及版本号控制等策略进一步提升并发性能的方法。作者:小明爱吃火锅,来源:稀土掘金。
661 2
|
SQL 分布式计算 Hadoop
org.apache.hadoop.security.AccessControlException Permission denied: user=anonymous, access=WRITE...
在尝试通过 HiveServer2 远程执行 DDL 操作时遇到权限错误,错误信息显示匿名用户(`anonymous`)无权执行写操作。解决方案包括:1) 使用 `hdfs dfs -chmod -R +777 /warehouse` 给目录授权(不推荐,仅适用于测试环境);2) 配置 Hive 和 Hadoop,创建具有权限的用户,如 `ad`,并将该用户添加到 Hive 的管理员角色和 Hadoop 的 proxyuser 配置中,然后重启相关服务。通过 `beeline` 测试连接和操作,确认权限问题已解决。
909 0
|
运维 监控 安全
园区网典型组网架构及案例实践
园区网典型组网架构及案例实践
|
存储 JavaScript 前端开发
探索 Pinia:简化 Vue 状态管理的新选择(上)
探索 Pinia:简化 Vue 状态管理的新选择(上)
探索 Pinia:简化 Vue 状态管理的新选择(上)
|
SQL 存储 容灾
关于主从延迟,一篇文章给你讲明白了!
在实际的生产环境中,由单台MySQL作为独立的数据库是完全不能满足实际需求的,无论是在安全性,高可用性以及高并发等各个方面 因此,一般来说都是通过集群主从复制(Master-Slave)的方式来同步数据,再通过读写分离(MySQL-Proxy)来提升数据库的并发负载能力进行部署与实施总结MySQL主从集群带来的作用是:提高数据库负载能力,主库执行读写任务(增删改),备库仅做查询。提高系统读写性能、可扩展性和高可用性。数据备份与容灾,备库在异地,主库不存在了,备库可以立即接管,无须恢复时间。用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。可以简单理解为记录的就是sq
2084 0