第 12 章 应用的性能改善

使用 Web 应用时,随着访问量的增加,我们会遇到响应延迟、请求失败无法正常提供服务的情况。

本章我们先学习上述问题的应对方法以及 Web 应用 / 服务器的性能评估,然后阶段性地导入 gunicorn、nginx,并在各阶段进行性能评估,观察性能改善的情况。

本章最终将形成如图 12.1 所示的服务器结构。gunicorn 和 nginx 的相关内容会在导入时详细说明,这里不作深入了解。

{%}

图 12.1 nginx 与 gunicorn 的协作

NOTE

所谓反向代理(Reverse Proxy),是指负责接收随机多个客户端发来的请求的服务器,其目的在于降低特定服务器处理请求的负担或向指定服务器群分配访问请求。它的运作方式与通常的代理服务器正好相反,因此称为反向代理。

12.1 Web 应用的性能

Web 应用负荷过重时会产生哪些问题?面对负荷过重应采取什么对策?选择对策时又应该进行哪些考量呢?接下来我们将了解一下这些问题。

12.1.1 Web 应用面对大量集中请求时会产生哪些问题

当应用服务器接收到的请求增多时,如果一个请求尚未处理完又接到了下一个请求(即两个请求几乎同时到达),那么后一个请求将被加入队列,等待前一个请求处理完毕。即便是可以并行处理多个请求的服务器,一旦到了并行处理数的极限,后到的请求也会被加入队列,等待处理的线程将越来越多。

这种状态会使应用服务器进入高负荷状态,出现 CPU 负担加重、内存空间不足等问题。

高负荷状态下应用可能无法正常运行,或者性能出现明显下降。以第 2 章中编写的 Web 应用为例,这个应用在 Python 的 SimpleHTTPServer 模块的 Web 应用服务器上运行,然而这个服务器是单进程单线程的,无法同时处理多个请求。因此请求集中到达时等待处理的请求会增多,应用性能会下降。

另外,等待队列也是有上限的,具体上限与硬件本身的性能有关。一旦等待数达到上限,新的请求将被视为无法处理的请求,表现为切断连接,请求失败。另外,队列达到上限有时会造成服务器的程序异常停止,导致无法连接服务器。

这些问题在客户端会表现为无法连接、反应慢、频繁报错等。该状态会影响用户对应用的正常使用。

12.1.2 针对高负荷的对策

解决高负荷状态需要哪些对策呢?

高负荷其实可以细分为很多种,不同种的负荷解决方法也不同。比如应用服务器的 CPU 处理能力不足了,结果我们换了一块性能更好的硬盘,这显然无法解决问题。

因此要明确问题所在,选择合适的对策。下面是几种典型问题及其对策。

问题- 解决方法
CPU 占用率高 增加处理的进程和线程数。更换成性能更好的 CPU
硬盘 I/O 负荷高 优化读取、写入数据的方法。更换成性能更好的硬盘。
内存不足 释放被无用程序占用、分配的内存。增加物理内存。

除此之外还有许多解决方法,不过本章将以上述 3 点为中心进行说明。另外,为提高改善性能的效率,需要遵循以下原则。

  • 从预期效果最大的方法开始尝试

  • 从所需资金、步骤、时间最少的方法开始尝试

  • 实施对策前后各评估一次性能

第一条“从预期效果最大的方法开始尝试”是因为如果先采用了效果较小的方法,日后再采用效果较大的方法时,前一个方法的效果会被覆盖掉,这就使前一次的工作成了无用功。

第二条“从所需资金、步骤、时间最少的方法开始尝试”指优先选择性价比高的方法,把成本高成效低的方法摆到次要位置。

第三条“实施对策前后各评估一次性能”是为了掌握该对策的实际效果。评估结果能明确告诉我们成本换来了多少效果。

12.2 评估留言板应用的性能

本节,我们将对第 2 章中开发的应用进行性能评估,然后学习如何让该应用在 nginx 和 gunicorn 上运行。

12.2.1 什么是应用的性能

我们总是会说“应用的性能好 / 不好”,但好与不好究竟指的是什么呢?下面是几个性能好的例子。

  • 应用的处理能力强

  • 内存占用量小

  • 页面显示速度快

  • 通信响应速度快

这些评价对象都不相同,评价中使用的词语也是强、小、快,各不一样。可见,应用的性能有多个指标。所以在谈论性能时,必须首先确定是哪方面性能,不然理解上就会出现偏差。

在 Web 应用的各项性能指标中,本章将重点研究从发送 HTTP 请求到返回响应的这段时间(即响应时间)。响应时间越短,浏览器切换页面的速度就越快,用户就会觉得应用越流畅。另外,我们在评估性能时将使用 ApacheBench(ab 命令)。这是一款基准测试工具,能简单地检测应用的响应时间和请求成功率。

12.2.2 安装 ApacheBench

ApacheBench 是 Web 服务器 Apache 附带的工具之一。通过 apt 进行安装时,要安装 apache-utils(LIST 12.1)。

LIST 12.1 通过 apt 进行安装

  1. $ sudo apt-get install apache2-utils

安装完后就能使用 ab 命令了。本章使用的是 ApacheBench 的 2.3 版本。

12.2.3 用 ApachBench 评估性能

这里运行第 2 章中开发的应用,用 ab 命令检测响应时间。这里以留言板首页的响应速度为检测对象。我们希望在已提交数据的状态下进行检测,因此事先输入了 50 条数据。另外,应用的运行环境如下。

OSUbuntu 14.04(64bit 版)
CPUIntel Core 2 Duo 2.2GHz(双核)
内存512MB

检测前先启动应用服务器(LIST 12.2)。

LIST 12.2 启动应用

  1. $ python guestbook.py
  2. Running on http://127.0.0.1:8000/
  3. Restarting with reloader

使用 screen 命令的情况下会弹出新窗口,在新窗口中执行 ab 命令。不使用 screen 命令时则需要按 Ctrl+Z 键,将运行中的应用移至后台,然后再执行 ab 命令。

应用可以通过 IP 地址 127.0.0.1、端口号 8000 访问,所以 ab 命令要指定这个 URL(LIST 12.3)。-n 选项可以指定请求次数,-c 选项可以指定连接数。这里我们把总计 1000 次请求分 100 个连接并行发送。

LIST 12.3 执行 ab 命令

  1. $ ab -n 1000 -c 100 http://127.0.0.1:8000/

检测结束后会输出如 LIST 12.4 所示的报告。

  1. Server Software: Werkzeug/0.9.6
  2. Server Hostname: 127.0.0.1
  3. Server Port: 8000
  4. Document Path:
  5. Document Length: 7398 bytes
  6. Concurrency Level: 100
  7. Time taken for tests: 4.911 seconds
  8. Complete requests: 1000
  9. Failed requests: 0
  10. Write errors: 0
  11. Total transferred: 7554000 bytes
  12. HTML transferred: 7398000 bytes
  13. Requests per second: 203.61 [#sec] (mean)
  14. Time per request: 491.146 [ms] (mean)
  15. Time per request: 4.911 [ms] (mean, across all concurrent requests)
  16. Transfer rate: 1501.99 [Kbytes/sec] received
  17. Connection Times (ms)
  18. min mean[+/-sd] median max
  19. Connect: 0 0 1.1 0 5
  20. Processing: 19 466 82.5 489 497
  21. Waiting: 19 466 82.5 489 497
  22. Total: 24 467 81.5 489 497
  23. Percentage of the requests served within a certain time (ms)
  24. 50% 489
  25. 66% 490
  26. 75% 491
  27. 80% 491
  28. 90% 492
  29. 95% 496
  30. 98% 497
  31. 99% 497
  32. 100% 497 (longest request)

接下来了解一下检测结果中需要注意的地方。

Complete requests

Complete requests 部分显示了请求成功与失败的情况。通过上面的例子,我们可以看到,1000 个请求全部成功。如果请求没有全部成功,表示可能超出了 Web 服务器的处理能力。

  1. Complete requests: 1000
  2. Failed requests: 0

Requests per second

1 秒处理的请求数。下述结果表示每秒大约处理 203 个请求。

  1. Requests per second: 203.61 [#/sec] (mean)

Connection Times

Connection Times 部分可以查看处理 1 个请求所需的连接时间(Connect)、处理时间(Processing)、等待时间(Waiting),单位精确到毫秒。各列元素分别代表最小值(min)、平均值(mean)、标准差([+/-sd])、中间值(median)、最大值(max)。如果某个时间存在明显的不平均现象,证明应用的某个部分有可能存在瓶颈。

以下述执行结果为例,我们是在本地环境中连接本地的 Web 应用,所以连接时间非常短。另外,处理时间和等待时间都没有明显的不平均现象,证明应用中不存在瓶颈。

  1. Connection Times (ms)
  2. min mean[+/-sd] median max
  3. Connect: 0 0 1.1 0 5
  4. Processing: 19 466 82.5 489 497
  5. Waiting: 19 466 82.5 489 497
  6. Total: 24 467 81.5 489 497

我们的这个留言板应用直接使用了 Flask 内置的 HTTP 服务器。该内置 HTTP 服务器由 Python 标准模块的 HTTPServer 类扩展而来。这个服务器并不算特别慢,但如果导入更高速的 Python 专用应用服务器,将能明显改善响应性能。

接下来我们将导入 gunicorn,看看这个 Python 的面向 Web 应用的 HTTP 服务器是如何改善响应性能的。

NOTE

本章用 ApacheBench 评估了应用的性能,但要注意,这与 Web 应用实际运作时的情况不完全相同。

ApacheBench 的评估对象是单一URL,但一般的 Web 应用除HTML 以外还需要对图片、CSS、JavaScript 文件发送请求。要获取某 URL 页面中的所有元素,有时需要几十个请求。因此各位要记住,ab 命令检测出的响应性能与用户体感的响应时间之间是存在差异的。

12.3 gunicorn 简介

gunicorn 是面向 Python WSGI 应用的 HTTP 服务器。它在 Linux/Unix 上运行,优点是轻量级且速度快。gunicorn 通过一个控制进程和多个工作进程来执行应用程序。由于它是一个 Python 模块,所以可以变更工作进程的类以及扩展服务器本身。

12.3.1 安装 gunicorn

gunicorn 可以用 pip 命令安装(LIST 12.5)。

LIST 12.5 用 pip 命令安装

  1. $ pip install gunicorn

通过上述方法安装 gunicorn 后,就可以使用 gunicorn 命令了。

12.3.2 在 gunicorn 上运行应用

首先设置一个工作进程,运行应用(LIST 12.6)。

LIST 12.6 用 gunicorn 命令运行留言板应用

  1. $ gunicorn -w 1 -b 127.0.0.1:8000 guestbook

我们在这个状态下再执行一次 ab 命令,检测结果如下。

  1. Server Software: gunicorn/19.1.1
  2. Server Hostname: 127.0.0.1
  3. Server Port: 8000
  4. Document Path:
  5. Document Length: 7398 bytes
  6. Concurrency Level: 100
  7. Time taken for tests: 4.469 seconds
  8. Complete requests: 1000
  9. Failed requests: 0
  10. Write errors: 0
  11. Total transferred: 7560000 bytes
  12. HTML transferred: 7398000 bytes
  13. Requests per second: 223.75 [#sec] (mean)
  14. Time per request: 446.937 [ms] (mean)
  15. Time per request: 4.469 [ms] (mean, across all concurrent requests)
  16. Transfer rate: 1651.87 [Kbytes/sec] received
  17. Connection Times (ms)
  18. min mean[+/-sd] median max
  19. Connect: 0 1 2.2 0 8
  20. Processing: 31 424 71.7 444 462
  21. Waiting: 14 424 71.8 443 462
  22. Total: 37 425 69.8 444 469
  23. Percentage of the requests served within a certain time (ms)
  24. 50% 444
  25. 66% 444
  26. 75% 444
  27. 80% 444
  28. 90% 445
  29. 95% 445
  30. 98% 446
  31. 99% 446
  32. 100% 469 (longest request)

可以看到与之前用 Flask 内置服务器时的结果差别不大。

由于当前运行应用的虚拟机的 CPU 数设置为 2,因此单独一个工作进程无法充分利用 CPU 资源。所以进程数应至少设置为 2。

于是我们将 gunicorn 的工作进程设置为 2,再次执行 ab 命令进行检测。工作进程数用 -w 选项指定(LIST 12.7)。

LIST 12.7 用 gunicorn 命令运行应用(工作进程数为 2)

  1. $ gunicorn -w 2 -b 127.0.0.1:8000 guestbook

检测结果如下。

  1. Server Software: gunicorn/19.1.1
  2. Server Hostname: 127.0.0.1
  3. Server Port: 8000
  4. Document Path:
  5. Document Length: 7398 bytes
  6. Concurrency Level: 100
  7. Time taken for tests: 2.376 seconds
  8. Complete requests: 1000
  9. Failed requests: 0
  10. Write errors: 0
  11. Total transferred: 7560000 bytes
  12. HTML transferred: 7398000 bytes
  13. Requests per second: 420.91 [#sec] (mean)
  14. Time per request: 237.582 [ms] (mean)
  15. Time per request: 2.376 [ms] (mean, across all concurrent requests)
  16. Transfer rate: 3107.48 [Kbytes/sec] received
  17. Connection Times (ms)
  18. min mean[+/-sd] median max
  19. Connect: 0 1 1.6 0 7
  20. Processing: 18 225 37.3 235 248
  21. Waiting: 17 225 37.3 235 247
  22. Total: 25 226 35.8 235 252
  23. Percentage of the requests served within a certain time (ms)
  24. 50% 235
  25. 66% 236
  26. 75% 236
  27. 80% 236
  28. 90% 236
  29. 95% 237
  30. 98% 238
  31. 99% 238
  32. 100% 252 (longest request)

平均响应时间缩短了 40%,可见速度快了许多。接下来我们再看看用作 Web 服务器和代理服务器的 nginx。

12.4 nginx 简介

nginx(Engine X)1 是 Nginx Inc. 开发的 Web 服务器,拥有 HTTP 服务器、代理服务器等功能,轻量级且速度快。

1http://nginx.org/

近年来 nginx 成长迅速,在 Web 服务器界占有的份额仅次于 Apache、IIS(Internet Information Services),居世界第三(约 14%)。一些较大规模的 Web 服务也采用了 nginx,比如 WordPress.com2、GitHub3、Instagram4 等。

2https://wordpress.com/

3https://github.com/

4http://instagram.com

12.4.1 安装 nginx

nginx 的安装可以直接从源码构建,不过由于 Ubuntu 的版本库中有相关程序包,所以我们用 apt-get 命令进行安装(LIST 12.8)。

LIST 12.8 安装 nginx

  1. $ sudo apt-get install nginx

本书使用了 nginx 的 1.4.6 版本(LIST 12.9)。

LIST 12.9 查看版本

  1. $ nginx -v
  2. nginx version: nginx/1.4.6 (Ubuntu)

启动、停止等操作通过 service 命令实现(LIST 12.10 ~ LIST 12.13)。

LIST 12.10 启动 nginx

  1. $ sudo service nginx start

LIST 12.11 停止 nginx

  1. $ sudo service nginx stop

LIST 12.12 重启 nginx

  1. $ sudo service nginx restart

LIST 12.13 重载 nginx

  1. $ sudo service nginx reload

测试设置文件是否有错时,执行配置测试(ConfigTest)(LIST 12.14)。

LIST 12.14 nginx 的配置测试

  1. $ sudo nginx -t

12.4.2 检测nginx 的性能

前面我们检测了留言板应用的性能,现在我们看看 Web 服务器 nginx 本身的性能如何。

由于要运行默认站点,所以这里我们先用管理员权限编辑设置文件 etcnginx/sites-available/default(LIST 12.15)。如果 Apache 等已安装的软件已经占用了端口 80,则需要将 listen 后的端口号修改成其他数值。

LIST 12.15 etcnginx/sites-available/default

  1. server {
  2. listen 80; # 使用端口80
  3. server_name localhost; # 主机名为localhost
  4. # 访问日志的文件路径
  5. access_log varlog/nginx/localhost.access.log;
  6. location { # 域名后的路径为
  7. root varwww; # 根目录
  8. index index.html index.htm; # 索引文件的文件名
  9. }
  10. }

这里不存在已设置好的根目录 varwww 时,需要手动创建(LIST 12.16)。

LIST 12.16 创建 varwww 目录

  1. $ sudo mkdir varwww
  2. $ sudo chown www-data:www-data varwww

用管理员权限生成用于默认站点的目录(LIST 12.17)。

LIST 12.17 varwww/index.html

  1. <html>
  2. <head>
  3. <meta http-equiv="Contenttype" content="text/html; charset=utf-8">
  4. <title> 测试页面</title>
  5. </head>
  6. <body>
  7. <h1> 测试页面</h1>
  8. </body>
  9. </html>

另外,有时候我们会遇到工作进程数默认为 1 的情况,所以要根据 CPU 数(核心数)修改 etcnginx/nginx.conf 的 worker_processes。这里我们设置为 2(LIST 12.18)。

LIST 12.18 etcnginx/nginx.conf

  1. user www-data;
  2. worker_processes 2;
  3. # 下略

编辑完成后执行配置测试,确认没有问题后重载或重启 nginx(LIST 12.19)。

LIST 12.19 nginx 的配置测试与重载

  1. $ sudo nginx -t
  2. the configuration file etcnginx/nginx.conf syntax is ok
  3. configuration file etcnginx/nginx.conf test is successful
  4. $ sudo service nginx reload
  5. * Reloading nginx configuration nginx [ OK ]

接下来用 ApacheBench 评估性能(LIST 12.20)。

LIST 12.20 执行 ab 命令

  1. $ ab -n 1000 -c 100 http://127.0.0.1/

评估结果如下。

  1. Server Software: nginx/1.4.6
  2. Server Hostname: 127.0.0.1
  3. Server Port: 80
  4. Document Path:
  5. Document Length: 194 bytes
  6. Concurrency Level: 100
  7. Time taken for tests: 0.214 seconds
  8. Complete requests: 1000
  9. Failed requests: 0
  10. Write errors: 0
  11. Total transferred: 404000 bytes
  12. HTML transferred: 194000 bytes
  13. Requests per second: 4662.92 [#sec] (mean)
  14. Time per request: 21.446 [ms] (mean)
  15. Time per request: 0.214 [ms] (mean, across all concurrent requests)
  16. Transfer rate: 1839.67 [Kbytes/sec] received
  17. Connection Times (ms)
  18. min mean[+/-sd] median max
  19. Connect: 0 9 2.7 10 11
  20. Processing: 1 12 3.8 11 26
  21. Waiting: 1 10 4.1 9 25
  22. Total: 11 21 2.9 21 29
  23. Percentage of the requests served within a certain time (ms)
  24. 50% 21
  25. 66% 21
  26. 75% 21
  27. 80% 21
  28. 90% 23
  29. 95% 26
  30. 98% 28
  31. 99% 29
  32. 100% 29 (longest request)

与 gunicorn 上运行的留言板相比,响应速度快出了一个层级。

12.5 在 nginx 和 gunicorn 上运行应用

现在在 gunicorn 上运行留言板应用,然后通过 nginx 进行反向代理,返回响应。通过反向代理响应可以将留言板应用的服务器横向扩展成多个。这里我们不进行横向扩展,只评估单一服务器的应用性能。

12.5.1 gunicorn 的设置

gunicorn 可以指定 -D 选项将进程转为守护进程,即守护进程化(Daemonize)(LIST 12.21)。

LIST 12.21 通过 gunicorn 命令将留言板应用转为守护进程并执行

  1. $ gunicorn -w 2 -b 127.0.0.1:8000 -D guestbook

NOTE

守护进程化是指以 Unix 守护进程的形式运行程序。Unix 守护进程运行在后台,并且启动之后不需要用户直接控制。

以 gunicorn 为例,给 gunicorn 命令指定 -D 选项后,gunicorn 便脱离了用户的控制,会转为持续等待请求的后台进程。

12.5.2 nginx 的设置

编辑 nginx 默认站点的设置文件,以反向代理的形式连接在 gunicorn 上运行的应用(LIST 12.22)。

LIST 12.22 etcnginx/sites-available/default

  1. # 名为guestbook 的upstream(上游服务器)的设置
  2. upstream guestbook {
  3. server 127.0.0.1:8000;
  4. }
  5. server {
  6. listen 80; # 使用端口80
  7. server_name localhost; # 主机名为localhost
  8. # 访问日志的文件路径
  9. access_log varlog/nginx/localhost.access.log;
  10. location { # 域名后的路径为
  11. # 反向代理到名为guestbook 的upstream
  12. proxy_pass http://guestbook;
  13. }
  14. }

upstream 指令里可以设置多个服务器。在一台应用服务器无法满足处理需求时,可以在 upstream 里设置多台应用服务器来分担负荷。

设置文件编辑完成后要记得进行配置测试及重载。

12.5.3 评估 nginx+gunicorn 的性能

接下来用 ApacheBench 评估性能(LIST 12.23)。

LIST 12.23 执行 ab 命令

  1. $ ab -n 1000 -c 100 http://127.0.0.1/

评估结果如下。

  1. Server Software: nginx/1.4.6
  2. Server Hostname: 127.0.0.1
  3. Server Port: 80
  4. Document Path:
  5. Document Length: 7398 bytes
  6. Concurrency Level: 100
  7. Time taken for tests: 2.655 seconds
  8. Complete requests: 1000
  9. Failed requests: 0
  10. Write errors: 0
  11. Total transferred: 7556000 bytes
  12. HTML transferred: 7398000 bytes
  13. Requests per second: 376.61 [#sec] (mean)
  14. Time per request: 265.529 [ms] (mean)
  15. Time per request: 2.655 [ms] (mean, across all concurrent requests)
  16. Transfer rate: 2778.95 [Kbytes/sec] received
  17. Connection Times (ms)
  18. min mean[+/-sd] median max
  19. Connect: 0 1 3.6 0 14
  20. Processing: 22 252 42.2 264 273
  21. Waiting: 22 252 42.2 264 273
  22. Total: 27 253 39.8 264 286
  23. Percentage of the requests served within a certain time (ms)
  24. 50% 264
  25. 66% 265
  26. 75% 265
  27. 80% 265
  28. 90% 266
  29. 95% 266
  30. 98% 267
  31. 99% 268
  32. 100% 286 (longest request)

与单独由 gunicorn 构成的服务器相比,响应时间略有延长,但考虑到可用多台服务器构成服务器组,这点损失还是可以接受的。

12.5.4 性能比较

最后我们来比较一下改善前和改善后的性能评估结果。评估结果如 LIST 12.24 和 LIST 12.25 所示。

LIST 12.24 改善前的评估结果(精选)

  1. Connection Times (ms)
  2. min mean[+/-sd] median max
  3. Connect: 0 0 1.1 0 5
  4. Processing: 19 466 82.5 489 497
  5. Waiting: 19 466 82.5 489 497
  6. Total: 24 467 81.5 489 497

LIST 12.25 改善后的评估结果(精选)

  1. Connection Times (ms)
  2. min mean[+/-sd] median max
  3. Connect: 0 1 3.6 0 14
  4. Processing: 22 252 42.2 264 273
  5. Waiting: 22 252 42.2 264 273
  6. Total: 27 253 39.8 264 286

从 Flask 的开发服务器换到 nginx 和 gunicorn 组成的服务器之后,Total 的时间比改善前缩短了近一半。响应时间缩短了,因此我们可以认为有改善效果。至此改善工作结束。

NOTE

若想在上述基础上进一步缩短响应时间,可以考虑下述对策。出于篇幅原因,这里就不进行详细说明了。

  • 数据库方面将 shelve 换成 MySQL 等REBMS(加快数据库访问速度)

  • 在别的机器上运行应用,增加机器数(增加可同时处理的请求数)

  • 使用 memcached 等缓存服务器(减少访问数据库的次数)

12.6 小结

本章首先讲了对 Web 应用集中访问时会产生的服务器负担加重、应用运行不稳定等问题。

接下来介绍了该类问题的解决方法以及性能的概念,并用 ApacheBench 对第 2 章中开发的 Web 应用进行了性能评估。另外,还讲解了如何在 gunicorn 这一应用服务器上运行 Web 应用,以及如何设置 nginx 用作反向代理。

改善性能的目的是让已有系统的性能最优化。然而,在我们花大块时间一次次寻求改善时,会发现新解决方案的效果总是越来越小。因此,改善性能之前要先定一个性能的目标值,确定要花多少时间来做这件事。着手改善前切记要先对现有系统进行评估,通过比较确定目标。选一个合适的目标,用最少的时间和资源完成最优化工作,这才是我们追求的高效的性能改善。