对于本系列文章,HackerNews线程有一些有趣的总结。请检查一下。
Web应用程序是消费者和企业的主食。在用于移动和理解位的许多现有协议中,HTTP具有压倒性的优势。在您了解和学习Web应用程序开发的细微差别时,大多数人可能很少关注最终运行您的应用程序的操作系统。开发人员和运营人员的分离只会使情况变得更糟。但是,随着DevOps文化变得司空见惯,开发人员开始负责在云中运行其应用程序,更好地了解后端操作系统的本质是一个明显的优势。如果您将后端部署为供个人使用或供少数并发用户使用的系统,则您实际上不必担心Linux以及后端将如何扩展。但是,如果要为成千上万的并发用户进行部署,那么对操作系统如何在整个堆栈中计算出来的全面了解将非常有用。
我们编写的Web服务中要面对的约束与使Web服务或应用程序正常工作所需的其他应用程序中的约束非常相似。是那些负载平衡器还是数据库服务器。所有这些类型的应用程序在高性能环境中都面临类似的挑战。通常,了解这些基本约束以及如何解决它们将使您理解Web应用程序或服务要面对的性能和可伸缩性。
我写本系列文章的目的是要回应年轻的开发人员提出的问题,他们希望成为消息灵通的系统架构师。如果不深入探讨使Linux应用程序产生变化的基础知识以及构建Linux或Unix网络应用程序的不同方法,就不可能对Linux应用程序的性能有透彻的了解。尽管有许多类型的Linux应用程序,但我想在这里探索的是面向Linux网络的应用程序,而不是诸如浏览器或文本编辑器之类的桌面应用程序。这是因为本系列的读者是Web服务/应用程序开发人员和架构师,他们想了解Linux或Unix应用程序如何工作以及如何构建这些服务以实现高性能。
Linux是服务器操作系统,通常,您的应用程序最终可能会在Linux上运行。尽管我说的是Linux,但是大多数时候您可以放心地假设我还包括其他类似Unix的操作系统。但是,我还没有在其他类似Unix的系统上对附带的代码进行广泛的测试。因此,如果您对FreeBSD或OpenBSD感兴趣,那么您的工作量可能会有所不同。在尝试任何特定于Linux的地方,我已尽力指出了这一点。
虽然您当然可以使用这些知识来为要从头开始编写的新网络应用程序选择最佳的结构,但是您可能并没有启动自己喜欢的文本编辑器并使用C或C ++编写Web服务器来解决以下问题:在您的组织中交付下一个业务应用。那可能是让自己被解雇的保证方法。话虽如此,了解这些应用程序的结构将极大地帮助您从几个现有应用程序中选择一种,只要您知道它们的结构。在理解了本系列文章之后,您将能够了解基于流程的系统,基于线程的系统和基于事件的系统。您将了解并理解为什么Nginx的性能要比Apache httpd好,或者与基于Django的Python应用程序相比,基于Tornado的Python应用程序可能能够为更多的并发用户提供服务。
ZeroHTTPd是我从头开始用C语言编写的Web服务器,作为教学工具。它没有外部依赖性,包括Redis访问。我们推出了自己的Redis例程-请在下面阅读更多内容。
尽管我们可以讨论很多理论,但是没有什么比编写代码,运行代码,对其进行基准测试以比较我们所开发的每种服务器体系结构更是如此。这应该像其他方法一样巩固您的理解。为此,我们将使用基于流程,基于线程和基于事件的模型开发一个名为ZeroHTTPd的简单Web服务器。我们将对这些服务器中的每一个进行基准测试,并观察它们相对于彼此的性能。 ZeroHTTPd是一个简单可行的HTTP服务器,从头开始用纯C语言编写,没有任何外部库依赖项。它在单个C文件中实现。对于基于事件的服务器,我包括uthash,这是一个出色的哈希表实现,它位于单个头文件中。否则,就没有依赖性,这是为了使事情保持简单。
我对代码进行了大量评论,以帮助理解。 ZeroHTTPd除了是用几百行C语言编写的简单Web服务器以外,它还是一个最小的Web开发框架。它并没有做什么用。但是,它可以处理静态文件和非常简单的“动态”页面。话虽如此,ZeroHTTPd非常适合您了解如何构建Linux应用程序以实现高性能。最终,大多数Web服务都在等待请求,调查该请求是什么并处理它们。这正是我们将对ZeroHTTPd进行的操作。它是一种学习工具,不是您将在生产中使用的工具。它也不会因错误处理,安全性最佳实践(是的,我使用过strcpy)或C语言的巧妙技巧和捷径而获奖,其中有很多。但是,它有望很好地实现其目的(意想不到的双关语)。
现代的Web应用程序几乎不只提供静态文件。他们与各种数据库,缓存等进行复杂的交互。为此,我们构建了一个名为“ Guestbook”的简单Web应用程序,使来宾可以留下自己的名字和评论。留言簿还列出了以前由各个来宾留下的评论。页面底部还有一个访客计数器。
我们将访客计数器和访客留言条目存储在Redis中。与Redis交谈时,我们不依赖外部库。我们有自己的自定义C例程与Redis对话。当您可以使用已经可用且经过良好测试的产品时,我不愿意推出自己的产品。但是ZeroHTTPd的目标是传授Linux性能和访问外部服务,而在服务HTTP请求的过程中,就性能而言,这具有巨大的意义。我们需要完全控制我们正在构建的每种服务器体系结构中与Redis进行对话的方式。在一种架构中,我们使用阻塞调用,而在其他架构中,我们使用基于事件的例程。使用外部Redis客户端库将不允许我们使用此控件。另外,我们将仅在使用Redis的范围内实现自己的Redis客户端(获取,设置和递增键。获取并追加到数组)。而且,Redis协议非常优雅和简单。甚至有意学习的东西。实际上,您可以实现一个超快的协议,用大约100行代码来完成它的工作,这实际上说明了该协议的思想如何。
下图说明了当客户端(浏览器)请求/ guestbookURL时为使HTML准备就绪而可以使用的HTML的步骤。
当需要提供访客留言页时,有一个文件系统调用将模板读入内存,而有三个与网络相关的对Redis的调用。模板文件具有构成您在上面的屏幕截图中看到的Guestbook页面的大多数HTML内容。它还具有特殊的占位符,来自Redis的内容的动态部分(如来宾评论和访问者计数器)可以放置其中。我们从Redis获取这些内容,将其替换为模板文件中的占位符,最后,将完整的内容写出给客户端。由于Redis返回任何增量键的新值,因此可以避免对Redis的第三次调用。但是,出于我们的目的,当我们将服务器移至基于事件的异步体系结构时,拥有一台忙于阻塞网络调用的服务器是了解情况的好方法。因此,当我们增加访问者计数并在单独的调用中将其读回时,我们将丢弃Redis返回的返回值。
我们还将测量每种体系结构的性能,将每个体系结构加载10,000个HTTP请求。但是,当我们继续与可以处理更多并发性的体系结构进行比较时,我们将切换到具有30,000个请求的测试。我们测试3次并考虑平均值。
重要的是,不要在同一台计算机上对所有组件都运行这些测试。如果这样做,则操作系统将在所有这些组件争夺CPU的时间之间安排额外的开销。使用所选的每种服务器体系结构衡量操作系统开销是此练习的最重要目标之一。添加更多变量将对该过程有害。因此,上图中描述的设置将最有效。
load.unixism.net:这是我们运行Apache Benchmark实用工具ab的地方,该工具生成我们测试服务器体系结构所需的负载。
nginx.unixism.net:有时我们可能要运行服务器程序的多个实例。因此,我们使用适当配置的Nginx服务器作为负载平衡器,以将从ab传入的负载分散到我们的服务器进程中。
zerohttpd.unixism.net:这是我们运行服务器程序的地方,该服务器程序基于上面列出的7种不同体系结构,一次仅一种体系结构。
redis.unixism.net:此服务器运行Redis守护程序,该守护程序存储来宾评论和访客计数器。
所有服务器都具有单个CPU内核。这样做的目的是了解我们可以在每种服务器体系结构中获得多少性能。由于我们所有服务器程序都是在相同硬件上测得的,因此它充当了我们衡量相对性能或每种服务器体系结构的基准。我的测试设置包括从Digital Ocean租用的虚拟服务器。
我们可以衡量很多事情。但是,在给定一定数量的计算资源的情况下,我们希望了解在提高并发性的各个级别上,可以从每种体系结构中挤出多少性能。我们最多可以测试15,000个并发用户。
下表显示了采用不同流程体系结构的服务器在经受各种并发级别时的性能。在y轴上,我们有请求/秒,在x轴上,我们有并发连接。
从上面的图表可以看出,在8,000个并发请求之外,我们只有2个竞争者:预线程和epoll。实际上,我们基于轮询的服务器的性能要比线程服务器差,即使在相同的并发级别下,后者的性能也可以轻松击败前者。预线程服务器体系结构为基于epoll的服务器提供了一个不错的选择,这证明了Linux内核处理大量线程调度的能力。
您可以在此处找到ZeroHTTPd的源代码。每个服务器体系结构都有自己的目录。
ZeroHTTPd│├──01_迭代│├──main.c├──02_forking│├──main.c├──03_preforking│├──main.c├──04_threading│├──main.c├──05_prethreading │├──main.c├──06_poll│├──main.c├──07_epoll│└──main.c├──Makefile├──public│├──index.html│└──晚礼服。 png└──模板└──留言簿└──index.html
在顶层目录中,除了基于我们讨论的7种不同体系结构保存ZeroHTTPd代码的7个文件夹之外,还有2个其他目录。 “公共”和“模板”目录。 “ public /”目录包含一个索引文件和一个您在屏幕快照中看到的图像。您可以将其他文件和文件夹放在此处,ZeroHTTPd应该可以毫无问题地提供这些静态文件。当在浏览器中输入的路径组件与“ public”文件夹内的路径匹配时,ZeroHTTPd会在放弃之前在该目录中查找“ index.html”文件。我们的Guestbook应用程序是动态应用程序,可通过转到/ guestbook路径进行访问,这意味着其内容是动态生成的。它只有一个主页,该页面的内容基于文件“ templates / guestbook / index.html”。向ZeroHTTPd添加更多动态页面并对其进行扩展很容易。想法是用户可以在此目录中添加更多模板,并根据需要扩展ZeroHTTPd。
要构建所有7台服务器,您需要做的全部工作是从顶级目录“全部构建”,然后将所有7台服务器构建并放置在顶级目录中。可执行文件期望运行它们的目录中的“ public”和“ templates”目录。
如果您对Linux API的了解不深,那么您仍然应该能够遵循本系列文章,并获得足够体面的理解。但是,我建议您阅读有关Linux编程API的更多信息。在这方面,有无数的资源可以帮助您,而就本系列而言,这已经超出了范围。尽管我们将探讨Linux的API类别,但我们的重点将主要放在流程,线程,事件和网络方面。如果您不太了解Linux API,建议您阅读系统手册和库函数的手册页,并阅读有关其用法的书籍和文章。
一种关于性能和可伸缩性的想法。从理论上讲,它们之间没有关系。您可以拥有一个性能很好的Web服务,可以在几毫秒内响应,但根本无法扩展。同样,可能存在性能不佳的Web应用程序,该应用程序需要花费几秒钟来响应,但是可以扩展以处理成千上万的并发用户。话虽如此,高性能,高可扩展性服务的组合非常强大。高性能应用程序通常很少使用资源,因此可以有效地为每台服务器服务更多的并发用户,从而降低成本,这是一件好事。
最后,计算中总是只有两种可能的任务类型:I / O绑定和CPU绑定。通过Internet获取请求(网络I / O),提供文件(网络和磁盘I / O),与数据库对话(网络和磁盘I / O)都是与I / O绑定的活动。不过,几种类型的数据库查询可能会占用一点CPU(排序,计算一百万个结果的平均值等)。您将构建的大多数Web应用程序都将受I / O约束,而CPU几乎不会用尽其全部容量。当您看到在受I / O绑定的应用程序中使用了许多CPU时,很可能表明应用程序体系结构不佳。这可能意味着CPU实际上是在过程管理和上下文切换开销上花费的,这并不是非常有用。如果您正在执行繁重的图像处理,音频文件转换或机器学习推断之类的操作,那么您的应用程序将倾向于受CPU限制。但是,对于大多数应用程序而言,情况并非如此。
在审阅者的帮助下,撰写成千上万个单词的文章系列变得容易。感谢Vijay Lakshminarayanan和Arun Venkataswamy花费他们的时间来回顾本系列,并提出对一些明显但不太明显的问题的更正建议。
我叫Shuveb Hussain,我是这个关注Linux的博客的作者。您可以在Twitter上关注我,在那里我发布与技术相关的内容,主要针对Linux,性能,可伸缩性和云技术。