表面上我是个技术博主。
但没想到今天成了个情感博主。
我是没想到有一天,我会通过技术知识,来挽救粉丝即将破碎的感情。
掏心窝子的说。这件事情多少是沾点功德无量了。
事情是这样的。
最近就有个读者加了我的绿皮聊天软件,女生,头像挺好看的,就在我以为她要我拉她进群发成人专升本广告的时候。
画风突然不对劲。
她说她男朋友也是个程序员,异地恋,也关注了我,天天研究什么TCP,UDP网络。一研究就是一晚上,一晚上都不回她消息的那种。
话里有话,懂。
不出意外的出了意外,她发出了灵魂拷问
"你们程序员真的有那么忙吗?忙到连消息都不知道回。"
没想到上来就是一记直拳。
但是,这一拳,我接住了。
我很想告诉她"分了吧,下一题"。
但我不能。因为这样我就伤害了我的读者兄弟。
沉默了一下。
单核cpu都快转冒烟了,才颤颤巍巍在九宫格键盘上发出消息。
再回慢一点,我就感觉,我要对不起我这全日制本科学历了。
"其实,他已经回了你消息了,但你知道吗?网络是会丢包的。"
"我来帮他解释下,这个话题就要从数据包的发送流程聊起"
数据包的发送流程
首先,我们两个手机的绿皮聊天软件客户端,要通信,中间会通过它们家服务器。大概长这样。
聊天软件三端通信
但为了简化模型,我们把中间的服务器给省略掉,假设这是个端到端的通信。且为了保证消息的可靠性,我们盲猜它们之间用的是TCP协议进行通信。
聊天软件两端通信
为了发送数据包,两端首先会通过三次握手,建立TCP连接。
一个数据包,从聊天框里发出,消息会从聊天软件所在的用户空间拷贝到内核空间的发送缓冲区(send buffer),数据包就这样顺着传输层、网络层,进入到数据链路层,在这里数据包会经过流控(qdisc),再通过RingBuffer发到物理层的网卡。数据就这样顺着网卡发到了纷繁复杂的网络世界里。这里头数据会经过n多个路由器和交换机之间的跳转,最后到达目的机器的网卡处。
此时目的机器的网卡会通知DMA将数据包信息放到RingBuffer
中,再触发一个硬中断给CPU
,CPU
触发软中断让ksoftirqd
去RingBuffer
收包,于是一个数据包就这样顺着物理层,数据链路层,网络层,传输层,最后从内核空间拷贝到用户空间里的聊天软件里。
网络发包收包全景图
画了那么大一张图,只水了200字做解释,我多少是有些心痛的。
到这里,抛开一些细节,大家大概知道了一个数据包从发送到接收的宏观过程。
可以看到,这上面全是密密麻麻的名词。
整条链路下来,有不少地方可能会发生丢包。
但为了不让大家保持蹲姿太久影响身体健康,我这边只重点讲下几个常见容易发生丢包的场景。
建立连接时丢包
TCP协议会通过三次握手建立连接。大概长下面这样。
TCP三次握手
在服务端,第一次握手之后,会先建立个半连接,然后再发出第二次握手。这时候需要有个地方可以暂存这些半连接。这个地方就叫半连接队列。
如果之后第三次握手来了,半连接就会升级为全连接,然后暂存到另外一个叫全连接队列的地方,坐等程序执行accept()
方法将其取走使用。
半连接队列和全连接队列
是队列就有长度,有长度就有可能会满,如果它们满了,那新来的包就会被丢弃。
可以通过下面的方式查看是否存在这种丢包行为。
# 全连接队列溢出次数 # netstat -s | grep overflowed 4343 times the listen queue of a socket overflowed # 半连接队列溢出次数 # netstat -s | grep -i "SYNs to LISTEN sockets dropped" 109 times the listen queue of a socket overflowed
从现象来看就是连接建立失败。
这个话题在之前写的《没有accept,能建立TCP连接吗?》有更详细的聊过,感兴趣的可以回去看下。
流量控制丢包
应用层能发网络数据包的软件有那么多,如果所有数据不加控制一股脑冲入到网卡,网卡会吃不消,那怎么办?让数据按一定的规则排个队依次处理,也就是所谓的qdisc(Queueing Disciplines,排队规则),这也是我们常说的流量控制机制。
排队,得先有个队列,而队列有个长度。
我们可以通过下面的ifconfig
命令查看到,里面涉及到的txqueuelen
后面的数字1000
,其实就是流控队列的长度。
当发送数据过快,流控队列长度txqueuelen
又不够大时,就容易出现丢包现象。
qdisc丢包
可以通过下面的ifconfig
命令,查看TX下的dropped字段,当它大于0时,则有可能是发生了流控丢包。
# ifconfig eth0 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.21.66.69 netmask 255.255.240.0 broadcast 172.21.79.255 inet6 fe80::216:3eff:fe25:269f prefixlen 64 scopeid 0x20<link> ether 00:16:3e:25:26:9f txqueuelen 1000 (Ethernet) RX packets 6962682 bytes 1119047079 (1.0 GiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 9688919 bytes 2072511384 (1.9 GiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
当遇到这种情况时,我们可以尝试修改下流控队列的长度。比如像下面这样将eth0网卡的流控队列长度从1000提升为1500.
# ifconfig eth0 txqueuelen 1500
网卡丢包
网卡和它的驱动导致丢包的场景也比较常见,原因很多,比如网线质量差,接触不良。除此之外,我们来聊几个常见的场景。
RingBuffer过小导致丢包
上面提到,在接收数据时,会将数据暂存到RingBuffer
接收缓冲区中,然后等着内核触发软中断慢慢收走。如果这个缓冲区过小,而这时候发送的数据又过快,就有可能发生溢出,此时也会产生丢包。
RingBuffer满了导致丢包
我们可以通过下面的命令去查看是否发生过这样的事情。
# ifconfig eth0: RX errors 0 dropped 0 overruns 0 frame 0
查看上面的overruns
指标,它记录了由于RingBuffer
长度不足导致的溢出次数。
当然,用ethtool
命令也能查看。
# ethtool -S eth0|grep rx_queue_0_drops
但这里需要注意的是,因为一个网卡里是可以有多个RingBuffer的,所以上面的rx_queue_0_drops
里的0代表的是第0个RingBuffer的丢包数,对于多队列的网卡,这个0还可以改成其他数字。但我的家庭条件不允许我看其他队列的丢包数,所以上面的命令对我来说是够用了。。。
当发现有这类型丢包的时候,可以通过下面的命令查看当前网卡的配置。
#ethtool -g eth0 Ring parameters for eth0: Pre-set maximums: RX: 4096 RX Mini: 0 RX Jumbo: 0 TX: 4096 Current hardware settings: RX: 1024 RX Mini: 0 RX Jumbo: 0 TX: 1024
上面的输出内容,含义是RingBuffer最大支持4096的长度,但现在实际只用了1024。
想要修改这个长度可以执行ethtool -G eth1 rx 4096 tx 4096
将发送和接收RingBuffer的长度都改为4096。
RingBuffer增大之后,可以减少因为容量小而导致的丢包情况。
网卡性能不足
网卡作为硬件,传输速度是有上限的。当网络传输速度过大,达到网卡上限时,就会发生丢包。这种情况一般常见于压测场景。
我们可以通过ethtool
加网卡名,获得当前网卡支持的最大速度。
# ethtool eth0 Settings for eth0: Speed: 10000Mb/s
可以看到,我这边用的网卡能支持的最大传输速度speed=1000Mb/s。
也就是俗称的千兆网卡,但注意这里的单位是Mb,这里的b是指bit,而不是Byte。1Byte=8bit。所以10000Mb/s还要除以8,也就是理论上网卡最大传输速度是1000/8 = 125MB/s
。
我们可以通过sar命令
从网络接口层面来分析数据包的收发情况。
# sar -n DEV 1 Linux 3.10.0-1127.19.1.el7.x86_64 2022年07月27日 _x86_64_ (1 CPU) 08时35分39秒 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s 08时35分40秒 eth0 6.06 4.04 0.35 121682.33 0.00 0.00 0.00
其中 txkB/s是指当前每秒发送的字节(byte)总数,rxkB/s是指每秒接收的字节(byte)总数。
当两者加起来的值约等于12~13w字节
的时候,也就对应大概125MB/s
的传输速度。此时达到网卡性能极限,就会开始丢包。
遇到这个问题,优先看下你的服务是不是真有这么大的真实流量,如果是的话可以考虑下拆分服务,或者就忍痛充钱升级下配置吧。