笔记
<h2>总结</h2>
<ol>
<li>
<p>对于收包而言,抓包的位置很靠前,在具体走协议栈之前(先遍历 ptype_all,再遍历 ptype_base,而 IP 协议是注册在 ptype_base 中),所以抓包的信息是很原始的。</p>
</li>
<li>
<p>如果全连接队列满了,在客户端回复 ACK 后,服务端则会丢弃 ACK,因此表现为:服务端不断重传 SYN+ACK,而客户端,则不断重传 ACK。</p>
</li>
<li>
<p>端口不足如何确认?
用 strace 查看系统调用的时间,如果 connect 占用时间非常长,那很可能是端口不够用。</p>
</li>
<li>
<p>查看全连接队列长度?
<code>ss -nlt</code> 观察 Send-Q 来确认。溢出次数统计:<code>netstat -s| grep overflowed</code></p>
</li>
<li>
<p>半连接队列无法直接查看,需要手动计算。另外,通过 <code>netstat -s | grep SYNs</code> 查看是不准确的,因为全连接队列满了,也会直接这个计数。</p>
</li>
<li>
<p>对于 LISTEN 端,新请求的 sk 是在第 3 次握手的时候创建的</p>
</li>
<li>
<p>查看 ehash 槽数量:<code>dmesg | grep &quot;TCP established hash table entries&quot;</code></p>
</li>
<li>
<p>本机通信时,使用网口 IP 和使用 127.0.0.1 通信是一样的,并且也是交付到 lo 口</p>
</li>
<li>100W 个空连接,大概需要内存 4G 左右。因为 1 个是 3.3KB 左右,直接乘以 100W = 3.3G</li>
</ol>
<h2>高并发</h2>
<p>基础:
1、如果请求频繁,考虑使用长连接
2、考虑限制 TIME_WAIT 数量:tcp_max_tw_buckets // 听说未释放的时候,如果连接的是不同的服务端,也能直接复用?</p>
<p>服务端:
1、修改文件句柄限制:系统级,用户级。100W,则设置 110W。建议:</p>
<pre><code class="language-sh"># /ec/sysctl.conf
fs.file-max=1100000 # 系统级
fs.nr_open=110000 # 进程级
# /etc/security/limits.conf
soft nofile 1000000
hard nofile 1000000</code></pre>
<p>2、修改队列长度:如 somaxconn,backlog=1024
3、建议开启 tcp_syncookies,保证不会因为半连接队列满而丢包
4、减少握手重试次数:tcp_syn_retries</p>
<p>客户端:
1、修改文件句柄限制
2、修改可用端口范围:10000 - 60000,或 5000 - 65000
3、端口还是不够用,则考虑端口回收:</p>
<pre><code class="language-sh">net.ipv4.tcp_timestamps = 1 # 设置之前,要保证开启这个?
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tw_recycle = 1</code></pre>
<h2>网络性能优化</h2>
<p>1、ring buffer 调整
2、开启多队列 - 建议
3、网卡硬中断合并
4、软中断 budget 调整:net.core.netdev_budget。腾讯虚拟机上是 300
5、LRO/GRO:ethtool -l eth0
TSO/GSO
6、发送文件用 mmap 和 sendfile
7、多队列网卡 XPS 调优
8、本机网络 IO 优化:使用 eBPF 的 sockmap 和 sk redirect 可以绕过 TCP/IP 协议栈
9、kernel-bypass:DPDK
10、客户端慎用 bind ?据说绑定一个端口,这个端口就不能复用了?答:非也,随机分配时,只检查 establish 冲突,不检查是否被绑定</p>
<h2>半/全连接队列</h2>
<pre><code class="language-sh"># 查看 listen 状态异常统计
netstat -s | grep -i listen
# 说明:
LINUX_MIB_LISTENDROPS # 不一定,涉及 LISTEN 那一部分(接收 SYN 包 和 ACK 包时)所有的失败都会记录在这里
LINUX_MIB_LISTENOVERFLOWS # 意义明确:全连接队列满导致的异常。这个计数增加,则上面的计数一定会同步增加
#############################
# 查看当前半连接队列长度:
无
# 查看是否有半连接队列溢出:
netstat -s | grep SYNs # 注意:此计数包含了涉及 LISTEN 那一部分(接收 SYN 包 和 ACK 包时)所有的失败
# 增加半连接队列长度,参考:
min(backlog, somaxconn, tcp_max_syn_backlog) + 1 并向上取整为 2^n,但限制最小为 16
#############################
# 查看当前全连接队列长度:
ss -lnt # 结果中的 Send-Q 字段
# 查看是否有全连接队列溢出:
netstat -s | grep overflow # 意义明确:全连接队列满导致的异常。这个计数增加,则上面的计数一定会同步增加
# 增加全连接队列长度,参考:
min(backlog, somaxconn)
#############################
# 相关设置项
cat /proc/sys/net/core/somaxconn
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
echo '20000 61000' &gt; /proc/sys/net/ipv4/ip_local_port_range
</code></pre>
<p>另外,ss 结果的字段意义不同:</p>
<pre><code class="language-c">// file: net/ipv4/tcp_diag.c
static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
void *_info)
{
struct tcp_info *info = _info;
if (inet_sk_state_load(sk) == TCP_LISTEN) {
r-&gt;idiag_rqueue = sk-&gt;sk_ack_backlog; // 当前全连接队列大小
r-&gt;idiag_wqueue = sk-&gt;sk_max_ack_backlog; // 全连接队列最大长度
} else if (sk-&gt;sk_type == SOCK_STREAM) {
const struct tcp_sock *tp = tcp_sk(sk);
r-&gt;idiag_rqueue = max_t(int, READ_ONCE(tp-&gt;rcv_nxt) -
READ_ONCE(tp-&gt;copied_seq), 0); // 已收到但未被应用层读取的字节数
r-&gt;idiag_wqueue = READ_ONCE(tp-&gt;write_seq) - tp-&gt;snd_una; // 已发送但未收到确认的字节数
}
if (info)
tcp_get_info(sk, info);
}</code></pre>
<p>LISTEN 状态:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=04856d1f6bf8420f8969f63e81a927fa&amp;file=file.png" alt="" /></p>
<p>非 LISTEN 状态:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=a10429c229040a4af51b524d3ee725f8&amp;file=file.png" alt="" /></p>
<p>> 参考文档:<a href="https://ivanzz1001.github.io/records/post/tcpip/2018/04/24/tcpip_timewait">https://ivanzz1001.github.io/records/post/tcpip/2018/04/24/tcpip_timewait</a></p>
<h2>一些信息</h2>
<pre><code class="language-sh">cat /proc/net/snmp
# 统计网络信息
netstat -s
cat /proc/net/dev
cat /proc/net/netstat</code></pre>