Nginx适用于哪些场景

下面我们进入第一部分初识Nginx,这一部分希望能够帮助从来没有了解过Nginx的朋友,能够快速了解Nginx的背景,他的配置语法,他的命令行,能够让大家快速搭建一个简单的web服务器,再来体会他强大的一个功能。

很多人可能或多或少了解过Nginx,即使你没有使用过Nginx,但是你可能用Apache搭建过简单的web服务器,你可能用tomcat写过一些简单的动态页面,这些功能Nginx都可以实现。

但Nginx最重要的三个使用场景我认为是静态资源服务、反向代理服务和api服务。我们这里详细讲解一下。

一个web请求走进服务以后会先经过Nginx再到我们的应用服务,比如tomcat,然后再去访问redis或者访问mysql这样的数据库,提供基本的数据功能。

那么这里有一个问题,我们的应用服务因为要求开发效率非常的搞,所以他的运行效率是很低的,他的qbs,tps并发都是受限的,所以我们需要把很多这样的应用服务组成一个集群,向用户提供高可用性。

而一旦很多服务构成集群的时候,我们需要Nginx具有反向代理的功能,可以把动态请求传导给应用服务。

而很多应用服务构成集群,他一定会带来两个需求,第一个需求我们需要动态的扩容,第二个则是有些服务出问题的时候我们需要做容灾。

那么这样我们的反向代理必须具备负载均衡的功能,其次在这样的一个链路中,Nginx是处在企业内网的一个边缘节点,随着我们网络链路的增长,用户体验到的时延会增加。

所以如果我们能把一些所有用户看起来不变的,或者在一段时间内看起来不变的动态内容缓存在Nginx部分,由Nginx直接向用户提供访问,那这样用户的时延就会减少很多。

所以反向代理会衍生出另外一个功能叫缓存,他能够加速我们的访问,而很多时候我们在访问像css或者js文件,或者这样一些小图片,那么这样静态的资源是没有必要由应用服务来访问的,他只需要通过本地文件,系统上放置的静态资源,直接由Nginx提供访问就可以。这是Nginx的静态资源功能。

那第三个应用场景则是因为应用服务它本身的性能因为有很多的问题,但是数据库服务要比应用服务好的多,因为他的业务场景比较简单,他的并发性能和tps都要远高于应用服务,所以我们这里衍生出第三个应用场景。

由Nginx直接去访问数据库或者redis或者像这样的应用服务,利用Nginx的强大的一个并发性能,实现如web防火墙这样复杂的一些业务功能来提供给用户。

这要求我们api服务有非常强大的业务处理功能,所以像openResty或者像Nginx集成了javascript运用javascript这样的语言功能和他们语言先天自带的一些工具库来提供完整的api服务。

以上是我们介绍Nginx三个最主要的使用场景。

Nginx出现的历史背景

下面我们分析下Nginx出现的原因和他的历史背景,在我看来Nginx出现的原因主要有三个。

第一个是互联网上数据的快速增长,这主要是全球化和物联网的快速发展,导致接入互联网中的人与设备的数量都在快速的上升,数据的快速爆炸,对我们硬件性能提出很高的要求。

而提到硬件大家都知道摩尔定律,之前我的服务跑在1GHZ的CPU上,当一年半以后我更新到2GHZ的CPU时,我可以预测到我的服务会有两倍的性能提升。

但是到了本世纪初,摩尔定律在单颗CPU的频率上已经失效了,CPU开始向着多核方向发展,这个时候当你的服务器现在是跑在8核CPU上时,一年半以后你换到了16核的CPU,你的服务的性能通常是不会有一倍的提升的。

那么这些性能究竟损耗在哪里呢,在我看来主要是操作系统和大量的软件没有做好服务于多核架构的准备,比如说像Apache,在我看来Apache是低效的,因为他的架构模型里一个进程同一时间,只会处理一个连接,一个请求。只有在这个请求处理完以后才会去处理下一个请求。

这有什么潜台词呢,它实际上在使用操作系统的进程间切换的特性,因为操作系统微观上只有有限的CPU,但是操作系统被设计为同时服务于数百甚至上千的进程。

而Apache一个进程只能服务于一个连接,这样的模式会导致当Apache需要面对几十万,几百万连接的时候,他没有办法去开几百万的进程,而进程间切换的代价成本又太高啦。

当我们并发的连接数越多,这种无谓的进程间切换引发的性能消耗又会越大。

Nginx是专门为了这样的应用场景而生的,Nginx可以处理数百万甚至上千万的并发连接,Nginx目前在web市场份额中排行第二,在过去几年他增长极度迅速,在不久的将来Nginx在web端的应用将远远超过其他服务器。

Nginx的优点

接下来我们看一下Nginx的主要优点,在我看来Nginx主要有五个优点,大部分的程序和服务器随着并发连接数的上升他的RPS数会急剧的下降,这里的原理就像我们之前所说过的,他的设计架构是有问题的。

Nginx他的第一个优点就是高并发和高性能同时具备的,往往高并发只需要我们对每一个连接所使用的内存尽量的少就可以达到。

而具有高并发的同时达到高性能,往往需要非常好的一个设计,那么Nginx可以达到一个什么样的标准呢,比如说我们现在主流的一些服务器32核64G的内存可以轻松达到数千万的并发链接,如果是处理一些简单的静态资源请求,可以达到一百万的RPS这样的级别。

第二个核心原因是他的可扩展性非常好,那么他的可扩展性呢主要体现在他的模块化设计,模块化设计非常的稳定,使得Nginx的第三方模块的生态圈非常的丰富。甚至于有像TNG,openRestry这样的第三方插件。在他们之上又生成了一套新的生态圈。丰富的生态圈为我们Nginx丰富的功能提供了保证。

第三个优点是它的高可靠性,所谓的高可靠性是指Nginx可以在服务器上持续不间断的运行数年,而很多web服务器往往运行几周或者几个月就需要做一个重启。

对于Nginx这样一个高并发高性能的反向代理服务器而言,他往往运行在企业内网的边缘节点上,这个时候如果我们企业想提供四个9,五个9,甚至更高的高可用性时,对于Nginx持续运行能够down机的时间一年可能只能以秒来计。

所以在这样一个角色中,Nginx的高可靠性给我们提供了非常好的保证。

第四个优点热部署,是指可以在不停止服务的情况下升级Nginx,这个功能对于Nginx来说非常的重要,因为在Nginx可能跑了数百万的并发连接。

如果是普通的服务我们可能只用kill掉进程再重启的方式就可以处理好,但是对于Nginx而言,因为kill掉Nginx进程,会导致操作系统为所有的已经建立连接的客户端发送一个tcp中的reset报文。而很多客户端是没有办法很好的处理这个请求的。

在大并发场景下,一些偶然事件就会导致必然的恶性结果,所以热部署是非常有必要的。

第五个优点是BSD许可证,BSD Listens是指Nginx不仅是开源的免费的,而且我们可以在有定制需要的场景下,去修改Nginx源代码,再运行在我们的商业场景下,这是合法的。

Nginx的优点其实不止这五条,但以上的优点我认为是Nginx最核心的特性。

Nginx的组成

下面我们来看一下,Nginx由哪些部分组成。首先我们举个例子,假设有一辆汽车,这个汽车提供基本的驾驶功能,但是还需要一个驾驶员控制这辆汽车开往哪个方向,这个汽车经过的所有地方还会形成GPS轨迹,如果这个汽车出现了任何的事故,我们还需要一个黑匣子来分析究竟是汽车本身出现了问题,还是说驾驶员的一些操作出现了问题。

那么Nginx四个组成部分中,第一个就是Nginx的二进制可执行文件,这是由Nginx本身的框架他官方模块还有我们编译进去的各种第三方模块,一起构建的一个文件。

这个文件就相当于汽车本身,他有完整的系统,所有的功能都由他提供。

那么第二个组成部分是Nginx.conf这个配置文件,他相当于驾驶员,虽然我们的二进制可执行文件已经提供了许多功能,但这些功能究竟有没有开启,或者开启了以后定义了怎样的行为处理请求,都是由Nginx.conf这个配置文件决定的。

所以他就相当于这个汽车的驾驶员,他控制着这个汽车的行为。

Nginx的第三个组成部分叫做access.log访问日志,它相当于这辆汽车经过所有地方形成的GPS轨迹,access.log会记录下每一条Nginx处理过的http请求的请求信息与响应信息。

Nginx的第四个组成部分是error.log错误日志,他就相当于黑匣子一样,当出现了一些我们不可预期的问题时,可以通过error.log去把问题定位出来。

Nginx的二进制可执行文件这四个部分是相辅相成的。

Nginx的二进制可执行文件和Nginx.conf它定义了Nginx处理请求的方式。

如果我们想对我们的web服务,做一些运营或者运维的分析,需要对access.log做进一步的分析。

如果出现了任何未知的错误,或者与预期的行为不一致时,我们应该通过error.log去定位根本性的问题。

以上就是Nginx的四个主要组成部分。

版本历史

接下来我们主要看一下Nginx的版本发布,从这里我们可以看到Nginx的发展脉络。

Nginx每发布一个版本的时候会有三个特性,一个是feature,就是他新增了哪些功能,bugfix表示他修复了哪些bug,change表示做了哪些重构。

那么每一个版本呢还有mainline主干版本和stable稳定版本。

在Nginx的官网(nginx.org),那么开源版的nginx中,我们点击一下右下角的download,可以看到Mainline version版本号相对比较大,在后续像1.15.x这样的单号版本就表示主干版本,他会新增很多功能,但这些功能不一定稳定。而双数版本,1.14.x这是一个稳定版本。

我们打开CHANGES文件,可以看到每一个版本中,都含有新增的功能,修复的bug,以及做了哪些小的重构。在版本的后面还有版本发布日期。

大概在2009年以后Nginx的bugfix数量已经大幅度减少,所以Nginx相对已经很稳定了。

Nginx的开发时间是在2002年,但是他在2004年10月4日推出了第一个版本,在2005年曾经国祚一次大的重构。

那么后面应该Nginx优秀的设计,使得他的生态圈极为丰富,那么模块的设计,架构的设计没有做过大的变动。

在2009年Nginx开始支持windows操作系统,2011年1.0正式版本发布,同时Nginx的商业公司Nginx Plus也成立了,在2015年Nginx发布了几个重要的功能。

其中提供stream,四层反向代理,他在这个功能上完全可以替代我们传统使用的LVS而且具有更丰富的功能。

以上就是Nginx的版本发布情况,可以看出Nginx对高性能的特性支持越来越好,我们可以预期Nginx未来也会越来越好。

选择Nginx版本

免费开源: nginx.org

商业版本: nginx.com

当我们准备使用Nginx的时候,我们会发现互联网上提供了很多个版本的Nginx,既有开源免费的Nginx,也有收费的Nginx Plugs。既有免费版的OpenResty,也有收费版的OpenResty。我们应该怎么选择呢。

先来看开源免费版的Nginx和商业版Nginx有什么区别。

开源免费的Nginx在2002年开始开发,2004年发布第一个版本,到了2011年开源版的Nginx发布了1.0这个稳定版,同年Nginx的作者成立了一家商业公司,开始推出Nginx Plus这个商业版的Nginx。

商业版的Nginx在整合第三方模块上还有运营监控以及技术支持上有很多优点,但他有个最大的缺点就是他不是开源的,所以通常在国内我们会使用nginx.org这个开源版的。

那么阿里巴巴也推出了Tengine,Tengine的优点就是在阿里巴巴生态下他经历了非常严苛的考验,那么Tengine之所以会存在也是因为他的很多特性领先于Nginx的官方版本。

所以Tengine实际上是修改了Nginx官网版本的主干代码,他的框架被修改以后Tengine就遇到了一个问题,他没有办法跟着Nginx的官方版本同步的升级,所以Tengine的生态虽然也很丰富,也可以使用Nginx的官方版本的各种第三方模块,但是因为他的这一个特性所以不太推荐大家时候Tengine。

下面来看一下OpenResty,OpenResty的作者章亦春最早也是在阿里巴巴工作,在阿里巴巴的时候他开发了Lua语言版本的openResty,因为Nginx的第三方模块开发的难度相当大,而章亦春呢他把Nginx非阻塞,事件这样的一种框架以Lua语言的方式,以同步开发的方式提供给了广大开发者。

所以OenRestry兼具了高性能,以及开发效率提升的一个特点,OpenResty同样有开源版和商业版,我们目前多使用openresty.org这个站点下的开源版本。那么商业版OpenRestry他的主要特点是技术支持相对比较好一些。

虽然在互联网上大家可以发现更多的Nginx版本,但最主要的就是以上这五个版本。

如果你没有太多的业务诉求,那么使用开源版的Nginx就足够了,如果你需要开发Api服务器,或者需要开发web防火墙,openrestry是一个很好的选择。

Nginx安装

接下来我们介绍编译Nginx的过程,实际上安装Nginx有两种方法,除了编译外,我们还可以直接用操作系统上自带的一些工具,比如说yum,比如说apt-get,直接去安装Nginx。

但是直接安装Nginx有个大问题,就像之前所说的Nginx的二进制文件他是会把模块直接编译进来的,Nginx的官方模块,并不是每一个默认都会开启的。

如果你想添加第三方的Nginx模块,你必须通过编译Nginx这样一种方式,才能把第三方强大的生态圈里的功能添加到我们Nginx中。

编译Nginx主要分为以下六个部分,首先我们需要下载Nginx,从nginx.org这个网站上直接去下载就可以了。

接着我们会介绍从下载的Nginx的源代码中每个目录是做什么用的。

第三步按照unix系统的的惯例,我们首先需要执行Configure这个文件,这个文件会有很多中间选项,我们会详细的介绍。

Configure会生成很多中间文件,这里的中间文件我们也会做一个简要的介绍。

接下来我们开始编译Nginx,编译完Nginx以后实际上也会有一些中间文件,最后我们开始安装Nginx。

首先我们需要下载Nginx,打开nginx.org这个时候在这个页面中我们找到右下角donwload,点击之后我们会发现Nginx有两类版本,一类叫MainLine版本,一类就Stable版本,也就是说最新的主要功能都会在MainLine,最新已经到了1.19.3, 而Stable版本是1.18.0。

我们先选择Stable版本的下来链接,右键复制链接地址即可,接着我们进入到Linux中,首先我们要下载这个最新的版本。

cd  /home/nginx
wget http://nginx.org/download/nginx-1.18.0.tar.gz

当我们下载完Nginx压缩包以后首先w我们解压这个压缩包。

tar -xzf nginx-1.18.0.tar.gz

接着我们进入解压后的源码版本。

cd nginx-1.18.0
ll

可以看到源码目录里面这些目录大概是什么意思,我们这里简单的介绍下。

第一个目录叫auto目录,auto目录里面有什么样的目录结构呢,我们可以进去看一下。

cd auto

auto目录里面有四个子目录,cc他是用于编译的,lib库还有对操作系统的判断在os里面,其他所有的文件都是为了辅助config脚本执行的时候去判定我们的Nginx支持哪些模块。当前的操作系统有什么样的特性可以供给Nginx使用。

CHANGES文件就是Nginx每一个版本中提供了哪些特性和bugfix,我们可以简单浏览下。

vim ../CHANGES

我们可以看到其中会有feature和bugfix和change这样三种特性在里面。

CHANGES.ru文件是因为作者是一个俄罗斯人,所以他有一个俄罗斯语言的CHANGES文件。

conf文件呢是一个示例文件,也就是我们把Nginx安装好以后,为了方便运维去配置,那么会把config里面的示例文件copy到安装目录。

configure脚本呢是一个用来生成中间文件,执行编译前的一个必备动作。

contrib这个目录呢他提供了两个破脚本和vim的一个工具,比如我们再没有使用vim工具时用vim打开Nginx配置文件会发现他的色彩没有什么变化,我们可以看到Nginx的语法没有在vim中。

这个时候我们需要把contrib目录中vim的所有文件copy到我们自己的目录中

cp -r contrib/vim/* ~/.vim/

copy以后我们就可以把Nginx语言的语法高亮显示在vim中了。

接下来看html文件这个目录,这个里面提供了两个标准的HTML文件,一个是发现500错误的时候可以重定向到这个文件,另外一个是一个默认的Nginx的欢迎界面index.html。

man文件里则是Linux对Nginx的一个帮助文件,里面标识了最基本的Nginx帮助和配置。

最后我们看一下Nginx的源代码,在src目录,也就是他的框架都在这些目录里面的源代码中。

编译

接下来我们进行编译,编译前我们可以先看一下configure支持哪些参数。

./configure --help | more

这里主要分为几个大块,第一大块就是我们来确定Nginx执行中,他会去找哪些目录下的文件作为他的辅助文件。比如我们用动态模块那这时候--modules-path就会产生作用。还有像--lock-path来确定我们的nginx.lock文件放在哪里。

如果没有任何变动的话我们只需要指定--prefix=PATH这个参数就可以了,所有的其他的这些文件会在prefix目录下面,建相应的文件夹。

第二类参数主要是用来确定使用哪些模块和不使用哪些模块,他的前缀通常是--with和--without。

我们可以看到通常前面需要我们加--with,比如说--with-http_ssl_module或者--with-http_v2_module的时候,通常需要我们主动加--with模块的时候,意味着这个模块默认是不会编译进Nginx的。

而模块中显示--without比如说--without-http_charset_module或--without-http_gzip_module,他意味着默认他会编译进Nginx中,如果不加这个参数他是编译进的,加了这个参数,是把他移除默认的Nginx的模块中。

最后我们看第三类参数,第三类参数中指定了Nginx编译中需要的一些特殊的参数,比如说我用cc编译的时候需要加一些什么样的优化参数,或者说我要打印debug级别的日志(--with-debug),以及我需要加一些第三方的模块(--with-zlib-asm=CPU)

下面我们开始实际编译一次Nginx,首先用他的默认参数,这里我们指定的Nginx的安装目录是在/home/nginx目录下,回车。

./configure --prefix=/home/nginx/nginx/

如果没有任何报错,那么Nginx就已经编译成功了,这个时候所有Nginx的配置特性,以及Nginx的运行时的目录都会列在最下方。

在config执行完之后,我们会看到他会生成一些中间文件。中间文件会放在objs这个文件夹下,这里最重要的是会生成一个文件叫做ngx_modules.c,他决定了接下来我们执行编译时候,有哪些模块会被编译进Nginx。我们可以打开看一下。所有被编译进Nginx的模块都会列在这里,他们最后会形成一个叫做ngx_modules的数组。

接下来我们执行make编译。

nake

编译完成以后如果没有任何错误,这个时候就可以看见生成了大量的中间文件,以及我们最终的运行Nginx的二进制文件,可以在objs这个目录中看到。

cd objs/
ll

那么为什么我们要知道Nginx的目标文件是放在这里的呢,因为如果我们是做Nginx的版本升级,这个时候我们不能执行make install,还需要在这里把目标文件Nginx拷贝到安装目录中。

C语言编译时生成的所有的中间文件,都会放在src目录。

如果我们使用了动态模块,那么动态模块编译后会生成so动态文件,也会同样放在objs文件夹下。

最后我们进行make install,首次安装时可以使用这个命令。

make install

安装完成以后我们去到--prefix指定的安装目录中可以看到很多目录,这里主要的Nginx的二进制文件就在sbin目录下。

决定Nginx功能的配置文件在conf下,access.log和error.log在log文件夹下。

我们可以看到在conf目录下所有文件正是在源代码中那个conf目录中所有的文件copy了一份过来,其中的内容也是完全相同的。

以上就是编译Nginx的主要步骤,可以在自己的电脑上尝试一下。

Nginx配置语法

下面我们来看一下Nginx的配置语法,Nginx的二进制文件中,已经指定了他包含了哪些模块,但每一个模块都会提供独一无二的配置语法。

这些所有的配置语法,会遵循同样的语法规则,下面我们来看一下主要的语法规则。

Nginx的配置文件呢,是一个ascii文本文件,这个文本文件,主要有两部分组成,一个叫做指令一个叫做指令快。

http {
    include mime.types;
    upstream thwp {
        server 127.0.0.1:8000;
    }

    server {
        listen 443 http2;
        # nginx配置语法
        limit_req_zone $binary_remote_addr zone=one:10 rate=1r/s;
        location ~* \.(gif|jpg|jpeg)$ {
            proxy_cache my_cache;
            expires 3m;
        }
    }
}

像上面http就是一个指令快,而include mime.types;这是一条指令。

第二个每条指令都是以分号结尾的,指令和参数间以空格符号分隔,我们还是拿include mime.types;来看,include是一个指令名,他的中间可以一个或者多个空格来分隔,那么后面的mime.types就是他的参数,也可以具备多个参数,比如下面的limit_req_zone他有三个参数。

两条指令间是以分号作为分隔符的,两条指令放在一行中写也是没有问题的。只不过这样可读性会变得很差。

第三个指令块是以 {} 组成的,他会将多条指令组织到一起,比如upstream,他把一条指令server放在了thwp这个指令块下面。

像server他也防止了listen,limit_req_zone这些指令,他还可以包含其他的指令块,比如说location。

那么有些指令块呢可以有名字,比如说像upstream,后面有个thwp,location后面也可以有名字,但是有些指令块是没有名字的,比如说server和http。

究竟什么样的指令有名字,什么样的指令没有名字呢?就是由提供这个指令块的Nginx模块来决定的,他可以决定指令块后面有一个或者说多个参数,或者说没有参数。

第四个include语句他允许组合多个配置文件以提升可维护性。在我们这个例子中mime.types这个文件中其实里面是含有很多条不同的文件的后缀名与http协议中mime格式的对照关系表。

这些关系指令呢其实是耦合在一起的,和我们的语法关联不大,所以我们用了include这样一个语法以后呢我们整个文件的可读性好了很多。而不是简单的列在这。

第五使用#符号可以添加注释,提升可读性,比如在server的listen后面我们加了一个nginx配置语法的注释,以描述我们下面一些配置的表达。

第六,使用$符号可以使用变量,我们可以看下limit_req_zone,他控制了我们的留空,这里我们用了一个参数叫做$binary_remote_addr,他其实描述的是远端的地址,这是一个变量,这个变量并不是limit_req_zone这个模块提供的。而是Nginx的框架提供的。

第七条,部分指令的参数是支持正则表达式的,比如location后面我们可以看到,他可以支持非常复杂的正则表达式,而且可以把正则表达式括号里的内容通过$1,$2,$3的方式取出来。

那么在Nginx的配置文件中当涉及到时间的时候,我们还有许多表达方式,比如下面的方式:

ms -> 毫秒
s -> 秒
m -> 分钟
h -> 小时
d -> 天
w -> 周
M -> 月
y -> 年

我们看下刚才的例子,比如location中的expires 3m;就表示3分钟后我希望这个cache刷新。

空间也是有单位的,当我们后面不加任何后缀名时表示字节bytes,加了k或者K表示千字节,m表示兆字节,g表示G字节。

在http这样的框架中,它里面有四个块,像http,server,location,upstream,我们再简单说一下。

http大括号里面呢表示,里面所有的指令都是由http模块去解析,去执行的,一个非http模块,比如说像stream或者像mime他们是没有办法去解析这里面的指令的。

那么upstream则表示是上游服务,当我们Nginx需要与Tomcat等我们企业内网的其它服务交互的时候呢,我们可以定义一个upstream。

server只是一个对应的一个域名,或者说对应一组域名,location只是一个url表达式。

以上是Nginx的基本语法配置规则。