一文带你了解Nginx基础知识 | 建议收藏
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
网站上直接下载就可以。
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
配置语法的注释,以描述下面一些配置的表达。
部分指令的参数是支持正则表达式的,比如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"';
}复制代码
不同的名字可以对不同的域名下,做不同格式的日志记录,或者对不同的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
设置的一批服务叫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
中做一些配置处理。
$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.lo
g统计信息上的变迁,对分析网站的运营情况非常有帮助,可以看到每个时间点,每一周每一天,甚至不同的国家地区使用不同浏览器和操作系统的人使用站点的一个比例和分布。
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
证书很多都是免费的。
浏览器在安全角度对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
等服务,然后把不同的响应通过程序逻辑组合成相应的内容返回给用户。