以 Nginx 为例谈高性能服务端程序设计原则

Nginx 是一个高性能的 HTTP 和反向代理服务器,与 Apache 相比 Nginx 在高并发的情况下有着更好的性能,因而在访问量较大的网站中得到了越来越广泛的应用。

Nginx 的高性能得益于多个方面,本文不予深入探讨。这里仅仅以 Nginx 为例探讨高性能服务端程序的设计原则。

影响服务端程序性能的原因可以被总结为以下几点:

1、阻塞的方法调用

任何时候都不应该调用阻塞的方法,除非没有非阻塞的版本可供选择。程序被阻塞即意味着浪费 CPU 时间,这些时间本可以去处理更多的请求。

Nginx 使用了 epoll/kqueue 等异步事件模型和非阻塞的 socket 调用,以及 AIO 等异步 I/O 模型,确保任何时候 CPU 都不会因为阻塞而空闲。

2、上下文切换

当我们不得不使用某一个阻塞的方法的时候,为了能够尽量不使 CPU 空闲,我们必须创建一个新的线程或者进程来调用这个方法。这样,当这个线程或进程被阻塞的时候,操作系统可以把 CPU 资源分配给其它的任务而不至于浪费。

然而,上下文切换是一个非常费时的操作。特别是对于承担大量并发请求的服务端程序来说,处理一个请求也许只需要 1ms,上下文切换却需要 0.5ms。大量的上下文切换代价高昂。

多进程/线程导致的另一个问题是锁的竞争。锁的竞争不但浪费 CPU 时间、导致更多的上下文切换,而且带来潜在的死锁风险。

因此,Nginx 选择了多进程单线程的模型。对于每一个 CPU 核心,只启动一个单线程的进程来处理请求。最大程度的减少了进程/线程间的上下文切换。

上下文切换还存在于应用程序和内核之间,这就是系统调用。所以非必要的系统调用都应当使用缓存来避免。

Nginx 实现了一个文件缓存机制,用于保存已打开的文件句柄以及文件大小等信息。这样当重复请求同一个文件的时候,Nginx 就不需要重复地向操作系申请打开和查询该文件,显著地减少了系统调用的次数。

Nginx 默认没有打开该机制,因为缓存会导致 Nginx 不能及时地得知文件是否被修改,而错误的返回 302 响应,以及导致文件大小改变之后发送的内容长度不正确。虽然如此,对于访问量很大的服务器(特别是一些纯静态文件服务器,例如图片服务器),仍然非常有必要打开文件缓存机制。

3、其它

大段数据的拷贝非常浪费 CPU 时间并且往往是不必要的。所以总是确保只在内存中保留数据的一份拷贝。针对该问题,Nginx 使用了 sendfile 来发送文件内容,这避免了文件在内核缓冲区和用户缓冲区之间的来回拷贝。

频繁的小块内存申请会显著影响性能,特别是在每次申请的内存长度并不相同的情况下。申请固定长度的内存以及一次性申请一大块的内存以减少内存碎片并提高性能。

本文之前所提到内容均以充分使用 CPU 资源为目的。这是因为磁盘 I/O 和网络吞吐在大部分情况下难以为我们所控制。在必要时我们可以通过压缩数据的方式,以消耗 CPU 时间为代价换取磁盘和网络性能的提升。

相关文章
没有评论

留下评论

电子邮件地址不会被公开。 必填项已用*标注