一、常用工具
1.1 nettrace
对于Linux系统(内核)中的复杂网络异常问题,可使用nettrace进行故障分析和诊断,详细介绍和使用方法可参见这里:nettrace工具
1.2 ss
ss命令可以打印出来套接口的各种元数据信息,在套接口调试方面比netstat要强大的多,可以打印出来套接口的内存、网络拥塞等各种信息。对于TCP类型的套接口,常用命令为:
ss -itme
在下面的套接口内存信息中,要关注一下t后面的数值,这个代表了已经从buffer中发送但是还没离开本机的报文的数据量,也就是当前套接口积压在qdisc或者网卡发送队列中的报文。
skmem:(r0,rb87380,t0,tb2627840,f0,w0,o0,bl0,d0)
上面内存相关的各个参数的意义分别是:
- r:收包缓冲区中的数据量
- rb:收包缓冲区大小
- t:已经被发送到L3但是还没从网卡发送出去的数据量
- tb:发包缓冲区大小
- f:预分配内存大小
- w:发包缓冲区中的数据量(还没发到L3)
- bl:backlog队列中的数据大小
ts sack cubic wscale:6,10 rto:243 rtt:42.26/10.76 ato:40 mss:1228 pmtu:1500 rcvmss:1228 advmss:1448 cwnd:5 ssthresh:2 bytes_sent:14097941 bytes_retrans:11352 bytes_acked:14086553 bytes_received:1136677 segs_out:22647 segs_in:16449 data_segs_out:19616 data_segs_in:5418 send 1.2Mbps lastsnd:2 lastrcv:2 lastack:2 pacing_rate 1.4Mbps delivery_rate 6.6Mbps delivered:19524 busy:3316193ms unacked:1 retrans:0/132 dsack_dups:40 rcv_rtt:73.636 rcv_space:40636 rcv_ssthresh:186984 minrtt:34.34
上面的一些信息是TCP套接口的一些扩展和内部信息,重点说明一下其中的一些字段的含义:
- rto:重传定时器超时时间
- ato:延迟ack定时器超时时间
- cwnd:拥塞窗口大小
- ssthresh:慢启动阈值
- retrans:第一个代表正在重传的报文量,第二个是总共重传过的报文个数
- dsack_dups:通过DSACK确认的伪重传报文数
- app_limited:当前套接口处于app_limited状态,即由于上层没有足够的数据包而限制了带宽
- busy:处于发包状态的时间
- rwnd_limited:处于零窗口状态的时间(对端收包慢)
- sndbuf_limited:由于内存不足而导致的发包受限的时间
- pacing_rate:根据拥塞窗口和mss估算出来的发包带宽
- delivery_rate:根据发包数据评估出来的带宽
- delivered:已经发送且被确认的报文数量
- minrtt:不考虑延迟ACK和压缩ACK计算出来的rtt,可以理解为是环境延迟
- rtt:第一个代表srtt,即经过平滑处理的rtt;第二个是rtt的平均方差,代表着rtt的稳定性
- backoff:当前正在超时重传的报文的回退次数,可以理解为重传的次数
其他用法:
//查看套接口使用情况
ss -s
1.3 netstat
使用netstat可以查看丢包统计信息。这个统计信息是全局的,无法针对特定的连接。通过统计信息,可以知道一些常见的问题。基本命令为:netstat -s
除此之外,还可以使用该工具查看当前网络连接的一些基本信息,比如使用以下命令即可查看当前的TCP连接情况:
netstat -tpn
在出现网络性能问题的时候,可以使用这个命令简单地看一下套接口收、发包缓冲区中的数据大小。
1.4 conntrack
conntrack可以用来跟踪当前节点上的conntrack记录信息,这对于定位nat场景的问题比较有帮助。常用的命令包括:
//列出当前跟踪到的所有的连接信息,可以使用-s/-d/-r/-q等参数进行过滤
conntrack -L
//查看conntrack的一些统计信息
conntrack -S
//监控conntrack事件,包括conntrack状态的变更等
conntrack -E
当出现conntrack异常的时候,还可以打开系统上的conntrack日志来进行监控,打开方式:
//6代表着TCP协议
echo 6 > /proc/sys/net/netfilter/nf_conntrack_log_invalid
然后通过dmegs即可查看到异常日志。
1.5 tc
linux系统中一般使用tc来进行qdisc,常用的命令如下:
//列出所有的网卡的qdisc队列信息
tc qdisc show
//查看所有的qdisc队列上的统计信息,主要看丢包和backlog队列信息
tc -s qdisc show
//将eth0上的队列设置为pfifo类型
tc qdisc replace dev eth0 root pfifo
1.6 ipfm
Linux系统中进行流量统计的工具,能够针对IP进行流量(不是带宽)统计。这个工具以daemon的方式运行在后台,可以按照ipfm范例中的例子来进行配置文件的配置。配置文件中可以配置采样周期、日志位置、日志清理周期等信息。
1.7 atop
可以对系统的CPU、内存等整体情况进行监控,可用于排查系统环境对网络的影响。常用命令:
atop -w atop.log 1
这个命令会持续进行数据采集,并将采集到的数据保存到atop.log文件中。使用命令
atop -r atop.log
可读取监控数据。t和T可进行采样时间的前进和后退。
1.8 火焰图
使用perf进行采样,随后使用script命令报错到文件:
# perf record -F 1000 -a -g -- sleep 60
# perf script > out.perf
根据perf采样数据,生成对应的火焰图:
# stackcollapse-perf.pl out.perf > out.folded
# flamegraph.pl out.folded > kernel.svg
二、开箱脚本
下面的脚本可以在不额外安装软件的情况下获取到一些网络问题定位过程中的信息,开箱即用,方便快捷。
2.1 丢包重传
系统总体丢包重传:
(while true; do netstat -st | grep -e 'retransmitted\|TCPDSACKRecv' -e 'fast retransmits' -e 'in slow start'; echo '----'; sleep 1; done) | awk 'BEGIN{ir=0; ip=0; pr=0; pp=0; r=0; p=0; psr=isr=pfr=ifr=0;}/retransmitted/{r=$1; if (ir==0) pr=ir=r;}/TCPDSACKRecv/{p=$2; if (ip==0) pp=ip=p;}/fast retransmits/{fr=$1; if (ifr==0) ifr=pfr=fr; }/in slow start/{sr=$1; if (isr==0) psr=isr=$1;}/----/{ print strftime("%Y-%m-%d %H:%M:%S"); printf "retransmission: %12d %8d(pps) %8d(inc)\n", r, r-pr, r-ir; printf "pseudo retrans: %12d %8d(pps) %8d(inc)\n", p, p-pp, p-ip; printf "fast retrans: %14d %8d(pps) %8d(inc)\n", fr, fr-pfr, fr-ifr; printf "timeout retrans: %11d %8d(pps) %8d(inc)\n", sr, sr-psr, sr-isr; printf "drop retrans: %23d(pps) %8d(inc)\n", r-pr-(p-pp), r-ir-(p-ip); pp=p; pr=r; psr=sr; pfr=fr;}'
单个套接口丢包重传:
while true; do ss -itn; echo '----'; sleep 1; done | awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S");}/ESTAB/{src=$4; dst=$5;}/^\t/{if (match($0, / retrans:[^ ]+/)) { retrans=substr($0, RSTART + 1, RLENGTH); retrans=gensub(/retrans:[0-9]*\//, "", 1, retrans); if (match($0, /dsack_dups:[^ ]*/)) {drop=gensub(/dsack_dups:/, "", 1, substr($0, RSTART, RLENGTH)); drop=retrans-drop; } else {drop=retrans;} printf "%s->%s total:%d drop:%d\n", src, dst, retrans, drop;} }/----/{print strftime("%Y-%m-%d %H:%M:%S");}'
2.2 SS监控
watch -n 1 -d "ss -itme -n"
watch -n 1 -d "ss -itme -n 'src ip:port'"
watch -n 1 -d "ss -itme -n 'dst ip:port'"
2.3 格式化统计信息
用于将snmp/netstat接口中的信息从行显示转为列显示,可用于类似的其他场景中:
cat /proc/net/snmp | awk '(NR % 2)!=0{proto=$1; for (i=2; i<NF; i++) data[i]=$i; }(NR % 2)==0{print proto; for (i=2; i<NF; i++)printf " %-32s:%d\n", data[i], $i}'
2.4 收(发)包CPU查找
查看哪个队列在收包/发包:
watch -n 1 -d "ethtool -S eth0 | grep -E rx[0-9]+_bytes"
watch -n 1 -d "ethtool -S eth0 | grep -E tx[0-9]+_bytes"
找到目标队列所使用的中断号:
cat /proc/interrupts | grep key_word
key_word:
- virtio网卡:virtio.*input. 或者 virtio.*output.
- mlx网卡:mlx5_comp.*
获取该中断绑定的CPU:
cat /proc/irq/irq_number/smp_affinity_list
如果绑定了多个CPU核,那么可以观察哪个CPU上中断增加量比较多来判断是哪个核在收包:
watch -n 1 -d 'cat /proc/interrupts | grep irq_number:'
也可以使用下面的中断的命令来直接观察中断量高的来判断是否是发包/收包中断。
2.5 高频中断
使用下面的命令可以查看哪个中断在哪个核上的频率比较高:
while true
do
cat /proc/interrupts
sleep 1
done | awk '
/CPU0/{ if (NR != 1) print strftime("%Y-%m-%d %H:%M:%S") }
NR==1{ cpu_count = NF }
$1 ~ /^[0-9]+:/{
irq = $1;
desc = ""
for (i = 2; i < cpu_count + 2; i++) {
key = irq SUBSEP i;
prev_val = data[key];
data[key] = $i;
if (prev_val == "") continue;
delta = $i - prev_val;
if (delta <= 0) continue;
desc = sprintf("%s cpu%-3d:%8-d", desc, i - 2, delta);
}
if (desc != "") {
for (i = cpu_count + 2; i <= NF; i++) desc = desc " " $i
printf "%-6s%s\n", irq, desc;
}
fflush(stdout);
}'
下面的命令是查看每秒新增软中断的:
while true
do
cat /proc/softirqs
sleep 1
done | awk '
/CPU0/{ if (NR != 1) print strftime("%Y-%m-%d %H:%M:%S") }
NR==1{ cpu_count = NF }
$1 ~ /:/{
irq = $1;
desc = ""
for (i = 2; i < cpu_count + 2; i++) {
key = irq SUBSEP i;
prev_val = data[key];
data[key] = $i;
if (prev_val == "") continue;
delta = $i - prev_val;
if (delta <= 0) continue;
desc = sprintf("%s cpu%-3d:%-4d", desc, i - 2, delta);
}
if (desc != "") printf "%8s %s\n", irq, desc;
fflush(stdout);
}'
2.6 CPU处理报文统计
下面的脚本可以统计每秒钟每个核上处理的接收到的报文数量、丢弃的报文数量和每个核上backlog队列中的报文数量:
while true
do
echo '----'
cat /proc/net/softnet_stat
sleep 1
done | awk --non-decimal-data '
BEGIN{
field[1] = "PROCESSED";
field[2] = "DROP";
field[12] = "BACKLOG";
printf("%8s", "");
for (k in field) printf("%12s", field[k]);
printf("\n");
}
/----/{ cpu_start=NR; if (NR != 1) print strftime("%Y-%m-%d %H:%M:%S") }
$1 ~ /[0-9a-z]+/{
cpu = NR - cpu_start - 1;
changed = 0
for (k in field) {
processed = "0x" $k;
key = cpu SUBSEP k;
if (!(key in data)) break;
field[k] = processed - data[key];
if (field[k] > 0) changed = 1;
}
if (changed) {
printf("cpu%3-d: ", cpu);
for (k in field) printf("%12d", field[k]);
printf("\n");
}
for (k in field) data[cpu SUBSEP k] = "0x" $k;
}'
2.7 RPS/XPS
获取当前网卡的RPS/XPS配置信息:
find /sys/class/net/eth0/queues/ -type d -name "rx-*" | xargs -I % cat %/rps_cpus
find /sys/class/net/eth0/queues/ -type d -name "tx-*" | xargs -I % cat %/xps_cpus
设置网卡的RPS/XPS:
find /sys/class/net/eth0/queues/ -type d -name "rx-*" | xargs -I % sh -c "echo ffff > %/rps_cpus"
find /sys/class/net/eth0/queues/ -type d -name "tx-*" | xargs -I % sh -c "echo ffff > %/xps_cpus"
三、常见问题
3.1 TW套接口
在短连接的场景下,比如HTTP打流测试、NGINX代理等,环境上容易在短时间内积累大量的timewait类型的套接口。相比于常规的套接口,tw套接口在内核中占用的资源要小得多,但是过多的tw依然会导致资源消耗、性能下降、端口耗尽的问题。
端口耗尽
在没有开启tw_reuse的情况下,tw类型的套接口也会占据当前的端口,直到超时后被释放。过多的端口占用会引发严重的性能问题,因为内核在查找可用端口时采用的是线性遍历查找的方式。如果可用端口数量比较少,那么查找的效率就会非常低,消耗大量的CPU。通过下面的命令打开tw_reuse可以避免这一问题:
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
在当前的系统中,这个参数默认是开启状态的。
性能下降
除了端口消耗引入的问题,tw套接口过多也会导致套接口查找的性能下降,这是因为tw套接口和普通的TCP套接口一样,都处于ehash哈希表中。tw套接口过多会导致收包阶段在进行套接口查找的时候性能下降。当前系统上的tw套接口的总数量可以通过下面的接口来进行调整,当数量达到这个阈值后,会直接被释放:
$ cat /proc/sys/net/ipv4/tcp_max_tw_buckets
1048576
超时时间
在原生的Linux系统中,tw套接口的存活时间是60s,这个是固定的。但是在咱们内核剑走偏锋,提供了接口可以调整这个时间。所以减少tw套接口的另一个方式就是减小这个值:
$ cat /proc/sys/net/ipv4/tcp_tw_timeout
60
3.2 网络收发包受干扰
如果用户的业务进程CPU占用比较高,那么是可能会影响到网络收发包的。这是因为现在的网络收发包基本上都是在软中断线程中被处理的,而软中断线程的优先级默认是普通优先级,调度算法是CFS(完全公平调度)。为了提升收发包的效率,可以调整其优先级,比如将所有的软中断线程的优先级改成实时线程(RR调度策略):
pgrep ksoftirqd | xargs -I % chrt -r -p 20 %
3.3 带宽上不去
带宽打不上去,原因无非有三种:
- 环境因素,网络链路带宽受限,出现拥塞丢包
- 环境因素,网络链路存在硬件故障,出现概率丢包
- RTT较长,1s内能够传递的数据的次数变少
- 发端/收端性能问题,接收端收包不及时或者发端发送数据不及时
丢包限速
针对上面的四个原因,排查和解决的方法也不同,需要分别对待。首先需要排查是哪种原因导致的带宽受限,即先观察是否存在丢包重传。有两种方式:
- 根据2.1中提供的脚本来观察系统层面是否有丢包重传,重点关注drop retrans这个指标。这个方式观察的是整个系统层面的丢包重传。
- 根据2.1中提供的脚步来观察目标的连接是否有丢包重传,重点关注drop这个指标。
确定有丢包后,可以将拥塞算法改成bbr算法,这个算法允许一定的硬件丢包:
sysctl -w net.ipv4.tcp_congestion_control=bbr
如果带宽有一定的提升,说明是硬件错误丢包;否则,说明是链路上出现了网络拥塞导致的丢包。
RTT限速
使用命令ss -itn可以确定目标流的rtt情况。在网络没有出现丢包的情况下,所能达到的理论带宽为:
带宽 = 发送窗口 * 1000 / rtt
而TCP连接的发送窗口是由发送端的发包缓冲区和接收端的收包缓冲区共同决定的。从上面的公式可以看出来,在rtt一定的情况下,增加发送窗口可以提升带宽。如果当前连接的rtt比较高,可以使用下面的命令提升窗口来提升带宽:
sysctl -w net.ipv4.tcp_rmem="4096 131072 33554432"
sysctl -w net.ipv4.tcp_wmem="4096 65536 33554432"
注意:如果这里的rtt远高于环境本身的延迟情况,那么就考虑延迟发生在ECS内部,需要使用下面的性能限速进行排查。如果rtt远大于min_rtt,那么网络延迟可能不是由环境导致的,而是由于ECS内部。
性能限速
上面的两个原因主要分析了环境导致的带宽受限,这里主要考虑的是ECS内部因素导致的限速。ECS内部的问题主要考虑性能方面导致的带宽受限,最基本的包括发端发送不及时、收端接收不及时等,可以按照下面的思路来排查:
- 使用ss -tn命令来查看发送和接收端的buffer队列情况。对于接收端,观察接收队列,确保里面没有大量堆积的数据;对于发送端,观察发送队列,确保里面有等待发送的数据。
- 使用ss -tmn命令来观察发端堆积在网卡和qdisc中的数据,重点关注指标t,t指标过高的话会导致TSQ限速(TCP Small Queue)。默认情况下,这个值不能高于:当前带宽 / 1024。
- 使用ss -tin命令来观察发送端是否存在rwnd_limited、sndbuf_limited的字样,这两个代表着当前的TCP数据传输由于0窗口、发送缓冲区不足而停止发送的时长。