环境准备
预先安装 docker、curl、wrk、perf、FlameGraph 等工具
sudo yum groupinstall 'Development Tools'
# 安装火焰图工具
git clone https://github.com/brendangregg/FlameGraph
# 安装wrk
git clone https://github.com/wg/wrk
cd wrk && make && sudo cp wrk /usr/local/bin/
操作和分析
案例是一个 Nginx + PHP 应用
其中,wrk 和 curl 是 Nginx 的客户端,而 PHP 应用则是一个简单的 Hello World
<?php
echo "Hello World!"
?>
执行下面的命令,启动 Nginx 应用,并监听在 80 端口。如果一切正常,你应该可以看到如下的输出:
docker run --name nginx --network host --privileged -itd feisky/nginx-tp
docker run --name phpfpm --network host --privileged -itd feisky/php-fpm-tp
执行 docker ps 命令,查询容器的状态,你会发现,容器已经处于运行状态(Up)了:
docker ps
切换到终端二中,执行下面的 curl 命令,进一步验证 Nginx 能否正常访问。如果你看到 “Hello World!” 的输出,说明 Nginx+PHP 的应用已经正常启动了:
$ curl http://xxx.xxx.xxx.xxx
Hello World!
继续在终端二中,执行 wrk 命令,来测试 Nginx 的性能:
wrk --latency -c 1000 http://xxx.xxx.xxx.xxx
吞吐量(也就是每秒请求数)只有 156,并且所有 129个请求收到的都是异常响应(非 2xx 或 3xx)。这些数据显然表明,吞吐量太低了,并且请求处理都失败了。这是怎么回事呢?
根据 wrk 输出的统计结果,我们可以看到,总共传输的数据量只有 345KB,那就肯定不会是带宽受限导致的。所以,我们应该从请求数的角度来分析。
连接数优化
要查看 TCP 连接数的汇总情况,首选工具自然是 ss 命令。为了观察 wrk 测试时发生的问题,我们在终端二中再次启动 wrk,并且把总的测试时间延长到 30 分钟:
# 测试时间30分钟
wrk --latency -c 1000 -d 1800 http://xxx.xxx.xxx.xxx
回到终端一中,观察 TCP 连接数:
ss -s
wrk 并发 1000 请求时,建立连接数只有 109,而 closed 和 timewait 状态的连接则有 167 。其实从这儿你就可以发现两个问题:
一个是建立连接数太少了;
另一个是 timewait 状态连接太多了。
在终端一中,运行下面的命令,查看系统日志:
dmesg | tail
看到 nf_conntrack: table full, dropping packet 的错误日志。这说明,正是连接跟踪导致的问题。
连接跟踪数的最大限制 nf_conntrack_max ,以及当前的连接跟踪数 nf_conntrack_count。执行下面的命令,你就可以查询这两个选项:
sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 200
sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 200
最大的连接跟踪限制只有 200,并且全部被占用了。200 的限制显然太小,不过相应的优化也很简单,调大就可以了。
执行下面的命令,将 nf_conntrack_max 增大:
# 将连接跟踪限制增大到1048576
sysctl -w net.netfilter.nf_conntrack_max=1048576
执行下面的 wrk 命令,重新测试 Nginx 的性能:
# 默认测试时间为10s,请求超时2s
$ wrk --latency -c 1000 http://xxx.xxx.xxx.xxx
吞吐量已经从刚才的 156增大到了 1363,有 548响应异常,由于这些响应并非 Socket error,说明 Nginx 已经收到了请求,只不过,响应的状态码并不是我们期望的 2xx (表示成功)或 3xx(表示重定向)。
终端一,执行下面的 docker 命令,查询 Nginx 容器日志就知道了:
docker logs nginx --tail 3
响应状态码为 499。
499 并非标准的 HTTP 状态码,而是由 Nginx 扩展而来,表示服务器端还没来得及响应时,客户端就已经关闭连接了。换句话说,问题在于服务器端处理太慢,客户端因为超时(wrk 超时时间为 2s),主动断开了连接。
既然问题出在了服务器端处理慢,而案例本身是 Nginx+PHP 的应用,那是不是可以猜测,是因为 PHP 处理过慢呢?
执行下面的 docker 命令,查询 PHP 容器日志:
docker logs phpfpm --tail 5
server reached max_children setting (5),并建议增大 max_children。
执行下面的命令,删除案例应用:
# 停止nginx和phpfpm容器
docker rm -f nginx phpfpm