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的5个优点

接下来我们看一下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的发展脉络。

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的基本语法配置规则。

Nginx命令行及演示 - 重载,热部署,日志切割

当我们安装好Nginx并且准备好Nginx的配置文件后,这个时候我们就要开始操作Nginx的命令行,来启动Nginx为我们服务了。

Nginx命令行如同大多数linux命令是一样的,首先nginx后面跟上基本的指令,后面再跟指令相应的参数。

nginx -s reload

当我们需要帮助的时候呢,可以用-? 或者 -h, 来获取帮助。

nginx -?
nginx -h

默认情况下我们编译出来的Nginx会去寻找我们执行configure时指定的那个位置的配置文件。但是我们在命令行中,可以人为的指定另一个不同的配置文件。这个时候我们可以用-c 路径

那么我们还能指定一些配置的指令,这些指令可以用-g,所谓的指令就是在Nginx的configure目录里我们有很多条指令,但是这些条指令如果我需要在命令行中覆盖中间的一些指令呢,是可以做到的,可以使用-g来做到。

接下来我们还可以指定运行目录,因为我们的运行目录下会有很多的子目录,这些子目录比如说log,比如说modules。

如果我们指定了运行目录呢,他就会把我们在configure中定义好的那个运行目录替换掉。

nginx去操作运行中的进程的方法,一般是通过发送信号的,发送信号呢我既可以通过linux通用的kill命令也可以用nginx -s这个子命令,这个子命令后我们可以用stop,quit,reload,reopen

nginx -s stop # 停止nginx服务
nginx -s quit # 优雅的停止nginx服务
nginx -s reload # 重载配置文件
nginx -s reopen # 重新开始记录日志文件。

有时候我们改了配置文件之后不知道他里面有没有语法错误,我们可以用-t让nginx先去测试一下配置文件有没有问题,如果没有问题我们再发布到线上。

我们可以打印nginx的版本信息和编译信息,其中-v可以看到他的版本信息,-V就是我们在编译他的时候,用configure这个脚本执行的时候所加的所有的参数,都可以在这里看到。

下面我们做一个命令行的演示,主要包括三个部分,第一个是重载配置文件,第二个是热部署,第三个是切割日志文件。

首先我们来看一下什么叫重载配置文件,也就是说现在我需要去修改nginx配置文件中的一些值,比如说conf/nginx.conf文件中,我们打开tcp_nopush。

当我们修改完这个配置文件以后,我们可以直接执行nginx -s reload这样的话nginx是在不停止对客户服务的情况下使用了我们tcp_nopush这个新的配置项,非常的简单。

接下来我们看热部署,首先热部署是说我的nginx是正在运行的,现在我想更换最新版本的nginx,那么根据我们之前所说的,nginx编译方法,现在我们下载了一个更新的nginx。

说先我需要把现有的nginx二进制文件备份一下,因为我们所更换的只是二进制文件,并不会更换其他文件,接着我们需要把我们刚刚编译好的,最新版本的nginx二进制文件,copy到这个目录中,替换掉正在运行的进程所使用的的nginx文件。

copy完成我们需要给正在运行的nginx的master进程发送一个信号,告诉他我们开始进行热部署了,做一次版本升级,这个时候我们给nginx的master进程发送一个信号,USR2这个信号。

kill -USR2 进程号(13195)

接着nginx会新启一个master进程,那么这个新的master进程他使用了我们刚刚复制过来的最新的nginx二进制文件来启动的。

老的worker呢也在运行,新的master会生成新的worker,他们会平滑的把所有的请求过渡到新的二进制文件启的进程中。

这样我们就实现了一个平滑的过渡,这个时候我们再去看进程的状况,可以看到新老master进程和worker进程都在运行,但是老的worker进程已经不再监听80或者443这样的web端口了。

所以新的请求新的连接只会进入新的nginx进程中,这个时候我们需要向老的nginx进程发送一个信号叫做WINCH,告诉他请优雅的关闭你的所有进程。

kill -WINCH 13195

这时老的master进程和老的worker进程有一个变化,老的worker进程呢已经优雅的退出了,但是我们看到老的master进程还在,但是已经没有worker进程了。

这其实说明一件事情就是所有的请求已经全部切换到我们新的nginx中了,但是我们有可能会发现一些问题,需要把新版本退回到老版本,所以老的进程我们还可以发送reload命令,让他重新把worker进程拉起来。再把新版本关掉。

所以老的master进程他是不会自动退出的,保留在这里允许我们做版本回退。

接下来我们看日至切割,比如说我们当前的日志已经很大了。我需要把以前的日志备份到另外一个文件中,但是nginx还是正常运行的。

这就要通过reopen这个命令来做,首先我们需要把当前正在使用的日志copy一份放在另外的位置.

mv access_log bak.log

接着我们执行命令reopen。

nginx -s reopen

这个时候就重新生成了一个access.log, 原本的log我们备份成了bak.log,这样我们就实现了日志切割。

当然这种方法会非常不好用,实际上我们往往是每一天,或者是每一周执行一次日至切割,我们可以先写成一个bash脚本。

在这个bash脚本中我首先把这个文件复制一下,再执行-s reopen这个命令,最后把这个脚本放在crontab中。

以上就是nginx命令行的几个简单演示。

用Nginx搭建一个可用的静态资源Web服务器

使用nginx搭建一个静态的资源服务器,这要求我们有一台安装了linux的机器。

编辑conf/nginx.conf文件。首先我们找到server代码块中,listen配置监听哪个端口,这里我们用8080端口,然后我们需要配置一个location,这里所有的url请求都访问到www文件夹,这里我们使用/表示所有的请求。

我们这里需要指定这个url的后缀要与我们的文件目录后面的后缀一一对应,我们这里有两种用法,root和alias,root相对来说有一个问题,他会把url中的一些路径带到我们目录中来,所以我们通常使用alias。

alias就是Nginx安装目录的www目录下,后面的路径与我们的url路径是一一对应的。

server {
    listen 8080;
    ...
    location / {
        alias www/;
        ...
    }
    ...
}

那么我们做完配置之后再去启动Nginx就可以看到效果了。我们在浏览器中访问localhost:8080就可以访问。

nginx -s reload

这里我们开启gzip压缩,做完gzip压缩以后,传输的字节数会大幅度减少,所以通常我们会打开gzip。

首先我们打开nginx.conf文件,找到http代码块中的gzip相关选项,打开gzip(off -> on), gzip_min_length是小于多少字节不再执行压缩,因为小于一定的字节http传输直接就可以发送了,压缩反而消耗cpu性能,这里我们为了测试写成1,gzip_comp_level代表压缩级别,gzip_types是针对某些类型的文件才做gzip压缩。

http {
    ...
    gzip on;
    gzip_min_length 1;
    gzip_comp_level 2;
    gzip_types text/plain applicaton/x-javascript text/css image/png;
    ...
}

配置好之后我们重启Nginx, 浏览器中查看就会发现,传输的文件已经减少了很多,响应头中多出了Content-encoding: gzip。所以使用gzip以后整个web服务传输效率会高很多。

接下来我们演示另一个常用的功能,比如跟目录下有一个文件夹叫dlib,假定我们需要把dlib中的文件,或者文件夹及其目录结构信息分享给用户,用户来决定使用哪些文件。

这种常用的应用场景下呢,Nginx给我们提供了一个官方模块叫做autoindex, 我们先来看一下autoindex的说明,他可以提供当我们访问以/结尾的url时,显示这个目录的结构。使用方法也特别简单,就是autoindex on加入这样一个指令就可以了。

location / {
    autoindex on;
}

reload之后代码就生效了,他会把我们所访问的文件夹内所有文件列出来,当我们打开一个目录时,可以继续显示这个目录中的文件,这是一个很好的静态资源帮助功能。

还有一个非常常见的功能,比如我们的公网带宽是有限的,当有很多并发用户使用我们的带宽时,他们会形成一个争抢关系,我们可能会为了让用户访问某些大文件的时候来限制他的速度,以期望能够分理出足够的带宽给用户访问一些必要的小文件,如css,js等。

这个时候我们就可以使用set命令,配合我们一些内置的变量去实现这样的功能,比如说我们加上set $limit_rate 1k,他就在限制我们Nginx向客户浏览器发送响应的一个速度。

他的意思是每秒传输多少数据到浏览器中,这里我们写的是1k,这个时候我们去访问首页会发现,速度非常慢。

location / {
    set $limit_rate 1k;
}

接下来我们看下Nginx的另外重要的功能,就是记录access日志,首先我们需要记录access日志到底是怎样一种格式,我们找到一个指令叫做log_format, 他用来定义日志的格式。这里可以使用变量。

http {
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
}

$remote_addr为远端的地址,也就是浏览器客户端的ip地址,$time_local表示当时的时间。$status是返回的状态码。这样的日志格式我们需要命名,比如说我们这里命名为main。

为什么要做命名的,可能我们要对不同的域名下,做不同格式的日志记录,或者对不同的url一些大文件或者做一些反向代理等等不同用途时,我们记录不同日志格式。

配置好log_format之后我们还要去设置我们日志记录在哪里,我们可以用access_log这条指令,access_log所在的位置决定了他所属的这样一类请求会记录到后面的路径中,采用main这样一种格式。

比如access_log这里放在了server下,也就是所有发现这个域名或者这个端口的请求日志,都会记录到这个文件中。

那么刚刚我们再说日志格式的时候,我们提到了很多变量都可以放进来,这里我们举个例子。只要是变量我们这里都可以使用,gzip中有个变量叫做$gzip_radio, 表示当时使用的压缩比率,我们可以吧$gzip_radio放在我们的access.log中。

server {
    listen 8080;
    access_log logs/yindong.log main;
    location / {
        alias dlib;
    }
}

当我们配置好yindong.log后,所有的请求在完成之后都会记录下一条日志,在我们的yindong.log中, 我们可以进入logs/yindong.log中查看。每一条都是我们设置的格式。

以上就是搭建web静态资源服务器的常用方法和基本流程。

Nginx搭建一个具备缓存功能的反向代理服务

前面几节像大家演示了Nginx怎样作为一个静态资源web服务器工作,我们将以这个作为例子,把这个静态资源服务作为上游服务,再搭建一个Nginx作为反向当离,然后向大家演示,Nginx作为反向代理应该怎样使用。

由于上游服务要处理非常复杂的业务逻辑而且强调开发效率,所以他的性能并不怎么样,我们使用Nginx作为反向代理以后呢,可以由一台Nginx把请求按照负载均衡算法代理给多台上游服务器工作。

这样我们就实现了水平扩展,在用户无感知的情况下,我们添加更多的上游服务器,来提升我们的处理性能,而当上游服务器出现问题的时候,那么Nginx可以自动的把请求从有问题出现灾难的服务器,转交给正常的服务器。

我们可以把服务器作为上游服务器,上游服务器一般是不能被公网直接访问到的,我们可以在listen 8080端口时前面加上一个ip地址,比如127.0.0.1表示只能本机的进程来访问我们8080端口。

server {
    listen 127.0.0.1:8080;
}

这个时候我们再去访问页面地址就可以看到页面已经无法访问了,因为Nginx拒绝了浏览器发过去的链接。

现在我们搭建一个Nginx的反向代理,之前的Nginx服务我们使用的是Nginx的1.14版本,反向代理我们用openRestry。

反向代理另外一个Nginx的配置文件,我们首先添加一个upstream,也就是我的上游服务,他的一台server,访问地址是127.0.0.1:8080,如果我有很多台上游服务可以依次的放在这里。

upstream设置的一批服务,我们做一个命名比如叫local,接着我们做一个简单的配置。

因为我们这里作为反向代理的服务器有域名,对所有的请求我们使用proxy_pass这样一条指令,代理到我们刚刚配置的上游服务里。

upstream local{
    server 127.0.0.1:8080;
}
server {
    server_name yindong.com;
    listen 80;
    location / {
        proxy_set_header Host $host;
        proxt_set_header X-Real_IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://local;
    }
}

这时我们访问代理服务器上配置的域名,就可以看到访问结果了。

这个时候我们的响应头是由反向代理服务器发给我们的。

现在我们看下localtion中相关的配置,比如proxy_set_header,这个命令他起到一个这样的作用,因为有了一台反向代理,所以我们再去拿一些变量或者一些值可能就会出错了,比如说一个tcp链接他其实是有对端地址的,但是有了反向代理以后我们的反向代理与客户端是一个tcp链接,而反向代理与上游服务器又是另外一个连接,所以如果我们取原来的变量叫$remote_addr, 也就是tcp链接的远端地址,那么这个时候在上游服务那里,我去到的其实是我发现代理的这台机器的地址,那么如果我想拿浏览器的地址,作为我限制浏览器访问速度等功能的一个作用时其实是拿不到的。所以通过proxy_set_header我可以把有一些值添加一条新的header发送到上游,比如说叫x-real-ip,然后把他的值设为我们从tcp链接里面拿到的远端ip地址。

$host也是同样的因为用户直接访问的域名,是他在浏览器输入的,我们既可以让他在上游服务器可以处理这个域名,也可以由反向代理来处理。

所有这些配置特性我们都可以在官网中的http_proxy_module找到。

这里有个很重要的特性就是proxy_cache, 因为当我们的Nginx作为反向代理时,通常只有动态的请求,也就是不同的用户访问同一个url看到的内容是不同的,这个时候才会交由上游服务处理。

但是有一些内容可能是一段时间不会发生变化的,这个时候为了减轻上游服务器的压力,我们就会让Nginx把上游服务返回的内容缓存一段时间,比如缓存一天,在一天之内即使上游服务器对这个内容的响应发生了变化,我们也不管,我们只会去拿缓存住的这段内容向浏览器做出响应。

因为Nginx的性能远远领先于上游服务器的性能。所以使用这样一个特性后,对我们一些小的站点会有非常大的性能提升。

下面我们来演示一下怎么样配置一个缓存服务器。配置缓存服务器首先我们要去通过proxy_cache_path这条指令去设置我们的缓存文件写在哪个目录下。

比如这里是/tmp/nginxcache, 以及这些文件的命名方式,这些文件的关键之key, 我们是要放在共享内存中的。这里我们开了10MB的共享内存,命名为my_cache, 这些参数都在控制我们的缓存。

proxy_cache_patj /tmp/nginxcache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path_off;

这些缓存的使用方法就是在我们需要做缓存的url路径下,添加proxy_cache, 后面所跟的参数就是我们刚刚开辟的那一个共享内存,那么在共享内存中我们所设置的key是什么呢,就想我刚刚说的,同一个url访问时对不同的用户可能展示的东西是不一样的,所以用户这样一个变量就要放在key中。

这里我们做一个非常简单的key,比如说我们只跟我们访问的host url我们可能加了一些参数,这些参数可能已经指明了是哪个用户哪个资源,$host$uri$is_args$args; 这些作为一个整体的key。对于哪些访问不返回,我们这里也做了一些简单的处理。

location / {
    proxy_cache my_cache;
    proxy_cache_key $host$uri$is_args$args;
    proxy_cache_valid 200 304 302 1d;
}

加完这些参数以后,我们可以尝试停掉上游服务,然后访问站点,可以发现站点仍然是可以访问的。就是因为被缓存了。

用GOAccess实现可视化并实时监控access日志

Access日志记录了Nginx非常重要的信息,我们可以用Access日志来分析定位问题,也可以用它来分析用户的运营数据,但是如果想实时分析Access.log相对来说还比较困难。

有一款工具叫GoAccess, 他可以以图形化的方式,通过websocket协议实时的把Access.log的变迁反应到浏览器中,方便我们分析问题。下面我们将介绍GoAccess怎样应用在Nginx的Access.log的分析中。

GoAccess的站点是 https://goaccess.io, 他以一种非常好的图形化方式显示给我们,我们先来看以前之前的access日志。我们来看下之前搭建的静态服务器中的access是怎样的格式。

cat yindong.log

显示的是我们没有修改任何配置,只是安装完Nginx之后默认的access的日志格式所打印出来的日志内容,那这个时候GoAccess站点给我们提供了一个非常方便的日志格式用法。

我们打开GoAccess的Get Started目录快速开始,当我们的Nginx所用的log配置没有发生任何变化时,我们可以使用--log-format=COMBINED这样一种格式。

也就是说Nginx的Access非常灵活,我们可以自己添加各种不同的各模块的内置变量加入到Access中,所以当我们修改了Access.log的格式的时候,我们需要在log-format中重新定义我们所添加的格式,那么在这里我们没有添加任何的变量。

那么GoAccess是怎样使用的呢,它实际上会去使用-o这个参数生成一个新的html文件,把当前我们access.log文件中的内容以html图表的方式展示出来,当access.log变迁的时候呢GoAccess会新起一个socket进程,通过一种端口的方式把新的access.log推送到我们的客户端。

goaccess access.log -o report.html --log-format=COMBINED

现在我们开始执行GoAccess程序,首先我们制定了access.log程序制定的位置(yindong.log), 我们把它输出到../html/report.html这个文件中,我们使用的是--real-time-html就是事实更新这个页面的方式,我们的时间格式,--time-format='%H:%M:%S', 日期格式: --date-format='%d/%b/%Y', 以及日志格式--log-format=COMBINED都已经指定好了。

cd logs
goaccess yindong.log -o ../html/report.html --real-time-html --time-format='%H:%M:%S' --date-format='%d/%b/%Y' --log-format=COMBINED

那么GoAccess的安装可以用yum或者wget,也可以下载源码进行编译,这里就不再演示了。

启动完成之后可以看到一条log叫做 WebSocket server ready to accept new client connections, 也就是他已经打开了一个新的websocket监口,当我们访问他的report.html的时候,会向这个进程发起连接, 由这个进程给我们推送最新的log的变更。

接下来我们还要在nginx.conf中添加一个location,就是每当我们访问/report.html时候我们需要用alias把他重定向到我们刚刚访问的report.html中。

server {
    ...
    location /report.html {
        alias /usr/local/openresty/nginx/html/report.html;
    }
    ...
}

这时我们打开localhost:8080/report.html可以发现页面中展示7张表,因为我们静态web服务器刚刚搭建,所以几乎没用请求,这里有一些总体的统计,也有根据时间,根据浏览器,根据url,根据带宽等等各种信息统计出来的数值,而且这个数值是实时变动的,比如现在是281。

我们没有刷新这个页面,在我们去访问服务器之后我们日志发生变更,所以这里也变成了287。

以上就是GoAccess.log的简单用法,那么使用他以后我们可以非常直观的看到我们access.log统计信息上的变迁,他对我们分析我们网站的运营情况非常有帮助,可以看到每个时间点,每一周每一天,甚至不同的国家地区使用不同浏览器和操作系统的人使用我们站点的一个比例和分布。

从网络原理来看SSL安全协议

网络安全是当前非常受到关注的一个问题。大部分站点都是通过https来实现自己的数据安全的,那么怎么样才能把自己的站点编程https站点呢?我们需要了解ssl协议。

SSL的全称是Secure Sockets Layer,现在我们很多时候在使用TLS也就是Transport Layer Security很多同学可能不是很了解TLS是怎么样的一个协议,我们简单看一下他的发展过程。

SSL是由网景公司在1995年推出的,在3.0版本获得了一个非常大的发展。接下来微软把自己的IE浏览器捆绑windows一起卖出以后,导致网景遇到很大的困境,网景把SSL协议交给IETF组织。

在1999年,应微软的要求IETF把SSL更名为TLS1.0,那么,后面在06,08到2018年TLS分别发布了1.1,1.2和1.3协议。

那么TLS协议究竟是怎样保证http的明文消息被加密的呢?我们这里简述一下TLS的通用模型。

在ISO/OSI七层模型中,应用层是http协议,在应用层之下,我们的表示层也就是TLS所发挥作用的这一层,他通过握手,交换密钥,告警,对称加密的方式使http层没有感知的情况下做到了数据的安全加密,那么TLS究竟是怎样做到了数据的安全加密的呢。

我们可以看到TLS的安全密码套件,当我们抓包或者观察服务端配置时,我们可以看到安全密码的配置,这个安全密码的配置呢他决定了我们的TLS协议是怎样保证明文被加密的。这里大概有四个组成部分。

第一个组成部分叫做密钥交换,也就是ECDHE,这实际上是一个椭圆曲线加密算法的表达,密钥交换是为了解决浏览器和服务器之间怎样各自独立的生成密钥,而最后生成的密钥是相同的接下来他们会用这个密钥去加密数据。

那么在密钥交换这个过程中,我们需要让浏览器和服务器各自去验证对方的身份,而验证身份是需要一个算法的,这个算法叫做RSA,他用于身份验证。

接下来我们进行数据加密,解密这样的通讯的时候呢,我们需要用到对称加密算法而对称加密算法里AES_128——GCM就是表达这样一个对称加密算法,其中第一个部分AES,他表达了是怎样一种算法,第二个部分128表示了AES算法里支持了3种加密强度,我们使用了128位这样的一个加密强度。AES中有很多分组模式,其中GCM是一种比较新的分组模式,他可以提高多核CPU情况下加密和解密的一个性能。

SHA_256是一个摘要算法,他用来把不定长度的字符串生成固定长度的更短的一个摘要。

以上是TLS算法的概要介绍,下面我们来看一下对称加密算法和非对称加密算法有什么不同。

对称加密与非对称加密各自的应用场景

下面我们来看一下对称加密算法和非对称加密算法有什么不同。

在对称加密这样一个场景中,两个想通讯的人张三和李四,他们共同持有同一把密钥,张三可以把原始明文的文档,通过这一把密钥加密生成一个密文文档,而李四拿到这个文档以后呢,他可以用这把密钥还原转换为原始的明文文档,而中间的任何人如果没有持有这把密钥,即使他知道了对称加密的算法他也没有办法把密文还原成原始文档。

那么对称加密究竟是怎么实现的呢? 我们可以以RC4这样一个对称加密的序列算法来描述一下。

我们使用异或(xor)操作, 他是一个位操作,比如1和0进行异或得到1,0和1也得到了1,那么相同的1和1或者0和0进行异或操作都会得到0。

在这样一个场景下1010是我们共同持有的密钥,0110是我们的明文,张三执行加密的时候就会得到密文1100。

1 0 1 0 // 密钥
  xor
0 1 1 0 // 明文
  ||
1 1 0 0 // 密文

异或有一个对称的特性,就是把密文与密钥同样的做异或操作,比如1和1得到0, 1和0得到1,0和1得到1, 0和0得到0。

1 0 1 0 // 密钥
  xor
1 1 0 0 // 密文
  ||
0 1 1 0 // 明文

密文可以用同一把密钥完全还原成了明文,所以对称加密有一个最大的优点就是他的性能非常的好,他只要遍历一次就可以得到我们最终的密文,解密的过程也是一样,而非对称加密他的性能就会差很多。

我们看一下非对称加密算法,他根据一个数学原理,他会生成一对密钥,这一对密钥中如果我们称其中一个叫做公开钥匙,简称公钥,那么另一个就叫做私有钥匙,简称私钥。

公钥和私钥有什么特性呢,就是同一份命名文档如果用公钥加密了那么只有用对应的私钥才能把它解密,同样道理,如果文档用私钥加密了用公钥才能解密。

听到这里你可能有些迷惑,我们来说一下具体场景,比如说李四他有一对公钥和私钥,那么他就可以把他的公钥发布给大家,比如张三是其中的一个人,他拿到了李四的公钥,那么这个时候的加密操作是怎么做的呢。

比如张三如果想传递一份原始文档给李四,那么张三就拿着李四的公钥对原始文档进行加密,把密文再发送给李四,李四拿他的私钥才能进行解密,其他人用了公钥以后都没有办法进行解密。

 ----------                ----------                  ----------  
|  ------  |  李四的公钥    |  ------  |   李四的私钥     |  ------ | 
|  ------  | -----------> |  -- 密 -- |  ----------->  |  ------ |
|  ------  |    加密       |  ------  |     解密        |  ------ |
 ----------                ----------                  ----------
   原始文档                    加密文档                     原始文档 

公钥和私钥还有第二种用途,就是身份验证,比如现在有一段信息张三用它的私钥进行了加密,然后把密文发给了张三或者任何人,只要张三拿到了李四的公钥,因为公钥本身就是公开的,那么用公钥能够成功的解开这段密文就证明这段密文确实是由李四发出的。

这为我们接下来TLS的密钥交换算法提供了基本的签名保障,以上就是对称加密和非对称加密的基本原理。

SSL证书的公信力是如何保证的?

在之前的加密过程中,我们谈到了张三和李四之间通讯,但其实他们有个前提条件,就是李四必须知道张三就是张三,也就是他收到的信息必须是张三发来的。

那么这样一个新的问题在多方通信的过程中必须有一个公信机构,这个公信机构就是CA机构。接下来我们来看一看CA是怎么样颁发证书和把证书过期的。

下面这张图中我们可以看到,最右边的CA就是CA机构,他负责颁发证书,而我们作为一个站点的维护者我们就是证书的订阅人,首先我们必须去申请一个证书,为了申请这个证书我可能要去登记我是谁,我属于什么组织,我想做什么。

到了登记机构再通过CSR发给CA,CA中心通过以后他会去把生成一对公钥和私钥,那么公钥会在CA中保存着,公钥私钥证书订阅人拿到之后就会把它部署到自己的web服务器比如说Nginx,当浏览器访问我们的站点的时候,他会去请求我们的证书Nginx这样的web服务器会把我们的公钥证书发给浏览器,而浏览器需要去验证我们的证书是不是合法和有效的。

CA会把过期的证书放在CRL服务器里,这个服务器会把所有过期的证书形成一条链条所以他的性能非常的差,他又推出了OCSP程序他可以就一个证书去查询他是否过期,所以浏览器是可以直接去查询OCSP响应程序的,但OCSP响应程序性能还不是很高。

所以我们的Nginx会有一个OCSP的开关,当我们打开开关以后会由Nginx主动的去OCSP去查询,这样大量的客户端直接从Nginx就可以获取到证书是否有效。

证书究竟是怎样组成的呢?接下来我们看一下证书有哪几种类型。

第一种叫做域名验证DV证书,也就是说这个证书中他只会去验证我们域名的归属是否正确,比如我们申请证书的时候会发现只要域名指向的服务器是正在申请证书的那台服务器,就可以成功的申请到证书。如果使用其他CA机构颁发的证书可能会去验证注册的邮箱是否正确。

第二种证书叫做组织验证,OV验证证书,组织验证就是在申请证书的时候会去验证我们填写的机构,企业名称是否是正确的,所以OV证书的申请往往需要几天的时间,不像DV证书,基本上是实时就可以获取到了,而OV证书他的价格也要远远高于DV证书,DV证书很多都是免费的。

比OV证书做更严格的验证就是扩展证书,EV证书,因为EV证书做了更严格的验证,所以大部分浏览器会对EV证书显示的非常友好,他会把我们在申请证书时所填写的机构名称在浏览器的地址栏中的最左侧显示出来。

那我们获取这样一个证书是怎样生效的呢?浏览器从安全角度对DV,OV或者EV证书他的效果实际上是一样的。唯一验证的就是他的证书链。接下来我们来看一下他的证书链。

当我们点击网站地址栏中的锁头标志,点击证书,打开证书链的时候,可以发现存在三个级别,目前所有站点的主证书都是由三个证书构成的就是根证书,二级证书和我们的主证书。

那么为什么会形成这样一个三级机构呢,这是因为根证书,他的验证是非常谨慎的,像windows,安卓等操作系统每一年以上才会去更新一次他的根证书库,所以一个新的根证书CA机构是很难快速的加入到操作系统或者浏览器认可的库的。

而大部分浏览器他使用的是操作系统的证书库,可能像firefox这样的浏览器,他会维护自己的根证书库,所以浏览器在验证我们的证书是否有效时,除了验证有没有过期以外,最主要就是在验证根证书是不是有效的,是不是被跟证书库所认可的。

而我们的Nginx在向浏览器发送证书的时候呢需要发送两个证书,并不是三个证书,因为根证书是被操作系统或者浏览器内置的,我们不需要发送。

当Nginx向浏览器发送证书时会首先发送站点的主证书,接着会发送二级证书,浏览器会自动去认证二级证书的签发机构,根证书是不是有效的,到这里我们基本上搞清楚了,浏览器和服务器之间通信时怎样确认对方是我信赖的人。归根结底就是去验证给这个站点办法的根证书他的发行者是不是有效的。

SSL协议握手时Nginx的性能瓶颈在哪里?

接下来我们看一下TLS的通信过程,通信过程中,双方主要想完成四个目的。

  1. 验证对方身份

由浏览器像服务器发送一个client hello消息。因为浏览器是非常多样化的,而且浏览器的版本在不停的变更,所以不同的浏览器所支持的安全套件,加密算法都是不同的。所以这一步主要是告诉服务器,支持哪些加密算法。

  1. 对安全套件达成共识

Nginx会有一个自己能够支持的加密算法列表,以及他倾向于使用哪一个加密算法套件,Nginx会选择一套他最喜欢的加密套件发送给客户端,如果我们想复用session,也就是说Nginx打开了session cache,希望在一天内断开链接的客户端不用再次协商密钥,那么在这一步,他可以直接去复用之前的密钥。

所以server hello信息中主要会发送究竟我们选择哪一个安全套件。

  1. 传递并生成密钥

Nginx会把自己的公钥证书发送给浏览器,这个公钥证书中是包含证书链的,所以浏览器可以找到自己的根证书库,去验证证书是否是有效的

  1. 对数据进行加密通讯

服务器会发送server hello done,如果之前我们协商的安全套件,比如说椭圆曲线算法,这时需要把椭圆曲线的参数发送给客户端。以方便我们生成最终进行加密的密钥。客户端需要根据椭圆曲线的公共参数,生成自己的私钥以后再把公钥发送给服务器。

服务器有了自己的私钥,把公钥发送给客户端,可以根据自己的私钥和客户端的私钥,共同生成双方加密的密钥,这是服务器独自做的。

客户端根据服务器发来的公钥和他自己的私钥也可以生成一个密钥。

服务器和客户端各自生成的密钥是相同的,这个是由非对称加密算法保证的。

接着我们可以用用生成的密钥进行数据加密,进行通信,从这个过程中我们可以看到,TLS通信主要在做两件事,第一个是交换密钥,第二个是加密数据,所以最消耗性能的也是这两点。

我们来看一下Nginx怎样去优化他的性能,这里我们主要看他的算法性能,Nginx在握手的时候主要看他的椭圆加密算法和RSA算法的性能。

对于小文件,握手是影响他QPS性能的主要指标,对于大文件而言,我们主要考虑对称加密算法的一个性能比如AES,对称加密算法虽然性能很好,但是对非常大的这样一个文件,我们去测吞吐量的时候也可以看出,相对于其他算法AES的性能还是比较好的。

AES的综合性能,当以小文件为主时主要考验的是Nginx的非对称加密的一个性能,比如说RSA,当我们主要处理大文件时主要考验的是对称加密算法的性能,比如说AES。

那么当我们面对的场景是小文件比较多时,我们可能重点应该优化椭圆曲线算法的一些密码强度,看是不是有所降低,当我们主要面对大的文件处理的时候我们需要考虑AES算法是不是可以替换为更有效的算法,或者把密码强度调得更小一些。

以上我们讨论了TLS加密算法的过程,包括握手中怎样交换密钥,以及使用密钥怎样交换数据,以及Nginx在小文件和大文件场景中性能的表现,接下来我们会将演示一下http网站改造为https网站。

用免费SSL证书实现一个HTTPS站点

之前我们已经谈到了TLS的原理,如果现在我们有一个网站,那么我们的域名访问网站的时候,他不是https的,下面我们演示一下如何生成一个免费的DV证书来使得我们的网站编程https的网站。

首先我们有一个域名yindong.zhiqianduan.com, 他是一个http的网站,我们开始安装工具。

如果你的系统是CentOS,可以使用yum install pthon2-certbot-nginx安装这样的一个python工具就可以做到了。如果你是优班图操作系统,可以用wget工具去下载。

当我们安装好这个工具以后,会提供一个命令叫certbot,当我们后缀加上--nginx的时候他就开始为nginx的conf自动执行响应的配置文件的修改了。通常他会默认修改/usr/local/目录下的nginx配置。我们可以通过--nginx-server-root指定nginx.conf所在的路径。

接下来使用-d指定需要申请证书的域名,比如说yindong.zhiqianduan.com。

certbot --nginx --nginx-server-root=/usr/local/nginx/conf/ -d yindong.zhiqianduan.com

日志表明首先他会去获取一个证书,接下来会等待验证,然后会把这个证书部署到nginx.conf文件中。最后提示我们两个选择,第一不要做任何的重定向,第二做重定向。什么意思呢?就是很多时候我们有了https站点以后我们会希望所有的http明文显示的不安全流量全部用301或者302这样的重定向协议把他转到https站点。

那这里呢我们选择1,不做任何重定向,最后他会提示我们已经成功了。这个时候我们访问站点会发现可以使用https访问了。

接下来我们看下nginx.conf里面做了什么工作,可以看到他在server指令块中增加了443端口,接着他将公钥证书和私钥证书部署好,并把一些通用的参数通过include加入到配置文件中。

我们可以看一下options-ssl-nginx.conf里面有什么逻辑。

因为ssl中最消耗性能是的握手,所以为了降低捂手,增加了一个sessin_cache, 设置了1m,可以为大约4000个链接建立服务。也就是说每个http链接握手建立第一次以后如果断开了再次链接,那么在session_timeout时间以内时是不用进行再次握手的。我们可以复用之前的密钥,那么session_timeout设置了1440m,也就是一天。

ssl_protocols表示https支持哪些版本的TLS协议,ssl_prefer_server_ciphers表示nginx开始决定使用哪些协议与浏览器进行通信,他是通过ssl_ciphers中的安全套件,所有的安全套件以分号分隔,他们是有顺序的,排在前面的会优先被使用。所以说如果浏览器支持所有的安全套件的时候会选用第一个。

最后server中的ssl_dhparam是表示加密的时候使用怎样的参数,这些参数会决定网略安全的加密强度。

以上就是将http站点改造成https站点的一个过程。

基于OpenResty用Lua语言实现简单服务

之前我们介绍了官方开源Nginx的安装和实践,其实很多人在使用openresty做一些开发。下面我们演示一下openresty,这里我们包含五个部分。

  1. 下载openresty

  2. 分析目录结构

  3. 执行编译,安装

  4. 添加lua代码

  5. 运行openresty,看下添加lua代码的显示效果。

首先我们到openresty的站点,openresty.org, 这个时候我们去下载,下载中通常不会用二进制版本,在源码发布中找到最新版本。复制他的下载链接进行下载。

wget http://openresty.org/download/openresty-1.13.6.2.tar.gz

下载完成以后解压这个压缩包,然后进入到源代码目录,可以发现这个目录和Nginx的源代码目录相比少了很多东西,少的这些东西都在bundle目录下,build目录是我们编译以后生成的一些中间目标文件。

在bundle目录中有很多模块,最核心的是Nginx的源代码,也就说当前的OpenResty是基于对应的Nginx版本的,比如说1.13.6基于这个版本进行了二次开发。

那么所有Nginx1.13.6中没有的特性都不可能出现在OpenResty的这个版本中。

那么其他的目录呢这个模块又分为两类,第一类是Nginx的第三方模块,都是一些C模块,他们通常会以ngx开头。

还有一类模块是LUA模块,就是lua代码写就的,他需要使用刚刚那些C模块提供的各种功能,那么我们在编译的时候主要在编译C模块。

我们回到源代码的跟目录,看一看他的configure目录和Nginx官方开源版本这样的configure目录脚本有什么不同。

首先看一下他的帮助文件。

./configure --help | more

通过帮助文件我们可以看到OpenResty和Nginx的帮助文件基本没有太大的不同,只不过OpenResty他集成了很多第三方模块,比如http_echo, http_xss等等,这些在Nginx的官方版本中都是没有的模块。这些模块很多是OpenResty的作者写的。

在官方模块中如果前面出现了--without说明他默认是内置在我们的编译版本中的,如果是--with是默认没有安装在我们的Nginx当中的。这里是有所不同的。

我们可以看到它最核心的lua_module这个核心模块通常是不能移除来的,移除来之后整个lua就不能运行了。其他的配置项和官方的Nginx基本上是一模一样的。

现在我们开始编译一个最基本的OpenResty。编译完成以后可以执行make install安装

./configure

make install

下面我们来看一下怎么样把lua代码添加到OpenResty当中。

打开OpenResty的conf文件,在这个文件中实际上是可以直接添加lua代码的,但是不能直接的把lua的语法,直接放在conf中,因为Nginx的解析器配置语法和lua代码是不相同的。

在OpenResty的nginx_lua_module中提供了几条指令,其中有一条叫做content_by_lua, 是在http请求处理的内容生成阶段我们用lua代码来处理,下面我们写一个简单的例子。

增加一个location,当输入/lua的时候,使用lua代码进行处理, 为了使我们输出的这样一个文本能够以浏览器直接显示文本的方式显示,我们添加一个default_type text/html,在content_by_lua中我们加一些最简单的命令来演示lua是怎么生效的。

那么在OpenResty的lua模块中,他提供了一些API,比如说ngx.say会生成http响应,也就是说是放在http请求中的body中的,而不是放在header中的。

放在body中的文本可以通过ngx.say添加进去,我们这个简单的演示示例实现的是浏览器在发起http请求的过程中会在UA的header中去添加当前浏览器的类型。

这里例子中通过ngx.req.get_headers去把用户请求中的所有的http头部取出来,然后找出UA,把这个值返回给浏览器中。

server {
    server_name yindong.com;
    listen 80;

    location /lua {
        default_type text/html;
        content_by_lua 'ngx.say("User-Agent: ", ngx.req.get_headers()["User-Agent"])';
    }

    location / {
        alias html/yindong/;
    }
}

我们可以访问域名的lua路径,看下效果。

从这个例子中可以看到。通过OpenResty的nginx_lua_http模块我们可以用它提供给我们的API完成很多功能,在这里我们可以用lua语言本身的一些工具库,把lua语言添加进来参与我们生成响应的这样一个过程中。

这就可以直接使用OpenResty提供的API或者lua代码来生成响应,为浏览器为客户端服务,而我们的API服务正是基于这样一个原理,我们可以用lua语言以及相应的提供的API库直接去访问redis,mysql或者tomcat等等这样的服务,然后把不同的响应通过程序逻辑组合成相应的http响应返回给用户。