ZhaoTab 项目里一个简单的查询接口耗时 5 秒,这显然不太正常。研究一番后,我把它提速到了 0.1 秒。本文记录整个优化过程。
在浏览器控制台的 Timing 选项卡中,可以看到耗时分布如下。
| 阶段 | 耗时 |
|---|---|
| Waiting for server response(TTFB) | 3.07s |
| Content download | 2.27s |
| 响应大小 | 290KB |
这 5 秒由两段时间叠加而成,两者的成因完全不同,需要分开来看:
运营商所说的千兆带宽 1000Mbps,指的是每秒能传输 兆比特 的数据。1 字节 = 8 比特,换算成容量单位要除以 8,所以 1000Mbps 意味着理论峰值每秒能传输 125MB 的数据。
我的服务器带宽只有 1Mbps,理论峰值每秒只能传输 125KB 的数据。所谓理论峰值,是指同一时刻只有一个人在从服务器下载数据;如果有两个人,每秒能传输的数据就要对半分,只有 62.5KB。
数据只有 290KB,并不算大,但下载花了 2.27s,这与服务器 1Mbps 的下载速度是相符的。
# 查看响应头里是否有 Content-Encoding
curl -s -o /dev/null -D - -H "Accept-Encoding: gzip" \
  https://api.example.com/api/v1/store-apps | grep -i content-encoding
发现响应头里没有 Content-Encoding,这意味着 API 数据没有经过压缩。
于是在 nginx 上给 API 开启 gzip:
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_proxied any; # 反向代理过来的响应也要压缩
gzip_vary on;
gzip_types application/json text/plain application/javascript text/css;
同时对代码也做一下优化,把一些数据库中存在、但前端并未用到的字段直接排除掉。
经过上面两个步骤,响应数据从 290KB 降到了 48KB,Content download 从 2.27s 降到 47ms。
如果财力允许,可以增加带宽,那下载速度回更快,目前 47ms,不到 0.1s (100ms) 完全能接受。
服务器响应速度慢,其实有以下几个原因:
排查原因 1:用 explain 看数据库自己花了多少时间。
plan = col.find({}).sort("order", 1).explain()
stats = plan["executionStats"]
print(stats["executionTimeMillis"], stats["totalDocsExamined"])
结果:
COLLSCAN + SORT,确实是全表扫描加内存排序,没用上索引。executionTimeMillis 是 0,totalDocsExamined 是 475。475 条数据、总共约 320KB,全表扫描再排序,MongoDB 自己连 1 毫秒都用不到。原因 1 排除了——加索引能省下的时间不到 1ms,跟 3 秒相比可以忽略不计。
排查原因 2:这就是一个简单的查询接口,没有额外操作,排除。
排查原因 3:登录服务器,用 top 命令查看,CPU 和内存都正常,排除。
排查原因 4:在本机写一个脚本,直连同一个数据库执行同样的查询,测量把全部结果取回来要多久:
t = time.time()
docs = list(col.find({}).sort("order", 1))
print(len(docs), time.time() - t)
| 查询 | 结果 |
|---|---|
| 475 条(约 320KB) | 3.06s |
| 90 条 | 0.5~0.8s |
数据库执行 0ms,但把结果取回却要 3 秒,耗时和数据量成正比。时间几乎全部花在了数据从 MongoDB 传到客户端的过程中,速度大约 1Mbps,正好和第一轮测出的服务器公网带宽一致。
本机走公网连数据库慢,这很正常。问题是:线上后端和数据库在同一台服务器上,为什么也要 3 秒?
最后经过排查发现:虽然数据库和后端在同一台服务器上,但后端代码里 MONGODB_URL 的默认值,以及服务器上的实际配置,连接串里写的都是数据库的公网域名,而不是 127.0.0.1。
这样一来,后端去连同一台机器上的 MongoDB,流量会先出到公网再绕回来,和在本机连服务器上的数据库耗时是一样的。
把服务器上后端的连接串改成本机回环地址,然后重启后端:
# 改前(示意)
MONGODB_URL=mongodb://<user>:<password>@db.example.com:<port>/<db>
# 改后
MONGODB_URL=mongodb://<user>:<password>@127.0.0.1:<port>/<db>
再次测试时,Waiting for server response 直接从 3.34s 降到了 69ms。
| 阶段 | 优化前 | 优化后 |
|---|---|---|
| Waiting for server response | 3.07~3.34s | 69ms |
| Content download | 2.27s | 47ms |
| 传输体积 | 290KB | 约 48KB |
| 合计 | 约 5.3s | 约 0.12s |
改动加起来就这些:nginx 加了几行 gzip 配置,接口少返回几个字段,服务器上改了一行数据库连接串。没有加索引,也没有升级服务器带宽。
↶ 返回首页 ↶