Linux网络应用程序性能。 引言

Web应用程序现在被广泛使用,而HTTP是所有传输协议中最大的份额。 在研究开发Web应用程序的细微差别时,大多数人很少关注这些应用程序实际运行所在的操作系统。 开发(Dev)与运营(Ops)的分离只会使情况变得更糟。 但是随着DevOps文化的传播,开发人员开始承担起在云中启动其应用程序的责任,因此,对他们全面了解操作系统后端非常有用。 如果您要为成千上万的并发连接部署系统,则此功能特别有用。

Web服务中的限制与其他应用程序中的限制非常相似。 无论是负载平衡器还是数据库服务器,所有这些应用程序在高性能环境中都存在类似的问题。 了解这些基本限制以及通常如何克服这些限制将帮助您评估Web应用程序的性能和可伸缩性。

我写这一系列文章是为了回应年轻开发人员的问题,他们希望成为消息灵通的系统架构师。 如果不深入研究Linux应用程序在操作系统级别的工作原理,就不可能清楚地了解它们的优化方法。 尽管应用程序类型很多,但是在本系列中,我想探索网络应用程序,而不是浏览器或文本编辑器之类的桌面应用程序。 本材料适用于希望了解Linux或Unix程序如何工作以及如何构建高性能程序的开发人员和架构师。

Linux是服务器操作系统,大多数情况下,您的应用程序都在此特定OS上运行。 尽管我说的是“ Linux”,但是大多数时候您可以放心地假设所有一般的类Unix操作系统都是必需的。 但是,我尚未在其他系统上测试过随附的代码。 因此,如果您对FreeBSD或OpenBSD感兴趣,结果可能会有所不同。 当我尝试某些特定于Linux的东西时,我会指出。

尽管您可以使用这些知识从头开始创建应用程序,并且将对其进行完美的优化,但最好不要这样做。 如果您为组织的业务应用程序使用C或C ++编写新的Web服务器,则可能是您工作的最后一天。 但是,了解这些应用程序的结构将有助于选择现有程序。 您可以将基于进程的系统与基于线程和基于事件的系统进行比较。 您将理解和欣赏Nginx为什么比Apache httpd更好,为什么基于Tornado的Python应用程序可以比基于Django的Python应用程序服务更多的用户。

ZeroHTTPd:学习工具


ZeroHTTPd是一个Web服务器,我从头开始用C编写了它作为培训工具。 它没有外部依赖性,包括对Redis的访问。 我们运行自己的Redis例程。 有关更多详细信息,请参见下文。

尽管我们可以讨论很长一段时间的理论,但没有什么比编写代码,运行它以及将所有服务器体系结构一起进行比较更好了。 这是最明显的方法。 因此,我们将使用每种模型编写一个简单的ZeroHTTPd Web服务器:基于进程,线程和事件。 让我们检查这些服务器中的每一个,并比较它们的工作方式。 ZeroHTTPd是在单个C文件中实现的;基于事件的服务器包括uthash ,这是一个出色的哈希表实现,它包含在单个头文件中。 在其他情况下,没有依赖关系,以免使项目复杂化。

在代码中有很多注释可以帮助您解决问题。 作为使用几行代码的简单Web服务器,ZeroHTTPd还是最小的Web开发框架。 它的功能有限,但是能够生成静态文件和非常简单的“动态”页面。 我必须说ZeroHTTPd非常适合学习如何创建高性能Linux应用程序。 总的来说,大多数Web服务都在等待请求,检查请求并进行处理。 这正是ZeroHTTPd将要做的。 这是一种学习工具,而不是生产工具。 他不善于处理错误,也不太可能吹嘘最佳安全实践(哦,是的,我使用strcpy )或C的深奥技巧,但我希望他能做好自己的工作。


ZeroHTTPd主页。 它可以产生不同类型的文件,包括图像

留言簿申请


现代Web应用程序通常不限于静态文件。 它们与各种数据库,缓存等具有复杂的交互。因此,我们将创建一个名为“ Guestbook”的简单Web应用程序,访问者在该应用程序中将条目保留在其姓名下。 留言簿将保存以前留下的条目。 页面底部还设有一个访客计数器。


留言簿Web应用程序ZeroHTTPd

访客计数器和留言簿条目存储在Redis中。 为了与Redis通信,将实现自己的过程;它们独立于外部库。 当有公开可用和经过测试的解决方案时,我不喜欢滚动自己编写的代码。 但是ZeroHTTPd的目标是研究Linux性能和对外部服务的访问,而服务HTTP请求则严重影响性能。 我们必须在每个服务器体系结构中完全控制与Redis的通信。 在一种架构中,我们使用阻塞调用,在其他架构中,我们使用基于事件的过程。 使用外部Redis客户端库不会提供这种控制。 此外,我们的Redis小客户端仅执行一些功能(获取,设置和增加键;获取并添加到阵列)。 此外,Redis协议非常优雅和简单。 他甚至不需要特别的教导。 该协议以大约一百行代码执行所有工作的事实表明,它经过了深思熟虑。

下图显示了客户端(浏览器)请求/guestbookURL时的应用程序。


留言簿应用程序的机制

当您需要发布留言簿页面时,有一个对文件系统的调用以将模板读入内存,而对Redis的三个网络调用。 模板文件包含以上屏幕快照中页面的大多数HTML内容。 对于内容的动态部分,还有特殊的占位符:记录和访客计数器。 我们从Redis获得它们,将它们插入页面中,并为客户提供完整的内容。 可以避免对Redis的第三次调用,因为Redis递增时会返回新的键值。 但是,对于我们的基于异步事件架构的服务器,许多网络调用对于培训而言都是不错的测试。 因此,我们丢弃有关访问者数量的Redis返回值,并在单独的调用中请求它。

ZeroHTTPd服务器架构


我们正在构建七个具有相同功能但架构不同的ZeroHTTPd版本:

  • 迭代式
  • 分支服务器(每个请求一个子进程)
  • 前叉服务器(前叉过程)
  • 带线程的服务器(每个请求一个线程)
  • 具有预线程的服务器
  • 基于poll()的架构
  • Epoll架构

我们通过向服务器加载HTTP请求来衡量每种体系结构的性能。 但是,当比较具有高度并行性的体系结构时,请求的数量会增加。 我们测试了三次,并考虑了平均值。

测试方法



用于压力测试ZeroHTTPd的安装

重要的是,执行测试时,所有组件都不能在同一台机器上工作。 在这种情况下,由于组件争夺CPU,因此OS承担了额外的计划开销。 测量每个选定服务器体系结构的操作系统开销是此练习的最重要目标之一。 添加更多变量将对该过程有害。 因此,上图中的设置效果最佳。

这些服务器各自做什么


  • load.unixism.net:在这里,我们运行Apache Benchmark实用工具ab 。 它生成测试我们的服务器体系结构所需的负载。
  • nginx.unixism.net:有时我们想运行一个服务器程序的多个实例。 为此,具有适当设置的Nginx服务器充当了从ab到我们服务器进程的负载平衡器。
  • zerohttpd.unixism.net:在这里,我们在七个不同的体系结构上运行我们的服务器程序,一次运行一个。
  • redis.unixism.net:Redis守护程序正在此服务器上运行,其中条目存储在留言簿和访客计数器中。

所有服务器都在单个处理器内核上运行。 这个想法是评估每种架构的最大性能。 由于所有服务器程序均在同一硬件上进行测试,因此这是进行比较的基本级别。 我的测试设置包括从Digital Ocean租用的虚拟服务器。

我们在测量什么?


您可以测量不同的指标。 我们评估此配置中每种体系结构的性能,以不同的并发级别向服务器加载请求:负载从20个并发用户增长到15,000个。

测试结果


下图显示了在不同并发级别上不同体系结构上的服务器的性能。 y轴是每秒的请求数,x轴是并行连接。







下表是结果表。

每秒请求
并发反复的叉子前叉流式预先串流投票投票
2071122100180022501900年2050年
50719022001700220020002000
100724522001700220021502100
200733023001750230022002100
300--38022001800240022502150
400--41022001750260020002000
500--44023001850年27001900年2212
600--46024001800250017002519
700--46024001600249015502607
800--46024001600254014002553
900--46023001600247212002567
1000--47523001700248511502439
1500--4902400155026209002479
2000--3502400140023965502200
2500--2802100130024534902262
3000--2801900年12502502广泛传播2138
5000--广泛传播160011002519--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个以上的请求,我们只剩下两个参与者:前叉和epoll。 随着负载的增加,基于轮询的服务器的性能要比流式服务器差。 预线程体系结构与epoll竞争:这证明Linux内核计划大量线程的能力。

源代码ZeroHTTPd


ZeroHTTPd的源代码在这里 。 每个体系结构都有一个单独的目录。

 零HTTPd
 │
 ├──01_迭代
 │├──main.c
 ├──02_分叉
 │├──main.c
 ├──03_前叉
 │├──main.c
 ├──04_线程
 │├──main.c
 ├──05_预螺纹
 │├──main.c
 ├──06_民意调查
 │├──main.c
 ├──07_epoll
 │└──main.c
 ├──Makefile
 ├──公众
 │├──index.html
 │└──tux.png
 └──模板
     └──留言簿
         └──index.html 

除了所有体系结构的七个目录外,顶层目录中还有两个目录:public和模板。 第一个包含index.html文件和第一个屏幕截图中的图像。 可以将其他文件和文件夹放在此处,ZeroHTTPd应该发出这些静态文件而不会出现问题。 如果浏览器中的路径与公用文件夹中的路径匹配,则ZeroHTTPd将在此目录中查找index.html文件。 留言簿内容是动态生成的。 它只有一个主页,其内容基于文件“ templates / guestbook / index.html”。 ZeroHTTPd轻松添加动态页面以进行扩展。 这个想法是用户可以将模板添加到该目录,并根据需要扩展ZeroHTTPd。

要构建所有七个服务器,请从顶级目录运行make all all-所有构建都将出现在此目录中。 可执行文件从运行它们的目录中查找public和template目录。

Linux API


要了解本系列文章中的信息,没有必要精通Linux API。 但是,我建议阅读更多有关此主题的信息,网络上有很多参考资源。 尽管我们将介绍Linux API的几种类别,但我们的重点将主要放在流程,线程,事件和网络堆栈上。 除了有关Linux API的书籍和文章之外,我还建议阅读有关系统调用和所用库函数的法力。

性能和可扩展性


关于性能和可伸缩性的一项说明。 从理论上讲,它们之间没有联系。 您可能有一个运行良好的Web服务,响应时间为几毫秒,但根本无法扩展。 同样,可能有一个运行不佳的Web应用程序,需要花费几秒钟的时间进行响应,但是它可以扩展到数十个,以处理数万个并发用户。 但是,高性能和可伸缩性的组合是非常强大的组合。 高性能应用程序通常以经济的方式使用资源,从而有效地为服务器上的更多并发用户提供服务,从而降低了成本。

CPU和I / O任务


最后,计算中总是有两种可能的任务类型:用于I / O和CPU。 通过Internet接收请求(网络I / O),文件维护(网络和磁盘I / O),与数据库通信(网络和磁盘I / O)都是I / O操作。 一些数据库查询可能会稍微增加CPU的负载(排序,计算一百万个结果的平均值等)。 大多数Web应用程序受最大可能的I / O限制,并且很少以满负荷使用处理器。 当您发现某些CPU使用大量CPU时,很可能是应用程序体系结构不佳的迹象。 这可能意味着将CPU资源用于过程控制和上下文切换-但这并不完全有用。 如果您正在执行图像处理,音频转换或机器学习之类的操作,则该应用程序需要强大的CPU资源。 但是对于大多数应用来说并非如此。

有关服务器架构的更多信息


  1. 第一部分:迭代架构
  2. 第二部分 货叉服务器
  3. 第三部分 前叉服务器
  4. 第四部分 带线程的服务器
  5. 第五部分:具有线程预创建的服务器
  6. 第六部分 基于轮询的架构
  7. 第七部分 Epoll架构

Source: https://habr.com/ru/post/zh-CN455212/


All Articles