一文带你了解Nginx基础知识

433人浏览   2023-10-23 14:44:57

1. 概述

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

nginx最重要的三个使用场景个人认为是静态资源服务、反向代理服务和api服务。

web请求走进服务以后会先经过nginx再到应用服务,然后再去访问redis或者mysql提供基本的数据功能。

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

很多服务构成集群的时候,需要nginx具有反向代理的功能,可以把动态请求传导给对应的应用服务。服务集群一定会带来两个需求,动态的扩容和容灾。

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

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

反向代理衍生出另外的功能叫缓存,他能够加速访问,而很多时候在访问像css或js文件又或者一些小图片是没有必要由应用服务来访问的,他只需要直接由nginx提供访问就可以了这就是nginx的静态资源功能。

应用服务它本身的性能有很大的问题,数据库服务要比应用服务好的多,原因是数据库他的业务场景比较简单,并发性能和tps都要远高于应用服务。由nginx直接去访问数据库或者redis也是不错的选择。

还可以利用nginx强大的并发性能,实现如web防火墙的一些业务功能,这就要求nginx服务有非常强大的业务处理功能,openResty和nginx集成了一些工具库来实现此功能。

2. 历史背景

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

摩尔定律表明之前服务跑在1GHZ的CPU上的服务更新到2GHZ的CPU时服务会有两倍的性能提升。

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

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

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

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

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

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

3. nginx的优点

大部分的程序和服务器随着并发连接数的上升他的RPS数会急剧的下降,这里的原理就像之前所说过的,他的设计架构是有问题的。

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

而具有高并发的同时达到高性能,往往需要非常好的设计,那nginx可以达到什么样的标准呢?

比如说现在主流的一些服务器32核64G的内存可以轻松达到数千万的并发链接,如果是处理一些简单的静态资源请求,可以达到一百万的RPS这种级别。

其次nginx的可扩展性非常好,主要在于他的模块化设计非常的稳定,而且nginx的第三方模块的生态圈非常的丰富。甚至于有像TNG,openRestry这种第三方插件。丰富的生态圈为nginx丰富的功能提供了保证。

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

对于nginx这种高并发高性能的反向代理服务器而言,他往往运行在企业内网的边缘节点上,如果企业想提供4个9,5个9,甚至更高的高可用性时,对于nginx持续运行能够down机的时间一年可能只能以秒来计。所以在这种角色中,nginx的高可靠性给提供了非常好的保证。

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

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

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

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

以上的优点是nginx最核心的特性。

【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!~点击加入(需要自取)


4. 主要组成部分

首先是nginx的可执行文件,它是由nginx自身的框架、官方模块以及各种第三方模块共同构建的文件。他有完整的系统,所有的功能都由他提供。

第二个部分是nginx.conf配置文件,类似于骑车的驾驶员,虽然可执行文件已经提供了许多功能,但这些功能有没有开启,或者开启了以后定义了怎样的行为处理请求,都是由nginx.conf配置文件决定的。

nginx的第三个组成部分叫做access.log访问日志,access.log会记录下每一条nginx处理过的http请求信息与响应信息。

第四个组成部分是error.log错误日志,当出现了一些不可预期的问题时,可以通过error.log去把问题定位出来。

这四个部分是相辅相成的。

nginx的可执行文件和nginx.conf定义了处理请求的方式。如果想对web服务,做一些运营或者运维的分析,需要对access.log做进一步的分析。如果出现了任何未知的错误,或者与预期的行为不一致时,应该通过error.log去定位根本性的问题。

5. 版本规则

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

每一个版本都有mainline主干版本和stable稳定版本。

在nginx的官网点击右下角的download,就可以看到版本号列表,单数版本表示主干版本,会新增很多功能,但不一定稳定。双数版本是稳定版本。

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, 并且具有更丰富的功能。

6. 版本选择

免费开源: nginx.org

商业版本: nginx.com

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

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

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

所以Tengine实际上是修改了nginx官网版本的主干代码,当然框架被修改以后Tengine就遇到了一个明显的问题,没有办法跟着nginx的官方版本同步的升级。Tengine也可以使用nginx的第三方模块。

OpenResty的作者章亦春在阿里巴巴的时候开发了Lua语言版本的openResty,因为nginx的第三方模块开发的难度相当大,章亦春把nginx非阻塞事件的一种框架以Lua语言的方式提供给了广大开发者。

OenRestry兼具了高性能,以及开发效率高的特点,OpenResty同样有开源版和商业版,目前多使用openresty.org站点下的开源版本。商业版OpenRestry的主要特点是技术支持相对比较好很多。

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

7. 编译配置

安装nginx有两种方法,除了编译外,还可以直接用操作系统上自带的一些工具,比如说yum,apt-get,直接去安装nginx。

但是直接安装nginx有个问题,就是nginx的二进制文件不会把模块直接编译进来,毕竟nginx的官方模块,并不是每一个默认都会开启的。

如果想添加第三方的nginx模块,就必须通过编译nginx的方式。

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

打开nginx.org在页面中找到右下角donwload,选择Stable版本的下来链接,右键复制链接地址即可,进入到Linux中使用wget进行下载

cd  /home/nginx
wget http://nginx.org/download/nginx-1.18.0.tar.gz
复制代码

下载完nginx压缩包以后首先解压压缩包。

tar -xzf nginx-1.18.0.tar.gz
复制代码

接着进入解压后的目录通过ll命令查看所有文件。

cd nginx-1.18.0
ll
复制代码

第一个目录叫auto目录。

cd auto
复制代码

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

CHANGES文件标记了nginx每一个版本中提供了哪些特性和bugfix。

cat ../CHANGES
复制代码

其中会有feature,bugfix,change三种特性在里面。

CHANGES.ru文件是俄罗斯语言的CHANGES文件,可能因为作者是个俄罗斯人。

conf文件是一个示例文件,就是把nginx安装好以后,为了方便运维配置,会把config里面的示例文件copy到安装目录。

configure脚本用来生成中间文件,执行编译前的一个一些配置,也就是记录编译前的设定信息,编译时使用。

contrib目录提供了两个脚本和vim工具,也就是让vim打开config配置文件时支持代码高亮。

把contrib目录下vim的所有文件copy到自己的目录中

cp -r contrib/vim/* ~/.vim/
复制代码

就可以把nginx语言的语法高亮显示在vim中了。

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

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

src目录是nginx的核心源码。

8. 开始编译

编译前可以先看一下configure支持哪些参数。

./configure --help | more
复制代码

首先就是确定nginx执行中会去找哪些目录下的文件作为辅助文件。比如用动态模块时--modules-path就会产生作用。--lock-path确定nginx.lock文件放在哪里等。

如果没有任何变动的话只需要指定--prefix=PATH就可以了,设定一个安装目录。

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

比如说--with-http_ssl_module或者--with-http_v2_module通常需要主动加--with的时候,意味着模块默认是不会编译进nginx的。

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

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

这里指定的nginx的安装目录是在/home/nginx目录下。

./configure --prefix=/home/nginx/nginx/
复制代码

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

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

执行make编译。

make
复制代码

编译完成以后如果没有任何错误,就可以看见生成了大量的中间文件,以及最终的nginx二进制文件。

cd objs/
ll
复制代码

最后进行make install。

make install
复制代码

安装完成之后在--prefix指定的安装目录中可以看到很多目录,nginx的执行文件就在sbin目录下。

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

可以看到在conf目录下所有文件就是在源代码中conf目录copy过来的,其中的内容也是完全相同的。

9. 配置语法

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作为他的名字。

具体什么样的指令有名字什么样的指令没有名字是由提供指令块的nginx模块来决定的,他也可以决定指令块后面有一个或者说多个参数,或者说没有参数。

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

include是导入其他配置模块的意思。

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

使用$符号可以使用变量,可以看下limit_req_zone这里用了一个参数叫做$binary_remote_addr,这是一个变量描述的是远端的地址。

部分指令的参数是支持正则表达式的,比如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模块去解析和执行的,非http模块,比如说像stream或mime是没有办法去解析指令的。

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

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

10. 重载,热部署,日志切割

需要帮助的时候可以用-? 或者 -h获取帮助信息。

nginx -?
nginx -h
复制代码

默认情况下编译出来的nginx会寻找执行configure命令时指定的配置文件。在命令行中可以指定另一个配置文件用-c 路径。

还能指定一些配置用-g,指令就是在nginx的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可以测试一下配置文件是否合法问题。

-V是在编译时用configure脚本执行所加的所有参数。

1. 重载配置文件

修改nginx配置文件中的一些值,比如说conf/nginx.conf文件中,打开tcp_nopush。

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

2. 热部署

nginx在运行的情况下想更换最新版本的nginx,根据之前所说的,nginx编译方法下载一个新的nginx。

把最新版本的nginx编译后的可执行文件nginx,copy到目录中替换掉正在运行的nginx文件。copy完成需要给正在运行的nginx的master进程发送一个信号,告诉他开始进行热部署做一次版本升级,给nginx的master进程发送一个信号,USR2信号。

kill -USR2 进程号(13195)
复制代码

nginx会新启一个master进程使用的正式刚刚复制过来的最新的nginx二进制文件。

旧的worker也在运行,新的master会生成新的worker,他们会平滑的把所有的请求过渡到新的进程中。

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

kill -WINCH 13195
复制代码

这时老的worker进程会优雅的退出,但是老的master进程还在,只是是没有worker进程了。

这说明所有的请求已经全部切换到新的nginx中了,如果需要把新版本退回到老版本,可以向老的进程发送reload命令,让他重新把worker进程拉起来。再把新版本关掉。所以保留master是为了允许做版本回退。

3. 日志切割

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

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

mv access_log bak.log
复制代码

接着执行命令reopen。

nginx -s reopen
复制代码

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

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

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

11. 静态资源Web服务器

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

这里需要指定url的后缀与文件的后缀一一对应,有两种用法,root和alias,root是系统的跟目录,所以通常使用alias,alias是nginx的安装目录。

server {
    listen 8080;
    ...
    location / {
        alias www/;
        ...
    }
    ...
}
复制代码

做完配置之后启动nginx在浏览器中访问localhost:8080就可以了。

nginx -s reload
复制代码

1. 开启gzip

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

首先打开nginx.conf文件,找到http代码块中的gzip相关选项,打开gzip(off -> on), gzip_min_length是小于多少字节不再执行压缩,因为小于一定的字节http传输直接就可以发送了,压缩反而消耗cpu性能,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服务传输效率会高很多。

2. 打开目录结构

nginx给提供了一个官方模块叫做autoindex,他可以提供当访问以/结尾的url时,显示目录的结构。使用方法也特别简单,就是autoindex on加入一个指令就可以了。

location / {
    autoindex on;
}
复制代码

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

3. 网速限制

比如公网带宽是有限的,当有很多并发用户使用带宽时,他们会形成一个争抢关系,可以让用户访问某些大文件的时候来限制他的速度,节省足够的带宽给用户访问一些必要的小文件。

就可以使用set命令,配合一些内置的变量实现这种功能,比如说加上set $limit_rate 1k,限制nginx向客户浏览器发送响应的一个速度。意思是每秒传输多少数据到浏览器中。

location / {
    set $limit_rate 1k;
}
复制代码

4. 日志

首先需要设置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所在的代码块决定了日志的位置比如access_log这里放在了server下,也就是所有请求这个路径和端口的请求日志,都会记录到logs/yindong.log文件中,使用的格式就是main。

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

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

12. 反向代理服务

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

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

反向代理需要添加一个upstream,就是上游服务server,访问地址是127.0.0.1:8080如果有很多台上游服务可以依次的放在这里。

upstream设置的一批服务叫local。对所有的请求使用proxy_pass一条指令,代理到local里。

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;
    }
}
复制代码

因为反向代理的原因,真实的服务器拿到的信息是经过nginx代理服务器转发的,所以很多信息都不是真实的,比如说域名,ip都是代理服务器发送过来的,所以需要在location中做一些配置处理。

通过proxy_set_header可以把有一些值添加一条新的header发送到上游,比如说叫x-real-ip,然后把他的值设为从tcp链接里面拿到的远端ip地址。

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

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

1. 缓存

这里有个很重要的特性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;
}
复制代码

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

13. 监控access日志

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

有一款工具叫GoAccess可以以图形化的方式,通过websocket协议实时的把Access.log的变迁反应到浏览器中,方便分析问题。

GoAccess的站点是 https://goaccess.io, 以一种非常友好的图形化方式显示。

GoAccess使用-o参数生成新的html文件,把当前access.log文件中的内容以html图表的方式展示出来,当access.log变迁的时候GoAccess会新起一个socket进程,通过端口的方式把新的access.log推送到客户端。

goaccess access.log -o report.html --log-format=COMBINED
复制代码

首先制定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就可以看到效果了。

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

14. SSL安全协议

SSL的全称是Secure Sockets Layer,现在很多时候使用的是TLS也就是Transport Layer Security。可以将TLS看做是SSL的升级版。

SSL是网景公司在1995年推出的,后来因为微软把自己的IE浏览器捆绑windows一起卖出导致网景遇到很大的发展困境,网景把SSL协议交给IETF组织。

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

那么TLS协议究竟是怎样保证http的明文消息被加密的呢?

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

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

第一个组成部分叫做密钥交换,也就是ECDHE,这实际上是一个椭圆曲线加密算法的表达,密钥交换是为了让浏览器和服务器之间怎样各自独立的生成密钥,数据传输时他们会用密钥去加密数据。加解密是需要使用到对方的密钥的所以需要进行交换。

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

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

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

15. 对称加密、非对称加密

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

那么对称加密究竟的实现可以以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 0 1 0 # 密钥
  xor   # 异或操作
1 1 0 0 # 密文
  | |    # 输出
0 1 1 0 # 明文
复制代码

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

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

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

比如说李四他有一对公钥和私钥,那么他就可以把他的公钥发布给大家,比如张三是其中的一个人,他拿到了李四的公钥,加密操作是怎么做的呢?

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

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

公钥和私钥还有第二种用途,就是身份验证,比如现在有一段信息李四用它的私钥进行了加密,然后把密文发给了张三,只要张三如果可以使用李四的公钥解开这份文档,那么就证明这段密文确实是由李四发出的。因为只有李四有自己的加密私钥,如果是王五加密的文档张三用李四的公钥是解不开的,只有用李四私钥加密的使用李四的公钥才能解开。

16. SSL证书的公信力

这里其实还有个问题,李四怎么就知道消息真的是张三发过来的。这里面涉及到一个新的概念叫公信机构。在多方通信的过程中必须有一个公信机构CA,负责颁发证书和把证书过期的。

作为站点的维护者就是证书的订阅人,首先必须申请一个证书,申请证书可能需要登记是谁,属于什么组织,想做什么。

登记机构通过CSR发给CA,CA中心通过后会生成一对公钥和私钥,公钥在CA保存着,公钥私钥证书订阅人拿到之后就会把它部署到自己的web服务器,当浏览器访问站点的时候,服务器会把公钥证书发给浏览器,浏览器需要向CA验证证书是否合法和有效的。如果有效就证明没有被篡改。

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

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

证书一共有3种类型。

第一种叫做域名验证DV证书,也就是说证书只会去验证域名的归属是否正确,申请证书的时候只要域名指向的服务器是正在申请证书的服务器,就可以成功的申请到证书。

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

比OV证书做更严格的是EV证书,大部分浏览器对EV证书显示的非常友好,他会把证书申请时所填写的机构名称在浏览器的地址栏中显示出来。

浏览器在安全角度对DV,OV,,EV证书他的效果是一样的。唯一验证的就是证书链。

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

之所以需要三级机构是因为根证书的验证是非常谨慎的,如windows,安卓等操作系统每一年以上才会去更新一次根证书库,所以一个新的根证书CA机构是很难快速的加入到操作系统或者浏览器中的。

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

nginx在向浏览器发送证书的时候需要发送两个证书,根证书是被操作系统或者浏览器内置的并不需要发送。首先发送站点的主证书,接着会发送二级证书,浏览器会自动去认证二级证书的签发机构,根证书是不是有效的。

浏览器和服务器之间通信时确认对方是信赖的人其实就是验证给站点颁发根证书的发行者是不是有效的。

17. SSL协议握手时nginx的性能瓶颈

TLS的通信过程主要想完成四个目的。

1. 验证对方身份

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

2. 对安全套件达成共识

nginx有自己能够支持的加密算法列表,以及他倾向于使用的哪一个加密算法套件,nginx会选择一套他最喜欢的加密套件发送给客户端。

如果想复用session,也就是说nginx打开了session cache,希望在一天内断开链接的客户端不用再次协商密钥,可以直接去复用之前的密钥。

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

3. 传递并生成密钥

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

4. 对数据进行加密通讯

服务器会发送server hello done,如果之前协商的安全套件是椭圆曲线算法,这时会把椭圆曲线的参数发送给客户端。客户端需要根据椭圆曲线的公共参数,生成自己的私钥后再把公钥发送给服务器。

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

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

服务器和客户端各自生成的密钥是相同的,是由非对称加密算法保证的。接着可以用生成的密钥进行数据加密,进行通信。

TLS通信主要在做两件事,第一个是交换密钥,第二个是加密数据,主要的性能消耗也是这两点。

nginx在这里是有性能优化的,主要是他的算法性能,对于小文件,握手是影响QPS性能的主要指标,对于大文件而言,主要考虑对称加密算法的性能比如AES,对称加密算法虽然性能很好,但是对非常大的一个文件,测吞吐量时还是AES的性能比较好的。

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

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

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

首先需要有一个域名比如说yindong.zhiqianduan.com他是一个http的网址。

接着开始安装工具,必须的工具。

如果系统是CentOS,可以使用yum安装,优班图系统可以使用wget工具下载。

yum install pthon2-certbot-nginx
复制代码

安装好会提供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文件中。最后提示两个选择,第一不要做任何的重定向,第二做重定向。重定向就是将http的访问302到https从而禁掉不安全的http访问。

选择之后就可以使用https访问yindong.zhiqianduan.com域名了。
https://yindong.zhiqianduan.com

他是在在server指令块中增加了443端口,让后将公钥证书和私钥证书部署好,并把一些通用的参数通过include加入到配置文件中。

因为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是表示加密的时候使用怎样的参数,这些参数会决定网络安全的加密强度。

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

在openresty的站点(openresty.org)下载,在源码发布中找到最新版本,复制他的下载链接进行下载。

wget http://openresty.org/download/openresty-1.13.6.2.tar.gz
复制代码

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

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

所有nginx对应版本中没有的特性都不可能出现在OpenResty的版本中。

其他的目录又分为两类,第一类是nginx的第三方模块,都是一些C模块,通常会以ngx开头。第二类模块是LUA模块,是lua代码写就的,他需要使用刚刚那些C模块提供的各种功能,在编译的时候主要是在编译C模块。

./configure --help | more
复制代码

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

最核心的lua_module核心模块通常是不能移除来的,移除来之后整个lua就不能运行了。其他的配置项和官方的nginx基本上是一样的。

./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中的。

可以通过ngx.say语法将内容添加到body中的文本中。这里通过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语言添加进来参与响应的过程。

可以用lua语言以及相应的提供的工具库直接访问redis,mysql或者tomcat等服务,然后把不同的响应通过程序逻辑组合成相应的内容返回给用户。

相关推荐