一、水平扩展:性能优化
浏览器的 keepalive(简单了解)
keepalive 在响应头里通常以 Connection: keep-alive 或 Keep-Alive: timeout=xxx 的形式出现,即浏览器往往自带 keepalive 连接。
何时启用/关闭?用户不长时间停留在一个页面,开 keepalive(就是对建立的 tcp 连接复用);html 页面有子请求(控制台 F12 查看),用到时会自己加载,这种请求一般只抓一次,所以不开 keepalive。
在 nginx 中关闭 keepalive
参数 keepalive_timeout 65; 代表活跃时间为 65s,65s 内有活动就保持连接(长连接),反之关闭,部分特殊场景需要关闭 keepalive。
当值为 0,keepalive 为关闭状态。未关闭时查看 HTTP 请求包,connection 字段可能有 keep-alive 值,关闭时 value 变为 close。此时请求包可能还是 keep-alive,而响应时 close,这意味着浏览器发起请求想要保持连接~
数据包中看具体的情况(客户端)
数据包可以看到到底有没有建立长连接,抓包可以用 charles,charlesproxy.com。
为什么要用这个工具呢?目的就是查看是否有 keepalive。百度首页几十个 JS、CSS、图片,如果每个都新建 TCP 连接,页面加载就慢;用了 keepalive,同一个连接复用多次。这个结论只有抓包能看到,控制台看不到。
keepalive 在 nginx 的详细配置
官方写了,keepalive 有最大连接时间,一旦到了,强制切断连接。
以下内容可以在官方文档查看,完整的配置都在官方的文档中。
nginx 和客户端之间的配置:

keepalive_disable默认禁用一些浏览器
对上游服务器的模块是 upstream,官方文档标题就是:

对客户端的模块:直接搜索 disable 找到 keepalive 相关。
keepalive_requests:这个参数就配置了可以发起多少请求,默认 1000。一个 tcp 管道可以处理很多请求,服务器和客户端的操作系统都是异步的,建立好了连接可以并发请求,1000 这个数值是较多的,其实一般几百即可。
send_timeout:两次客户端写操作之间的间隔,大于这个间隔没有发送数据,会强制关闭连接(可能导致数据丢失)。

keepalive_timeout 是保持连接的时间,这个是返回数据的时间,这个不能比前者短,假设连接时间是 65s,send 数值是 60s,可能没传完会断开,因为有的复杂计算操作等待时间会长一些。
keepalive_time 1h;:设置 tcp 连接的最长连接时间,1h 即可。
keepalive_timeout 65 65;:第一个65是空闲连接保持时间,第二个65是响应头里Keep-Alive: timeout=65的值,告诉浏览器"这条连接还能用 65 秒"。跟 HTTP 1.0 还是 1.1 没关系,HTTP 1.0 默认短连接,开了 keepalive 才能用这个头;1.1 默认长连接,一般只写第一个值就够了。
超过设定时间不活动会失效,这个参数可以配置两个。
配置一个 HTTP 响应包只有一个 keepalive 的值,如果配置俩就是下图:

| 对谁 | 配在哪 |
|---|---|
| 浏览器 到 Nginx | server 或 location 块 |
| Nginx 到 上游 | upstream 块 + location 块 |
上面部分是浏览器和 nginx 之间的,下面了解 nginx 到上游服务器的配置选项。
直接写入 /etc/nginx/nginx.conf 的 http 块下,全局生效。

nginx 和上游服务器之间的配置
在 upstream 块配置
keepalive 100:向上游服务器保留的连接数。
keepalive_timeout:与客户-浏览器之间的意思一样,连接保留时间。
keepalive_time 这个指令在 Nginx 1.19.10 才加入,Nginx 版本可能是更早的,不支持这个参数,直接删了就行。
keepalive_requests:一个 tcp 复用的最大请求个数。
在 location 块
proxy_http_version 1.1;:http 的版本号。默认使用 1.0 的 HTTP 向后端请求,每次发完都会关闭连接,下次请求还要开启新的连接,很建议直接配成 1.1,效率更高。
proxy_set_header Connection "";:清除 close 信息。
用户所带的信息经过 nginx 的转发,会清除掉,然后再把请求转发到后端,后端不知道用户的 IP 等信息(后端不和用户交互)。
比如浏览器和 nginx 是长连接的,数据包有 connection 的 keepalive 请求头。
proxy_set_header Connection ""; 就是把字段 Connection 的值改为空,value 要加引号。
清空 Connection 头(设为空字符串)意味着不传 Connection 头给后端,后端按 HTTP 1.1 默认行为处理(长连接)。如果写成 Connection: close,后端收到后会主动断开,长连接就废了。所以这行的目的是清除浏览器带来的旧 Connection 头,让 Nginx 和后端之间按 HTTP 1.1 默认走长连接。
补充内容:Nginx 内置了 keepalive,不需要装任何第三方模块,基础篇提到的是 keepalived,与 keepalive 不同。
upstream httpget {
server 172.16.0.9;
server 10.1.4.16;
keepalive 16;
}
server {
listen 80;
server_name 168kaguyachi.site;
root /var/www/html;
index index.html;
#向客户端
keepalive_requests 500;
send_timeout 70s;
keepalive_timeout 65 65;
location / {
proxy_pass http://httpget;
add_header Content-Type "text/html; charset=utf-8";
#向上游
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
# 需要注意没有高亮的参数,可能nginx版本不支持
这里再配置的时候遇到了很多困难的问题,放到了本内容的子目录笔记下。
压力测试和调优方式
apt install apache2-utils -y 安装工具。
yum install httpd-tools 是 centos 的。
参数:

参数很多但是一般只用 -c 和 -n。
在 nginx 的机器上安装,进行压测。
ab -n 10000 -c 30 http://172.16.0.9/ 对该地址发起 1w 个请求并发 30。
一般是在网关机器安装,对上游服务器测试(结尾要有 /)。
关键数据如下:
| 指标 | 位置 | 含义 |
|---|---|---|
| QPS | Requests per second | 每秒处理多少请求,越高越好 |
| 失败数 | Failed requests | 必须为 0,不为 0 说明扛不住 |
| 平均响应时间 | Time per request 第一行 | 用户平均等多久,越低越好 |
| 99% 响应时间 | Percentage 倒数第二行 | 99% 的请求在多少 ms 内完成 |
用 1w 个请求测试:
ab -n 10000 -c 30 http://172.16.0.9/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 172.16.0.9 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests # 总共发送的请求
Server Software: SimpleHTTP/0.6
Server Hostname: 172.16.0.9
Server Port: 80
Document Path: /
Document Length: 8 bytes
Concurrency Level: 30
Time taken for tests: 4.205 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1920000 bytes
HTML transferred: 80000 bytes
Requests per second: 2378.23 [#/sec] (mean) # QPS和请求复杂度有关
Time per request: 12.614 [ms] (mean)
Time per request: 0.420 [ms] (mean, across all concurrent requests)
Transfer rate: 445.92 [Kbytes/sec] received # 上面是吞吐量(可能未压满)
Connection Times (ms)
min mean[+/-sd] median max
Connect: 2 7 67.4 2 1038
Processing: 2 5 22.6 4 840
Waiting: 2 5 22.6 3 840
Total: 5 12 74.5 6 1434
Percentage of the requests served within a certain time (ms)
50% 6
66% 6
75% 7
80% 7
90% 7
95% 7
98% 8
99% 9
100% 1434 (longest request)
不能单看个别数据,和综合的因素有关。
再用 10w 测试一次:
ab -n 100000 -c 30 http://172.16.0.9/
Benchmarking 172.16.0.9 (be patient)
Completed 10000 requests
Completed 100000 requests
Finished 100000 requests
Document Path: /
Document Length: 8 bytes
Concurrency Level: 30
Time taken for tests: 41.168 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 19200000 bytes
HTML transferred: 800000 bytes
Requests per second: 2429.08 [#/sec] (mean)
Time per request: 12.350 [ms] (mean)
Time per request: 0.412 [ms] (mean, across all concurrent requests)
Transfer rate: 455.45 [Kbytes/sec] received
可以复制两次到文档对比。
控制变量:可以把 nginx 的 keepalive 的参数注释掉或者修改,再次测试来看。
测试出来的肯定不准确,但是可以参考。
测试网关:
ab -n 100000 -c 30 http://127.0.0.1/
Benchmarking 127.0.0.1 (be patient)
Completed 10000 requests
Finished 100000 requests
Server Software: nginx/1.18.0
Server Hostname: 127.0.0.1
Server Port: 80
Document Path: /
Document Length: 7682 bytes
Concurrency Level: 30
Time taken for tests: 9.652 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 792600000 bytes
HTML transferred: 768200000 bytes
Requests per second: 10360.17 [#/sec] (mean)
Time per request: 2.896 [ms] (mean)
Time per request: 0.097 [ms] (mean, across all concurrent requests)
Transfer rate: 80190.13 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.3 1 7
Processing: 0 2 0.5 2 10
Waiting: 0 1 0.4 1 9
Total: 1 3 0.4 3 11
经过 Nginx 转发后 QPS 反而涨到 10360,直连后端只有 2429。不是 Nginx 加速了后端,而是你压测 127.0.0.1:80 时打到了网关的另一个站点(Document Length: 7682 bytes 是另一个页面的 HTML),这个站点是纯静态文件,没走 proxy_pass,Nginx 直接从磁盘读文件返回,性能当然暴增。
重新压测,指定 Host 头让它匹配 168kaguyachi.site,走负载均衡转发到后端。
ab -n 10000 -c 30 -H "Host: 168kaguyachi.site" http://127.0.0.1/
ab -n 10000 -c 30 -H "Host: 168kaguyachi.site" http://127.0.0.1/
Document Path: /
Document Length: 10 bytes
Concurrency Level: 30
Time taken for tests: 3.532 seconds
Complete requests: 10000
Failed requests: 5000 # 失败请求
(Connect: 0, Receive: 0, Length: 5000, Exceptions: 0)
Total transferred: 2455000 bytes
HTML transferred: 90000 bytes
Requests per second: 2831.09 [#/sec] (mean) # QPS
Time per request: 10.597 [ms] (mean) # 用户平均多久
Time per request: 0.353 [ms] (mean, across all concurrent requests)
Transfer rate: 678.74 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
99% 9 # 99%的响应完成时间
100% 3029 (longest request)
经过 Nginx 反向代理后,QPS 反而涨了 16%,响应时间降了 14%。
keepalive 16 和 proxy_http_version 1.1 生效了,Nginx 和后端之间复用了长连接,减少了握手开销,反向代理反而比直连更快。
反向代理的优点就是保护好反向代理服务器就能保护好内网。
Tomcat 直连和走 nginx 的压测对比
不会 tomcat,仅用视频数据学习。
测试 tomcat 的项目在压测前先访问进行初始化,更准确。
在 tomcat 对自身压测:

在 linux 安装的 tomcat 默认以 apr 形式运行网络接口(本机 c 语言形式),所以性能很高。
win 安装的是 bio,更差一些。
在 tomcat 对网关压测:

正常情况 10w 算少的,一般都测试 5min 以上,压测一下很可能看不出问题。
区别在于直接压 tomcat 没有连接复用(keepalive),压 nginx 会高因为本身 nginx 就好。
为什么反向代理比直连更好?直接看图:

验证:把 keepalive 都注释掉,直连 nginx:

关掉不加 keepalive,走 nginx 比直走 tomcat 吞吐量掉了近 1w,其余的也掉了很多,所以 keepalive 的性能优化很强,nginx 也很强。
加了 keepalive 不等于并发量变高,因为测试环境的客户端不支持 keepalive,真实的客户端都是浏览器默认支持的 keepalive,直连和加 nginx 要自己决定做减法(何时在 tomcat 前置 nginx 性能有明显提升)。
还有一个原因是 tomcat 本身的性能不如 nginx,用 nginx 代理更好。
总之,实际环境中要根据需求调整架构。