网络延迟
网络延迟(Network Latency),人们通常认为它是指网络数据传输所需的时间。
但是,这里的“时间”是指双向流量,即数据从源发送到目的地,然后从目的地地址返回响应的往返时间:RTT(Round-Trip Time)。
除了网络延迟之外,另一个常用的指标是应用延迟(Application Latency),它是指应用接收请求并返回响应所需的时间。
通常,应用延迟也称为往返延迟,它是网络数据传输时间加上数据处理时间的总和。
通常人们使用ping命令来测试网络延迟,ping是基于ICMP协议的,它通过计算ICMP发出的响应报文和ICMP发出的请求报文之间的时间差来获得往返延迟时间。这个过程不需要特殊的认证,从而经常被很多网络攻击所利用,如,端口扫描工具nmap、分组工具hping3等。
因此,为了避免这些问题,很多网络服务都会禁用ICMP,这使得我们无法使用ping来测试网络服务的可用性和往返延迟。在这种情况下,您可以使用traceroute或hping3的TCP和UDP模式来获取网络延迟。
例如:
# -c: 3 requests
# -S: Set TCP SYN
# -p: Set port to 80
# hping3 -c 3 -S -p 80 baidu.com
HPING baidu.com (eth0 39.156.66.10): S set, 40 headers + 0 data bytes
len=40 ip=39.156.66.10 ttl=51 id=8101 sport=80 flags=SA seq=0 win=8192 rtt=39.9 ms
len=40 ip=39.156.66.10 ttl=51 id=48516 sport=80 flags=SA seq=1 win=8192 rtt=39.8 ms
len=40 ip=39.156.66.10 ttl=51 id=18386 sport=80 flags=SA seq=2 win=8192 rtt=49.7 ms
--- baidu.com hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 39.8/43.2/49.7 ms
当然,你也可以使用traceroute:
# traceroute --tcp -p 80 -n baidu.com
traceroute to baidu.com (39.156.66.10), 30 hops max, 60 byte packets
1 172.18.1.1 2.665 ms 3.893 ms 5.950 ms
...
14 * * 39.156.66.10 28.805 ms
traceroute会在路由的每一跳(hop)发送三个数据包,并在收到响应后输出往返延迟。如果没有响应或响应超时(默认 5s),将输出一个星号*。
案例展示
我们需要在此演示中托管host1和host2两个主机:
-
host1 (192.168.47.128):托管两个Nginx Web应用程序(正常和延迟)
-
host2 (192.168.47.129):分析主机
host1 准备
在host1上,让我们运行启动两个容器,它们分别是官方Nginx和具有延迟版本的Nginx:
# 正常的nginx
# docker run --network=host --name=good -itd nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
9e3ea8720c6d: Pull complete
bf36b6466679: Pull complete
15a97cf85bb8: Pull complete
9c2d6be5a61d: Pull complete
6b7e4a5c7c7a: Pull complete
8db4caa19df8: Pull complete
Digest: sha256:480868e8c8c797794257e2abd88d0f9a8809b2fe956cbfbc05dcc0bca1f7cd43
Status: Downloaded newer image for nginx:latest
047094f369a331b6e7b3a72f43e47695c9dae41e00addf85861f3080b9e9161e
# 延迟的nginx
# docker run --network=host --name=good -itd nginx
58f015e037bfb5a88139ce3b29468430f1ec5c61e2f29118291fa1cf7cfd0f28
root@ubuntu:~# docker run --network=host --name=latency -itd feisky/nginx:latency
Unable to find image 'feisky/nginx:latency' locally
latency: Pulling from feisky/nginx
5e6ec7f28fb7: Pull complete
ab804f9bbcbe: Pull complete
052b395f16bc: Pull complete
56e01c98ad9d: Pull complete
6f031ef539d7: Pull complete
Digest: sha256:6c99de8c27d7e1ba440246fec9c99f0832a5d56990c7b2c32fb64be0face020a
Status: Downloaded newer image for feisky/nginx:latency
13f6b1af1b812440c4932d468eb76b7fb7956736297e35634e943d54fd7a12ce
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
13f6b1af1b81 feisky/nginx:latency "nginx -g 'daemon of…" 27 seconds ago Up 27 seconds latency
047094f369a3 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes good
运行以下命令以验证两个容器都在为流量提供服务:
# 测试正常nginx
# curl http://127.0.0.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
# 测试延迟nginx
# curl http://127.0.0.1:8080
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
host2 准备
现在让我们用上面提到的hping3来测试它们的延迟,看看有什么区别。在host2中,执行以下命令分别测试案例机的8080端口和80端口的延迟:
80 端口:
# hping3 -c 3 -S -p 80 192.168.47.128
HPING 192.168.47.128 (eth0 192.168.47.128): S set, 40 headers + 0 data bytes
len=46 ip=192.168.47.128 ttl=64 DF id=0 sport=80 flags=SA seq=0 win=64240 rtt=3.9 ms
len=46 ip=192.168.47.128 ttl=64 DF id=0 sport=80 flags=SA seq=1 win=64240 rtt=3.3 ms
len=46 ip=192.168.47.128 ttl=64 DF id=0 sport=80 flags=SA seq=2 win=64240 rtt=3.3 ms
--- 192.168.47.128 hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 3.3/3.5/3.9 ms
8080 端口:
# 测试8080端口延迟
# hping3 -c 3 -S -p 8080 192.168.47.128
HPING 192.168.47.128 (eth0 192.168.47.128): S set, 40 headers + 0 data bytes
len=46 ip=192.168.47.128 ttl=64 DF id=0 sport=8080 flags=SA seq=0 win=64240 rtt=4.3 ms
len=46 ip=192.168.47.128 ttl=64 DF id=0 sport=8080 flags=SA seq=1 win=64240 rtt=7.6 ms
len=46 ip=192.168.47.128 ttl=64 DF id=0 sport=8080 flags=SA seq=2 win=64240 rtt=11.1 ms
--- 192.168.47.128 hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 4.3/7.7/11.1 ms
从这个输出中您可以看到80端口平均为3.5毫秒,8080端口平均为7.7毫秒。两者差别不是很大,但这仅适用于单个请求。并发请求怎么办?让我们用wrk试一试。
80 端口:
# wrk --latency -c 100 -t 2 --timeout 2 http://192.168.47.128/
Running 10s test @ http://192.168.47.128/
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 10.11ms 4.04ms 85.90ms 76.53%
Req/Sec 5.02k 533.59 6.99k 76.00%
Latency Distribution
50% 9.61ms
75% 11.68ms
90% 14.84ms
99% 22.60ms
99820 requests in 10.02s, 81.20MB read
Requests/sec: 9963.87
Transfer/sec: 8.11MB
8080 端口:
# wrk --latency -c 100 -t 2 --timeout 2 http://192.168.47.128:8080/
Running 10s test @ http://192.168.47.128:8080/
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 44.92ms 6.74ms 67.51ms 94.57%
Req/Sec 1.11k 144.41 2.00k 87.50%
Latency Distribution
50% 44.49ms
75% 47.73ms
90% 48.74ms
99% 54.38ms
22163 requests in 10.05s, 17.99MB read
Requests/sec: 2206.31
Transfer/sec: 1.79MB
从以上两个输出可以看出,Nginx(监听 80 端口)的平均延迟为10.11ms,而延迟的Nginx(监听 8080 端口)的平均延迟为44.92ms。从延迟分布上来看,Nginx 可以在14.84ms内完成 90% 的请求;对于延迟的Nginx,50%的请求已经达到44.49ms。发生了什么?我们来做一些分析。
分析:
1. 在host1中,让我们使用tcpdump捕获一些网络数据包:
# tcpdump -nn tcp port 8080 -w nginx.pcap
tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
2.在host2上,重新运行wrk命令
# wrk --latency -c 100 -t 2 --timeout 2 http://192.168.47.128:8080/
当wrk命令完成后,再次切换回host1,并按Ctrl+C结束tcpdump命令。然后,用Wireshark打开。
由于网络包的数量很多,我们可以先过滤一下。在上面过滤信息填入:tcp.stream eq 24,如图:
从这里,您可以看到从三次握手开始,此 TCP 连接的每个请求和响应。这可能不够直观,可以继续点击菜单栏中的 统计(Statistics) -> 流量图(Flow Graph),选择 “限制显示过滤器(Limit to display filter)”,将流类型(Flow type)设置为 “TCP Flows”:
请注意,此图的左侧是客户端,而右侧是Nginx服务器。从这个图中可以看出,前三次握手和第一次HTTP请求和响应都相当快,但是第二次HTTP请求就比较慢了,尤其是客户端收到服务器的第一个数据包后,该ACK响应在 40ms 后(图中标注的0.078950比原来0.036211多了0.04)才被发送。这是TCP延迟ACK的最小超时。这是TCP ACK的一种优化机制,即不是每次请求都发送一个ACK,而是等待一段时间(比如 40ms),看看有没有“搭车”的数据包。如果在此期间还有其他数据包需要发送,它们将与ACK一起被发送。当然,如果等不及其他数据包,超时后会单独发送ACK。
由于客户端发生了40ms延迟,可以怀疑客户端开启了延迟确认机制(Delayed Acknowledgment Mechanism)。这里的客户端其实就是之前运行的wrk。
根据TCP文档,只有在TCP套接字专门设置了TCP_QUICKACK时才会启用快速确认模式(Fast Acknowledgment Mode);否则,默认使用延迟确认机制:
TCP_QUICKACK (since Linux 2.4.4)
Enable quickack modeifsetordisablequickack modeifcleared. In quickack mode, acks are sent imme‐
diately, rather than delayedifneededinaccordance to normal TCP operation. This flag is not perma‐
nent, it only enables a switch to or from quickack mode. Subsequent operation of the TCP protocol will
once again enter/leave quickack mode depending on internal protocol processing and factors such as
delayed ack timeouts occurring and data transfer. This option should not be usedincode intended to be
portable.
让我们测试一下我们的质疑:
# strace -f wrk --latency -c 100 -t 2 --timeout 2 http://192.168.47.128:8080/
...
setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
...
可以看到wrk只设置了TCP_NODELAY选项,没有设置TCP_QUICKACK。可以看到为什么延迟Nginx响应会出现一个延迟。
那为什么80的那个nginx没有出现呢?我们进入两个nginx,查看配置
80的nginx
# docker exec -it 58f015e037bf bash
# cd /etc/nginx# cat nginx.config
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
8080的nginx
# docker exec -it 13f6b1af1b81 bash
# cd /etc/nginx
# cat nginx.config
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush off;
tcp_nodelay off;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
看到了吗?标红的就是他们的差别,正常官方nginx的是注释了tcp_nopush这个配置的,而他默认是on,而延迟nginx是特别标注了tcp_nopush off和tcp_nodelay off
软件的一个配置问题就可以导致软件性能多大的差距啊。至于worker_processes auto和worker_processes 1由于是虚拟机都没有设置那么多线程,差距效果也不会很明显。
结论
网络延迟是核心网络性能指标。由于网络传输、网络报文处理等多种因素的影响,网络延迟是不可避免的。但过多的网络延迟会直接影响用户体验。配置好软件,有可能解决一部分网络延迟的问题哦。
检验工具
-
使用hping3和wrk等工具确认单个请求和并发请求的网络延迟是否正常。
-
使用traceroute,确认路由正确,并查看路由中每个网关跳跃点的延迟。
-
使用tcpdump和Wireshark确认网络数据包是否正常收发。
-
使用strace等观察应用程序对网络socket的调用是否正常。