gitbook/Linux性能优化实战/docs/84529.md
2022-09-03 22:05:03 +08:00

98 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 45 | 答疑(五):网络收发过程中,缓冲区位置在哪里?
你好,我是倪朋飞。
专栏更新至今,四大基础模块的最后一个模块——网络篇,我们就已经学完了。很开心你还没有掉队,仍然在积极学习思考和实践操作,热情地留言和互动。还有不少同学分享了在实际生产环境中,碰到各种性能问题的分析思路和优化方法,这里也谢谢你们。
今天是性能优化答疑的第五期。照例,我从网络模块的留言中,摘出了一些典型问题,作为今天的答疑内容,集中回复。同样的,为了便于你学习理解,它们并不是严格按照文章顺序排列的。
每个问题,我都附上了留言区提问的截屏。如果你需要回顾内容原文,可以扫描每个问题右下方的二维码查看。
## 问题 1网络收发过程中缓冲区的位置
![](https://static001.geekbang.org/resource/image/49/28/49649598767b9ef537169558ee6be128.png)
第一点,是网络收发过程中,收发队列和缓冲区位置的疑问。
在 [关于 Linux 网络,你必须要知道这些](https://time.geekbang.org/column/article/80898) 中,我曾介绍过 Linux 网络的收发流程。这个流程涉及到了多个队列和缓冲区,包括:
* 网卡收发网络包时,通过 DMA 方式交互的**环形缓冲区**
* 网卡中断处理程序为网络帧分配的,内核数据结构 **sk\_buff 缓冲区**
* 应用程序通过套接字接口,与网络协议栈交互时的**套接字缓冲区。**
不过相应的,就会有两个问题。
首先,这些缓冲区的位置在哪儿?是在网卡硬件中,还是在内存中?这个问题其实仔细想一下,就很容易明白——这些缓冲区都处于内核管理的内存中。
其中,**环形缓冲区**,由于需要 DMA 与网卡交互,理应属于网卡设备驱动的范围。
**sk\_buff 缓冲区**是一个维护网络帧结构的双向链表链表中的每一个元素都是一个网络帧Packet。虽然 TCP/IP 协议栈分了好几层,但上下不同层之间的传递,实际上只需要操作这个数据结构中的指针,而无需进行数据复制。
**套接字缓冲区**,则允许应用程序,给每个套接字配置不同大小的接收或发送缓冲区。应用程序发送数据,实际上就是将数据写入缓冲区;而接收数据,其实就是从缓冲区中读取。至于缓冲区中数据的进一步处理,则由传输层的 TCP 或 UDP 协议来完成。
其次,这些缓冲区,跟前面内存部分讲到的 Buffer 和 Cache 有什么关联吗?
这个问题其实也不难回答。我在内存模块曾提到过,内存中提到的 Buffer ,都跟块设备直接相关;而其他的都是 Cache。
实际上sk\_buff、套接字缓冲、连接跟踪等都通过 slab 分配器来管理。你可以直接通过 /proc/slabinfo来查看它们占用的内存大小。
## 问题 2内核协议栈是通过一个内核线程的方式来运行的吗
第二个问题,内核协议栈的运行,是按照一个内核线程的方式吗?在内核中,又是如何执行网络协议栈的呢?
![](https://static001.geekbang.org/resource/image/9b/1c/9bea298bcc349e80f46c1a406472381c.png)
说到网络收发,在中断处理文章中我曾讲过,其中的软中断处理,就有专门的内核线程 ksoftirqd。每个 CPU 都会绑定一个 ksoftirqd 内核线程,比如, 2 个CPU 时,就会有 ksoftirqd/0 和 ksoftirqd/1 这两个内核线程。
不过要注意并非所有网络功能都在软中断内核线程中处理。内核中还有很多其他机制比如硬中断、kworker、slab 等),这些机制一起协同工作,才能保证整个网络协议栈的正常运行。
关于内核中网络协议栈的工作原理,以及如何动态跟踪内核的执行流程,专栏后续还有专门的文章来讲。如果对这部分感兴趣,你可以先用我们提到过的 perf、systemtap、bcc-tools 等,试着来分析一下。
## 问题 3最大连接数是不是受限于 65535 个端口
![](https://static001.geekbang.org/resource/image/50/8c/504ddb710169cb247b349d0d8a32818c.png)![](https://static001.geekbang.org/resource/image/0a/70/0a5cb5d25a4b09c5f46ca8941e9fca70.png)
我们知道,无论 TCP 还是 UDP端口号都只占 16 位,也就说其最大值也只有 65535。那是不是说如果使用 TCP 协议,在单台机器、单个 IP 地址时,并发连接数最大也只有 65535 呢?
对于这个问题首先你要知道Linux 协议栈通过五元组来标志一个连接即协议源IP、源端口、目的IP、目的端口)。
明白了这一点,这个问题其实就有了思路。我们应该分客户端和服务器端,这两种场景来分析。
对客户端来说,每次发起 TCP 连接请求时,都需要分配一个空闲的本地端口,去连接远端的服务器。由于这个本地端口是独占的,所以客户端最多只能发起 65535 个连接。
对服务器端来说,其通常监听在固定端口上(比如 80 端口等待客户端的连接。根据五元组结构我们知道客户端的IP和端口都是可变的。如果不考虑 IP 地址分类以及资源限制,服务器端的理论最大连接数,可以达到 2 的 48 次方IP 为 32 位,端口号为 16 位远大于65535。
所以综合来看客户端最大支持65535个连接而服务器端可支持的连接数是海量的。当然由于 Linux 协议栈本身的性能以及各种物理和软件的资源限制等这么大的连接数还是远远达不到的实际上C10M 就已经很难了)。
## 问题 4 “如何优化 NAT 性能”课后思考
![](https://static001.geekbang.org/resource/image/c6/a0/c623453e2e054d2f4407ab1e4a87f5a0.png)
在 [如何优化 NAT 性能](https://time.geekbang.org/column/article/83189) 的最后, 我给你留了两个思考题。
MASQUERADE 是最常用的 SNAT 规则之一,通常用来为多个内网 IP 地址,提供共享的出口 IP。假设现在有一台 Linux 服务器,用了 MASQUERADE 方式,为内网所有 IP 提供出口访问功能。那么,
* 当多个内网 IP 地址的端口号相同时MASQUERADE 还能正常工作吗?
* 内网 IP 地址数量或者请求数比较多的时候,这种使用方式有没有什么潜在问题呢?
对于这两个思考题我来也、ninuxer 等同学,都给出了不错的答案:
![](https://static001.geekbang.org/resource/image/24/52/245ba322ff2975e56db18206f0797d52.png)![](https://static001.geekbang.org/resource/image/f4/55/f41d8ad99120f22a7967e3afffe97555.png)
先看第一点,当多个内网 IP 地址的端口号相同时MASQUERADE 当然仍可以正常工作。不过,你肯定也听说过,配置 MASQUERADE 后,需要各个应用程序去手动配置修改端口号。
实际上MASQUERADE 通过 conntrack 机制,记录了每个连接的信息。而在刚才第三个问题 中,我提到过,标志一个连接需要五元组,只要这五元组不是同时相同,网络连接就可以正常进行。
再看第二点,在内网 IP 地址和连接数比较小时,这种方式的问题不大。但在 IP 地址或并发连接数特别大的情况下,就可能碰到各种各样的资源限制。
比如MASQUERADE 既然把内部多个 IP ,转换成了相同的外网 IP即 SNAT那么为了确保发送出去的源端口不重复原来网络包的源端口也可能会被重新分配。这样的话转换后的外网 IP 的端口号,就成了限制连接数的一个重要因素。
除此之外连接跟踪、MASQUERADE机器的网络带宽等都是潜在的瓶颈并且还存在单点的问题。这些情况在我们实际使用中都需要特别注意。
今天主要回答这些问题,同时也欢迎你继续在留言区写下疑问和感想,我会持续不断地解答。希望借助每一次的答疑,可以和你一起,把文章知识内化为你的能力,我们不仅在实战中演练,也要在交流中进步。