现在Web应用已经随处可见,而在所有的传输协议中,HTTP占据了最大的份额。 在研究 Web 应用程序开发的细微差别时,大多数人很少关注这些应用程序实际运行的操作系统。 开发(Dev)和运营(Ops)的分离只会让情况变得更糟。 但随着 DevOps 文化的兴起,开发人员开始负责在云中运行他们的应用程序,因此彻底熟悉操作系统的后端对他们来说非常有用。 如果您尝试为数千或数万个同时连接部署系统,这尤其有用。
Web 服务中的限制与其他应用程序中的限制非常相似。 无论是负载均衡器还是数据库服务器,所有这些应用程序在高性能环境中都存在类似的问题。 了解这些基本限制以及如何克服它们将有助于您评估 Web 应用程序的性能和可扩展性。
我写这一系列文章是为了回答那些想成为消息灵通的系统架构师的年轻开发人员的问题。 如果不深入了解 Linux 应用程序优化技术在操作系统级别的工作原理,就不可能清楚地理解它们。 尽管应用程序有很多种类型,但在本系列中,我想探索基于 Web 的应用程序,而不是浏览器或文本编辑器等桌面应用程序。 本材料面向想要了解 Linux 或 Unix 程序如何工作以及如何构建它们以实现高性能的开发人员和架构师。
Linux 是 服务器机房 操作系统,并且大多数情况下您的应用程序都在此操作系统上运行。 虽然我说的是“Linux”,但大多数时候您可以放心地假设我指的是所有类 Unix 操作系统。 但是,我尚未在其他系统上测试随附的代码。 因此,如果您对 FreeBSD 或 OpenBSD 感兴趣,您的结果可能会有所不同。 当我尝试一些 Linux 特有的东西时,我会指出来。
虽然您可以使用这些知识从头开始构建应用程序并且它将得到完美优化,但最好不要这样做。 如果您用 C 或 C++ 为组织的业务应用程序编写新的 Web 服务器,这可能是您工作的最后一天。 然而,了解这些应用程序的结构将有助于选择现有程序。 您将能够将基于流程的系统与基于线程的系统以及基于事件的系统进行比较。 您将理解并理解为什么 Nginx 的性能比 Apache httpd 更好,为什么与基于 Django 的 Python 应用程序相比,基于 Tornado 的 Python 应用程序可以为更多用户服务。
ZeroHTTPd:学习工具
尽管我们可以详细讨论理论,但没有什么比编写代码、运行代码并相互比较所有服务器架构更好的了。 这是最明显的方法。 因此,我们将使用每种模型编写一个简单的 ZeroHTTPd Web 服务器:基于进程、基于线程和基于事件。 让我们检查一下每台服务器,看看它们的性能对比如何。 ZeroHTTPd 在单个 C 文件中实现。基于事件的服务器包括
代码中有很多注释可以帮助你理解。 ZeroHTTPd 是一个由几行代码组成的简单 Web 服务器,也是一个用于 Web 开发的最小框架。 它的功能有限,但能够提供静态文件和非常简单的“动态”页面。 我不得不说 ZeroHTTPd 对于学习如何创建高性能 Linux 应用程序很有帮助。 总的来说,大多数 Web 服务都会等待请求、检查并处理它们。 这正是 ZeroHTTPd 要做的事情。 这是一个学习工具,而不是生产工具。 它在错误处理方面表现不佳,并且不太可能拥有最佳安全实践(哦,是的,我使用过 strcpy
)或者 C 语言的巧妙技巧。但我希望它能很好地完成它的工作。
ZeroHTTPd 主页。 它可以输出不同的文件类型,包括图像
留言簿申请
现代 Web 应用程序通常不限于静态文件。 他们与各种数据库、缓存等进行复杂的交互。因此,我们将创建一个名为“留言簿”的简单 Web 应用程序,访问者可以在其姓名下留下条目。 留言簿存储之前留下的条目。 页面底部还有一个访客计数器。
Web 应用程序“留言簿”ZeroHTTPd
访客计数器和留言簿条目存储在 Redis 中。 对于与 Redis 的通信,实现了自己的程序;它们不依赖于外部库。 当有公开可用且经过充分测试的解决方案时,我不太热衷于推出自制代码。 但 ZeroHTTPd 的目的是研究 Linux 性能和访问外部服务,而服务 HTTP 请求会对性能产生严重影响。 我们必须完全控制每个服务器架构中与 Redis 的通信。 在某些体系结构中,我们使用阻塞调用,在其他体系结构中,我们使用基于事件的过程。 使用外部 Redis 客户端库将无法提供此控制。 此外,我们的小型 Redis 客户端仅执行一些功能(获取、设置和递增键;获取和附加到数组)。 另外,Redis协议极其优雅和简单。 你甚至不需要专门教它。 该协议用大约一百行代码完成了所有工作,这一事实表明它是经过深思熟虑的。
下图展示了当客户端(浏览器)请求时应用程序会做什么 /guestbookURL
.
留言簿应用程序的工作原理
当需要发布留言簿页面时,需要对文件系统进行一次调用以将模板读入内存,并对 Redis 进行 XNUMX 次网络调用。 模板文件包含上面屏幕截图中页面的大部分 HTML 内容。 内容的动态部分还有特殊的占位符:帖子和访客计数器。 我们从 Redis 接收它们,将它们插入页面并为客户端提供完整的内容。 可以避免对 Redis 的第三次调用,因为 Redis 在递增时返回新的键值。 然而,对于我们的服务器来说,它具有基于异步事件的架构,大量的网络调用对于学习目的来说是一个很好的测试。 所以我们丢弃访问者数量的Redis返回值,并通过单独的调用来查询它。
服务器架构 ZeroHTTPd
我们正在构建具有相同功能但不同架构的 ZeroHTTPd 的七个版本:
- 迭代
- Fork 服务器(每个请求一个子进程)
- 预分叉服务器(进程预分叉)
- 具有执行线程的服务器(每个请求一个线程)
- 具有预线程创建的服务器
- 基于架构
poll()
- 基于架构
epoll
我们通过向服务器加载 HTTP 请求来衡量每种架构的性能。 但在比较高度并行架构时,查询数量会增加。 我们测试三次并计算平均值。
测试方法
ZeroHTTPd 负载测试设置
运行测试时,所有组件不要在同一台机器上运行,这一点很重要。 在这种情况下,由于组件竞争 CPU,操作系统会产生额外的调度开销。 测量每个选定服务器架构的操作系统开销是本次练习最重要的目标之一。 添加更多变量将对流程产生不利影响。 因此,上图的设置效果最好。
这些服务器分别做什么?
- load.unixism.net:这是我们运行的地方
ab
, Apache 基准实用程序。 它生成测试我们的服务器架构所需的负载。 - nginx.unixism.net:有时我们想要运行一个服务器程序的多个实例。 为此,具有适当设置的 Nginx 服务器充当来自以下位置的负载均衡器: ab 到我们的服务器进程。
- Zerohttpd.unixism.net:在这里,我们在七种不同的架构上运行我们的服务器程序,一次一个。
- redis.unixism.net:该服务器运行 Redis 守护进程,其中存储留言簿条目和访客计数器。
所有服务器都运行在同一处理器核心上。 这个想法是评估每个架构的最大性能。 由于所有服务器程序都在相同的硬件上进行测试,因此这是比较的基准。 我的测试设置由从 Digital Ocean 租用的虚拟服务器组成。
我们在测量什么?
您可以衡量不同的指标。 我们通过向服务器加载不同并行级别的请求来评估给定配置中每种架构的性能:负载从 20 个并发用户增长到 15 个并发用户。
测试结果
下图显示了不同架构上的服务器在不同并行级别下的性能。 y 轴是每秒请求数,x 轴是并行连接数。
下表是结果。
每秒请求数
排比
迭代的
叉
预分叉
流媒体
预流媒体
英寸
民意调查
20
7
112
2100
1800
2250
1900
2050
50
7
190
2200
1700
2200
2000
2000
100
7
245
2200
1700
2200
2150
2100
200
7
330
2300
1750
2300
2200
2100
300
–
380
2200
1800
2400
2250
2150
400
–
410
2200
1750
2600
2000
2000
500
–
440
2300
1850
2700
1900
2212
600
–
460
2400
1800
2500
1700
2519
700
–
460
2400
1600
2490
1550
2607
800
–
460
2400
1600
2540
1400
2553
900
–
460
2300
1600
2472
1200
2567
1000
–
475
2300
1700
2485
1150
2439
1500
–
490
2400
1550
2620
900
2479
2000
–
350
2400
1400
2396
550
2200
2500
–
280
2100
1300
2453
490
2262
3000
–
280
1900
1250
2502
广泛传播
2138
5000
–
广泛传播
1600
1100
2519
–
2235
8000
–
–
1200
广泛传播
2451
–
2100
10 000
–
–
广泛传播
–
2200
–
2200
11 000
–
–
–
–
2200
–
2122
12 000
–
–
–
–
970
–
1958
13 000
–
–
–
–
730
–
1897
14 000
–
–
–
–
590
–
1466
15 000
–
–
–
–
532
–
1281
从图和表中可以看出,超过 8000 个同时请求,我们只剩下两个玩家:pre-fork 和 epoll。 随着负载的增加,基于轮询的服务器的性能比流式服务器差。 线程预创建架构是 epoll 的有力竞争对手,这证明了 Linux 内核调度大量线程的能力。
ZeroHTTPd 源代码
ZeroHTTPd 源代码
ZeroHTTPd │ ├── 01_iterative │ ├── main.c ├── 02_forking │ ├── main.c ├── 03_preforking │ ├── main.c ├── 04_ 线程 │ ├── main.c ├── 05_prethreading │ ├── main.c ├── 06_poll │ ├── main.c ├── 07_epoll │ └── main.c ├── Makefile ├── public │ ├── index .html │ └── tux .png └── 模板 └── 留言簿 └── index.html
除了所有架构的七个目录之外,顶层目录中还有两个:public 和 templates。 第一个包含 index.html 文件和第一个屏幕截图中的图像。 您可以将其他文件和文件夹放在那里,ZeroHTTPd 应该可以毫无问题地提供这些静态文件。 如果浏览器中的路径与公共文件夹中的路径匹配,则 ZeroHTTPd 会在此目录中查找 index.html 文件。 留言簿的内容是动态生成的。 它只有一个主页,其内容基于文件“templates/guestbook/index.html”。 ZeroHTTPd 可以轻松添加动态页面进行扩展。 这个想法是用户可以将模板添加到此目录并根据需要扩展 ZeroHTTPd。
要构建所有七个服务器,请运行 make all
来自顶级目录 - 所有构建都将出现在该目录中。 可执行文件在启动它们的目录中查找 public 和 templates 目录。
Linux API
您无需精通 Linux API 即可理解本系列文章中的信息。 不过,我建议您阅读更多有关此主题的内容;互联网上有很多参考资源。 尽管我们将涉及 Linux API 的几个类别,但我们的重点将主要集中在进程、线程、事件和网络堆栈上。 除了有关 Linux API 的书籍和文章之外,我还建议阅读 mana 了解所使用的系统调用和库函数。
性能和可扩展性
关于性能和可扩展性的一点说明。 理论上,它们之间没有联系。 您可以拥有一个运行良好的 Web 服务,响应时间为几毫秒,但它根本无法扩展。 同样,可能有一个性能不佳的 Web 应用程序需要几秒钟的时间来响应,但它可以扩展数十秒以处理数以万计的并发用户。 然而,高性能和可扩展性的结合是一个非常强大的组合。 高性能应用程序通常会节省资源,从而有效地为服务器上的更多并发用户提供服务,从而降低成本。
CPU 和 I/O 任务
最后,在计算中总是有两种可能的任务类型:I/O 和 CPU。 通过 Internet 接收请求(网络 I/O)、提供文件服务(网络和磁盘 I/O)、与数据库通信(网络和磁盘 I/O)都是 I/O 活动。 某些数据库查询可能会占用一点 CPU 资源(排序、对一百万个结果求平均值等)。 大多数 Web 应用程序都受到最大可能 I/O 的限制,并且处理器很少满负荷使用。 当您看到某些 I/O 任务使用大量 CPU 时,这很可能是应用程序架构不佳的迹象。 这可能意味着 CPU 资源浪费在进程管理和上下文切换上——而且这并不完全有用。 如果您正在进行图像处理、音频文件转换或机器学习等操作,那么该应用程序需要强大的 CPU 资源。 但对于大多数应用程序来说,情况并非如此。
了解有关服务器架构的更多信息
来源: habr.com