初步认识 nginx

作者: | 更新日期:

最近几周简单看了 Nginx 的源码,并做了一个分享,简单记录一下。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

一、背景

最近几周简单的看了 Nginx 的源码,并做了一个分享。
这里简单记录一下。

二、WEB 服务器

在以前的时候,互联网上的数据量还较少,大家使用 Apache 就解决了全部性能问题。

Apache 处理请求的特点是一个请求连接对应一个进程。
当访问量上涨的时候,大家可以通过 增加进程、更换为高配机器、增加机器的数量来解决请求处理不过来的问题。

后来互联网爆炸式发展,各大网站的请求量都呈现指数级增长。
此时传统的砸机器、砸钱方式已经无法解决高访问量的问题了。

然后 Nginx 横空出世了。

目前主流的 WEB 服务器有 Apache、Tomcat、Nginx, 可以看到各家服务器的安装量情况。

由于 Apache 已经火爆那么多年了,所以目前安装量的比例还是比较高的。
不过可以根据各家服务器的历年来的比例趋势,看出 Nginx 是增长最快的。

到这里,大家可能会有疑问: Nginx 是因为什么这么厉害,性能这么高呢?
那是因为 Nginx 使用了一种高深的技术,叫做事件驱动。
这个技术的缺点是比较难,优点时性价比很高,可以处理很多请求。
另外,由于 Nginx 的模块设计比较好,大家也很容易做定制化的功能,所以被广为使用。

三、Nginx 的特征

既然 Nginx 这么厉害,有哪些显著的特征呢?

1.部署简单

Nginx 安装后,关键文件特别少。
主要有有一个可执行程序、一个配置文件、一个访问日志、一个错误日志。
其他的文件几乎都不需要关心。

2.操作简单

Nginx 有几个简单的命令。
如启动、停止、热加载、热重启。
通过这四个命令,就可以玩转 Nginx 的平常操作了。

3.设计简单

Nginx 采用了多进程共同监听端口,分别处理请求模式。
由于每个进程都是使用事件触发的模式处理请求的,分配的进程数只需要和CPU个数接近即可。

4.热加载

前面说操作的时候提到过热加载,这里单独再介绍下。

可以不客气的说,对于高并发服务,热加载与热启动的应用是一个巨大的进步。

之前的服务器,在升级过程中,往往需要停止服务。
在这期间部分请求会收到影响,而通过热启动,请求量几乎不受影响,可以平滑的切换到新的服务上。

而热加载的原理也很简单。
主进程使用新的配置生成新的进程,此时新旧进程同时干活。
然后主进程告诉旧进程,不要接受新的请求了,现有的处理完就退出吧。
于是旧进程按照这个指令去执行,就完整了热加载。

四、高深的事件驱动

我们知道在 Linux 上一切都是文件。
操作文件的时候,都是通过 FD 来操作的。
另外,对于文件的操作无非是读与写,所以 FD 就对应的有读事件和写事件。

在对 FD 操作的时候,往往存在等待。
就像我们两微信聊天,你给我发送消息后,我需要一段时间才会回复你一样。
这个期间你如果一直等待我的回复的话,就不能做其他事情了,你的时间就这样浪费了。
这种聊天方式叫做阻塞聊天。

比如我们监听一个端口的时候,更特殊,我们不知道谁会来,只能一直等待。

对于这种情况,我们一般会去做其他事情。
比如你先去和其他人聊天,过一会看看我有没有回复。
这样你就可以节约一下时间了。
你的这个聊天就是非阻塞聊天

对于监听端口一样,我们隔一会查一下是否有连接过来了。

对于微信,我们非阻塞聊天的时候,就可以同时和很多人一起聊天了。
那我们怎么快速判断是否要回复消息呢?

微信的小红点来帮助我们解决。

我们可以发现,微信帮我们监听了所有的群和好友的聊天事件。
当有新的聊天事件时,微信会在对应的角标上+1。

对于 FD 的事件,也一样。
我们把所有的时间加入到一个集合中,然后查询是否有新事件,有了,我们就处理对应的事件就可以了。

通过这种事件通知的方式,我们就可以快速知道有哪些事情要处理,从而可以做到高并发聊天了。

五、惊群

这个是多进程等待同一个资源时常会面临的问题。
多个进程都使用事件通知的方式来处理消息,假设只了一个消息,多个进程都收到通知。
除了第一个,其他的都会发现实际上没有需要处理的消息。

针对这个问题,解决方案也很简单:只通知一个进程。

怎样才能做到只通知一个进程呢?
如果操作系统支持(高版本),那使用高版本的操作系统。
如果操作系统不支持,那同一时间只允许一个进程来监听监听,就可以做到只监听一个进程了。

怎么才能做到只有一个进程监听呢?
使用锁来解决。
谁抢到锁,谁去监听。

六、Nginx 为啥如此优秀

1.没有对比就没有伤害

优秀是相对的。
既然说 Nginx 优秀,那肯定是和其他 WEB 服务器对比的。
和谁对比呢? 同步处理消息的 Apache 啦。

Apache 作为传统的 WEB 服务器,在互联网上的并发量较小时,存在了很多很多年。
那时候,Nginx 的孪生兄弟可能出现过,但是都死了。
为啥? Appche 就够了。

后来,互联网爆炸式增长了,Apache 遇到瓶颈了, 异步处理消息的 Nginx 就站出来了。

所以,Nginx 优秀只是和那些的同步方式处理消息的服务相比的。
近十几年新出现的服务器都是使用异步的方式处理消息的,他们在底层技术上相比,是势均力敌的。

如果有人说自己开发了 XX 功能,比 Nginx 性能还高,那说明那个人错误的使用了 Nginx。
比如他们同步的方式使用了 Nginx,然后遇到了并发量上不去的问题,最后又回到了传统的开一堆线程来提高并发量。

2.没有冗余就没有浪费

软件工程中,大家是鼓励功能复用的。
为了功能能够复用、通用,就会做很多冗余的事情。

而 Nginx 为了提高性能,所有的功能都自己来实现了。
比如 SLAB 内存管理,array、list、hash、红黑树、regex等等。
自己实现的时候,仅仅实现自己需要的功能。
这样做出来的功能没有那么通用,但是没有冗余,自己使用时性能会更高。

3.良好的模块设计

一个系统仅仅是高性能是不够的,你还需要好用。
而 Nginx 的模块设计的比较好,很容易增加各种第三方插件。
这样才使得 Nginx 能够广泛推广使用。

前提提到,很多人宣称自己基于 Nginx 做了一个比 Nginx 性能还高的服务,具体是什么回事呢? 那是因为他们使用了某些以同步模式开发的第三方插件。
服务一旦同步处理,比增加进程或线程的话,并发量肯定就上不去了。

4.适合业务的架构

Nginx 既然如此优秀,为啥我们所有的业务不都使用 Nginx 实现呢?
这是因为 Nginx 只是一个优秀的代理服务(做简单的事情),而不是一个优秀的网络框架。

换句话说就是 Nginx 服务设计都存在局限性,只是在某些场景下非常出色,而在其他场景下就比较鸡肋了。
其实所有的服务都是这样的,都存在自身的局限性。

比如 Redis 很多人都听过,也是使用事件触发实现的单进程单线程的服务。
性能是很高,但是只有一个进程,只能用一个CPU,一个人的力量再大也有上限的。

我们为了使得 Redis 用上多核 CPU,往往是部署多个 Redis 实例,这样就存在了很高的管理成本。
而且多个 Redis 实例,还需要有一层路由 HASH 层,这一层的成本也要考虑进来。

所以对于任何系统,不管如何高性能,都存在其局限性,都有对应的使用场景。
超过了使用场景,可能就不是高性能了,或者良好的模块设计就不那么良好了。

Nginx 是如此, Redis 依旧如此。

七、最后

虽说 Nginx 有其自身的局限性,但是其使用场景也很广泛,还是值得去研究一下 Nginx 的源码实现的。
后面我也会分几次来分享 Nginx 设计到的东西。

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越