Nginx的速度及如何复制它的速度

8
我对此感兴趣,但更多是从学术角度而不是实际角度;我不打算创建一个生产Web服务器来与Nginx竞争。我想知道的是Nginx为什么如此快速。谷歌搜索结果中排名最高的是这个帖子,但它只是链接到一个神秘的幻灯片和一般性的涵盖不同io策略的说明。所有其他结果似乎只是描述了Nginx有多快,而没有解释原因。
我尝试构建一个简单的Erlang服务器来尝试与Nginx竞争,但无济于事; Nginx获胜。我的服务器所做的全部工作就是为每个请求生成一个新进程,使用该进程将文件读取到套接字中,然后关闭文件并终止线程。虽然这并不复杂,但考虑到Erlang的轻量级进程和底层aio结构,我认为它应该能够竞争,但在重压力测试下,Nginx仍然以平均300毫秒的优势获胜。
Nginx正在做什么,而我的简单服务器没有做到?我的第一个想法是将文件保存在主内存中,而不是在请求之间抛弃它们,但是文件系统缓存已经实现了这一点,因此我认为它不会有太大的差异。我错了吗?还是我遗漏了其他的东西?

6
评论中的人们说:Erlang进程不是Unix进程或线程。最好假设它们没有相同的行为。 - Dustin
这也是我一直在想的事情。Erlang 的设计哲学的很大一部分是经常生成新线程,这让我想知道,在实现相同目标时,是否需要考虑构建普通 Unix 服务器时所考虑的许多设计因素在 Erlang 中是否仍然相关。 - Mediocre Gopher
3
Erlang 进程不会映射到操作系统进程或线程,Erlang 的哲学并不是生成新的线程,通常在 Erlang 虚拟机中每个核心只有一个线程。 - user177800
在我的最后一条评论中,将“threads”替换为“Erlang进程”(希望这是正确的术语)。 - Mediocre Gopher
4个回答

8
原来我的小测试服务器在切换到二进制模式后读取文件时与nginx相当有竞争力。我认为本主题其余的讨论对于不熟悉erlang和erlang服务器设计的人可能很困惑。我不想删除这个主题,因为里面有关于nginx的有用信息(而且我也不能删除,因为它已经有答案了),但我鼓励任何想要创建基于erlang服务器的人进行一些研究和编写大量测试,不要只依赖你在这里所看到的东西。

这里的大部分答案和评论来自没有erlang经验的人,可以安全地忽略。在erlang中编写一个事件驱动的网络套接字服务器是微不足道的,它不会阻塞,并且创建一个erlang进程来处理该连接是很好的实践。下一个问题是需要处理多少并发连接?100万个连接是可能的(尽管您需要多个IP地址来处理所有这些套接字)。 - Michael Shaw

0

Nginx是用C语言编写的,因此它被编译成本地机器语言。Erlang有自己的虚拟机。这就意味着,由于Erlang模块必须经过字节码解释器,你永远无法通过使用Erlang程序来匹配精心设计的C程序的性能。

尽管Erlang基于轻量级进程并且设计为在分布式架构上进行扩展,但它永远无法与用本地语言或者在这种情况下使用Nginx编写的几乎实时处理系统的性能相匹配。


2
Erlang也可以在使用exokernel VM的exokernels中运行,就像程序使用操作系统一样,不需要操作系统层,即使Erlang不能像本机机器码一样运行,如果大量并发连接更重要,例如WebSocket,可能比Nginx更好,因为它允许在连接数比Nginx更大的情况下实现软实时时间。 - user2006656

0

我会尽量简单地解释,因为我并不是这个主题的专家。但是让Nginx变快的两个最重要的因素是它使用事件轮询和不阻塞

事件轮询本质上意味着一个进程可以处理多个连接,这非常关键,因为生成进程在时间上非常昂贵。但是,事件轮询不应与线程编程混淆。如果您想了解更多信息,则比我聪明的人已经写过相关文章。

第二个最重要的事情是不要阻塞Nginx进程。如果您执行阻塞操作,则所有其他连接都将排队等待您完成。如果每个连接都被阻塞,则性能很快就会降至零。

Nginx做得比这更多,例如最小化上下文切换。然而,这些内容超出了我的专业知识范围,我不太舒服发表评论。


只要重复使用进程,生成进程的成本就不重要了。一个进程处理多个连接的原因之所以重要,是因为它最小化了上下文切换,而不是因为创建进程很昂贵。 - David Schwartz
确实如此。但他在原始帖子中说:“我的服务器所做的就是为每个请求生成一个新进程”,在这种情况下,他正在花费大量时间来生成进程。 - Martin Fjordvald
是的, 是。然而,第二段第一句话是错误的。例如,如果您每个连接使用一个进程,仅支持最多1,000个连接,但在启动时创建1,000个进程并重复使用它们,则生成进程的成本不会影响性能。一个进程处理多个连接的原因很重要,是因为它最小化了上下文切换,而不是进程启动的成本。 - David Schwartz
3
Erlang进程的创建和销毁成本低廉,它们不会一对一地映射为操作系统进程或线程。 - user177800

-3

嗯,你的架构非常低效。例如,如果你需要在50个连接中每个都做一点工作,你需要多少上下文切换?假设你正在处理的50个连接已经准备好进行I/O操作,那么你需要多少次调用系统才能发现这一点呢?


我明白了,那么我应该让每个线程同时处理多个连接。 - Mediocre Gopher
4
Erlang进程的创建和销毁非常廉价,它们不会直接映射到操作系统进程或线程。你所说的上下文切换永远不应该在操作系统级别发生。 - user177800
8
@DavidSchwartz 在Erlang进程中没有上下文切换,它们都在一组固定的线程上运行,拥有自己的堆栈和堆,你对操作系统级进程和线程的理解与Erlang进程无关。 - user177800
@DavidSchwartz 你没有理解重点,没有更高级别的上下文切换是因为没有上下文切换,而不是因为更高级别的上下文切换没有进行上下文切换。 - taur
@taur 对我来说这似乎是显而易见的。如果每个连接都有自己的堆栈和堆,那么在处理另一个连接之前,你不能不切换堆栈和堆。这不是显然正确的重言吗? - David Schwartz
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接