前端抢饭碗系列之深入Nginx
在大部分童鞋的印象里,Nginx应该都是属于后台工作的范畴,前端只要写好页面就好了;然后随着大前端范围的不断扩展,前端也在不断的进军服务器领域,而Nginx就是进军服务器领域必备的技能之一;以前我们都需要“低声下气”的让后端的同事给我们配置页面域名,但是学会了Nginx配置,域名配置、代理转发什么的完全就可以我们自己来了,这样抢来的饭碗它。。。。它难道不香吗?
简介
那么Nginx到底是什么,首先我们来看一下百度百科对Nginx的定义:
Nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
这里有三个词很关键,我们来拆解一下,分别是是高性能、反向代理和web服务器;首先这个web服务器自不用多说,像我们熟知的Apache、IIS、Tomcat等都是web服务器;然后是高性能,一个服务器的性能自然是网站开发者最为关心的,那么服务器的性能如何来进行衡量呢?一般可以通过CPU和内存的使用量来进行衡量。经过笔者简单的并发测试,在20000个并发链接时,CPU和内存占用也非常低,CPU仅占5%,内存占用也才2MB不到。
我们可以通过一个web压力测试工具Apache Bench
,对Nginx进行简单的压力测试;通过在命令行ab -n 20000 -c 10000 [url]
,我们对Nginx的首页发起请求总数为20000,并发数为10000的请求测试,测试结果如下:
我们看到总的请求时间(Time taken for tests)是25秒,平均每个请求耗时(Time per request)1.25毫秒,在这么高的并发量下面,服务器响应性能还是挺不错的。
然后是反向代理,与之对应的就是正向代理,这两者的区别也是面试中经常被问到的。我们先来看一下什么是正向代理,一个正向代理最典型的例子就是我们常用的“梯子”。
我们直接访问Google,是访问不到的,但是如果我们使用了代理服务器,那么通过访问代理服务器就可以浏览Google,这里的代理服务器就属于正向代理
;通过正向代理我们可以访问原来无法访问的资源。
那么什么是反向代理呢?反向代理最典型的例子就是我们的Nginx服务器了;比如我们在访问某个网站时,由代理服务器去目标服务器获取数据后返回给客户端,这样就能够隐藏真实服务器的IP地址,只对外开放代理服务器,以防止外网对内网服务器的恶性攻击。
理解了上面两个典型的案例,相信大家对正向反向代理也了解了,我们总结一下:
- 正向代理,代理客户端,服务端不知道实际发起请求的客户端。
- 反向代理,代理服务端,客户端不知道实际提供服务的服务端。
安装配置
Nginx安装程序分为Linux版和Windows版,Windows版本的Nginx下载解压后就可以直接运行了,而Linux版本的需要make、configure等命令编译安装,好处是可以方便灵活的编译不同的模块到Nginx;网上也有很多的安装教程,这里就不再赘述了,可以从官网下载适合自己的版本,下载好后我们来看一下他的目录结构:
1 |
|
我们经常用到的就是conf目录和html目录;而在根目录可以运行常用的一些命令对Nginx进行操作控制:
1 |
|
我们看前四个命令会发现,这四个命令可以分为两种,重启和停止Nginx,不过一种是强制的方式,另一种是优雅的方式;强制的方式就是让Nginx立即停止当前处理的所有请求,丢弃链接,停止工作;而优雅的方式是允许Nginx将当前正在处理的请求处理完成,但是不再接收新的请求,所有处理完成后再停止工作。
我们再来看一下主要配置文件nginx.conf的基本结构:
1 |
|
配置文件中主要可以分为以下几个块:
- 全局模块:从配置文件开始到events块之间的内容,此处的配置影响nginx服务器整体的运⾏,⽐如worker进程的数量、错误⽇志的位置等
- events:配置影响nginx服务器或与用户的网络连接。
- http:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。
- server:配置虚拟主机的相关参数,一个http中可以有多个server。
- location:配置请求的路由,以及各种页面的处理情况。
很多时候,我们不会将所有的配置全都写在一个主配置文件,因为这样会显得冗长,也不知道每个模块是做什么用的;而是会根据项目来拆分多个配置文件,每个配置文件彼此独立,互不干扰,然后在主配置文件中引入;我们在conf目录下新建一个projects目录,然后可以新建多个.conf配置文件:
1 |
|
然后在主配置nginx.conf中将projects目录下的所有配置文件引入:
1 |
|
这样我们可以直接在projects目录下新增.conf后缀的配置文件,而不用修改主配置文件;但是我们修改完还不能确定是否会有错误,可以通过命令对配置文件进行检测:
1 |
|
通过检测发现没有任何报错,就可以优雅的重启服务器了:
1 |
|
静态服务器
作为一个web服务器,最重要的就是能够对静态资源提供访问服务,我们的Nginx服务器可以用来托管一些静态的资源,比如js、css、图片等,访问某一特定的静态资源路径时会转发到本地目录文件上;那么我们就来看Nginx是如何一步一步的通过域名配置、URI配置以及目录配置来命中请求的。
server_name配置
在上面的配置中,我们主要是将server_name
设置为localhost
,但是这样仅能让局域网内的主机访问到;我们想要让广域网上的其他主机访问,可以将server_name
匹配域名,它的参数值可以是以下几种:
- 精确的域名,如www.my.com
- 通配符名称,但通配符只能用在由三段字符串组成的名称的首段或尾段,如
*.my.com或者www.my.*
- 正则表达式,使用波浪号
~
作为正则表达式字符串的开始标记,如~^www\d+\.my\.com$
- ip地址
在上面正则表达式中,^
表示以www开头,紧跟一个或多个数字(\d+),然后跟上域名my.com,最后以$
结尾;因此上面的表达式可以匹配的域名比如www1.my.com,但是www.my.com就不行。
正则表达式还支持字符串捕获功能,即将正则表达式匹配成功的名称中的一部分字符串截取出来,放在变量中供后面使用;比如将server_name进行如下设置:
1 |
|
这样,通过二级域名home.my.com到达Nginx时,被server_name
正则表达式捕获,将其中的home
字符串存入$1
变量中,我们在/usr/share/nginx/html/home
目录下的静态资源就能通过home.my.com域名来访问了;我们服务器的目录就可以是这样的:
1 |
|
这样就只需要一个server块来完成多个站点的配置。
nginx允许一个虚拟主机有多个域名,因此我们可以给server_name同时配置多个域名,多个之间以空格分隔:
1 |
|
由于server_name支持以上三种配置方式,如果出现多个server块同时匹配了相同的域名,那么这个请求交给哪个server呢?因此优先级顺序如下:
- 精确匹配server_name
- 通配符在开始时匹配server_name
- 通配符在结尾时匹配server_name
- 正则表达式匹配server_name
如果我们想让局域网内的设备访问nginx,可以将server_name
设置ip地址的方式:
1 |
|
如果还不能访问,可以查看下是否是防火墙的原因,在防火墙允许通过的应用中将Nginx勾选(没有找到Nginx可以点击允许其他应用
进行新增):
有时候我们还会见到将server_name设置为_
(下划线),意味着server_name为空,即匹配全部的主机;我们可以配置host,将a.com、b.com和c.com都指向本机,然后配置nginx:
1 |
|
这样我们不仅可以通过域名a.com、b.com、c.com来访问,也能通过ip的方式。
location配置
location用于匹配不同的URI请求,它的语法如下:
1 |
|
这里的uri
就是待匹配的请求字符串,可以是不含正则的字符串,比如/home
,称为标准URI
;也可以是包含正则的字符串,比如\.html$
(表示以.html结尾),称为正则URI
。而方括号中的四种匹配符都是可选的,用来改变请求字符串与URI的匹配方式,我们来看下四种匹配符的解释:
匹配符 | 解释 |
---|---|
不填 | location后没有参数,直接跟着标准URI,表示前缀匹配,代表跟请求中的URI从头开始匹配 |
= | 用于标准URI前,要求请求字符串与其精准匹配,成功则立即处理,nginx停止搜索其他匹配 |
^~ | 用于标准URI前,要求一旦匹配就会立即处理,不再去匹配其他正则URI,一般用来匹配目录 |
~ | 用于正则URI前,表示URI包含正则表达式,区分大小写 |
~* | 用于正则URI前,表示URI包含正则表达式,不区分大小写 |
@ | 定义一个命名的location,@定义的location名字一般用在内部定向 |
我们来看下每种匹配规则能匹配的url,首先不填代表的话表示前缀匹配,如果我们有多个相似的前缀匹配:
1 |
|
对于请求/pre/fix/home
,根据最大匹配原则,匹配第一个location。
然后是=
,要求路径完全匹配:
1 |
|
其次是^~
最佳匹配,它的优先级高于正则表达式:
1 |
|
接着是~
正则表达式匹配,它区分大小写匹配(注意:windows版本nginx不区分):
1 |
|
~*
同样也是正则匹配,只不过它不区分大小写,这里就不再演示。
如果我们的URI匹配到了多个location,其并不完全按照在配置文件中出现的顺序来进行匹配,URI会按照如下规则进行匹配:
- = 精确匹配会第一个被处理。如果发现精确匹配,nginx停止搜索其他匹配。
- 普通字符匹配,正则表达式规则和长的块规则将被优先和查询匹配,也就是说如果该项匹配还需去看有没有正则表达式匹配和更长的匹配。
- ^~ 则只匹配该规则,nginx停止搜索其他匹配,否则nginx会继续处理其他location指令。
- 最后匹配理带有
~
和~*
的指令,如果找到相应的匹配,则nginx停止搜索其他匹配;当没有正则表达式或者没有正则表达式被匹配的情况下,那么匹配程度最高的逐字匹配指令会被使用。
请求目录配置
在location匹配URI后,就需要在服务器指定的目录中寻找请求资源,而root
和alias
就是用来指定目录的两种指令,两者主要的区别在于如何解析location后面的路径;我们首先来看下root的用法,假如我们需要将/data/
下面的所有路径转发到html/roottest
下面:
1 |
|
当location接收到/data/index.html
的请求时,会在html/roottest/data/
目录下找到index.html文件并进行相应,root会将root路径和location路径进行拼接。
而alias指令则改变location接收到的请求路径,假如我们需要将/data1/
下面的所有路径转发到html/aliastest
下面:
1 |
|
当location接收到/data1/index.html
的请求时,会在html/aliastes/
目录下查找index.html文件并响应。
需要注意的是:alias指令后面的路径
必须以/结束
,否则会找不到文件,而root则可有可无。
访问权限控制
针对一些静态资源,我们可能会设置一些用户访问权限,比如和js一起打包产出的.map
文件,会对源码进行映射;但是我们想让它只能针对公司的ip进行开放,对外网的ip禁止访问,这时就需要用到allow
和deny
命令了。
假如局域网还有两个设备,我们只能让这两个设备的ip通过访问:
1 |
|
deny和allow指令是由ngx_http_access_module模块提供,Windows版本的Nginx并不包含该模块。
还可以对前端的.map文件进行访问权限控制,打包后的map文件一般会放在服务器上,但是如果能对所有人开放,别人就能查看到对应源码;因此我们可以控制只有公司的ip才有访问权限:
try_files
前端在配置路由时经常会用到history路由模式,因此后台就需要映射对应的路由到index.html;但是如果我们给每个路由都配置一个location就会比较繁琐,因此可以通过try_files
指令来进行尝试解析;try_files
的语法规则如下:
1 |
|
假设我们打包出来的单页面位于/html/my/index.html
,我们想要将/login、/regisrer等路由指向index.html,我们可以配置try_files:
1 |
|
对于多页面的应用,假设我们的页面都放在/html/pages/
目录下,我们想要访问/login
时响应/html/pages/login.html
页面,可以通过$uri
:
1 |
|
这里我们设置root目录为html/pages,当我们访问/login
路由时,这里的$uri就是/login,try_files会去尝试在根目录下找/login.html
;如果找不到就尝试/login/index.html
,最后找不到则会默认返回index.html。
gzip
我们都知道在服务端开启gzip压缩能够使得js、css、html等文件在传输时大幅提高访问速度,优化网站性能;gzip压缩后的文件大小可以变为原来的30%甚至更小;而对于图片、视频、音频等其他多媒体文件,因为压缩效果不好,所以不会开启压缩。
gzip压缩本质上是服务器端压缩,传输到浏览器后解压解析,我们来看下gzip的原理示意:
可以看到在请求和相应头上分别加了accept-encoding和content-encoding来进行传输;我们可以通过一个js的请求数据来查看:
既然gzip有这么多的好处,我们来看下nginx如何进行配置,gzip的配置可以在http块或者server块中:
1 |
|
密码控制
对于一些简单的页面,我们想要通过密码来限制其他用户的访问,但是又不想接入复杂的账号体系,Nginx提供了简单的账号密码控制;首先我们通过Linux的工具创建一个密码本存放账号密码:
1 |
|
passwd/passwd
文件就是生成的密码文件,运行后会要求连续两次输入密码,成功后为admin用户添加了密码;然后我们就修改nginx的配置文件,对站点开启密码验证:
1 |
|
重启nginx,再次访问站点就会出现需要身份验证的弹框了。
反向代理
上面我们介绍了正向代理和反向代理的区别,反向代理功能是nginx的三大主要功能之一(静态web服务器、反向代理、负载均衡)。反向代理不需要额外的模块,默认自带proxy_pass和fastcgi_pass指令,通过在location块中配置即可实现:
1 |
|
在配置proxy_pass时,我们需要注意url后面的/`;当我们通过下面几种情况访问
/proxy/home.html``时:
1 |
|
第一种情况url后面带上/,则会被代理到http://192.168.1.102:8080/home.html
。
1 |
|
第二种情况url后不带/,则会被代理到http://192.168.1.102:8080/proxy/home.html
1 |
|
第三种情况代理/doc/,则会被代理到http://192.168.1.102:8080/doc/home.html
1 |
|
第四种情况代理/doc,则会被代理到http://192.168.1.102:8080/dochome.html
在配置反向代理时,我们还可以修改代理请求的请求参数:
1 |
|
经过反向代理后,由于客户端和web服务器之间增加了一个代理层,因此web服务器无法拿到客户端请求的host和真实ip,我们通过proxy_set_header指令修改代理请求的头部;$host和$remote_addr是用户真实的host和ip,这里作为变量传入Host和X-Real-IP字段,因此我们在客户端服务器想要获取真实ip就可以通过request.getAttribute(“X-real-ip”)的方式。
负载均衡
随着互联网的发展,用户规模的增加,服务器的压力也越来越大,如果只使用一台服务器有时候不能承受流量的压力,这时我们就需要将部分流量分散到多台服务器上,使得每台服务器都均衡的承担压力。
nginx负载均衡目前支持六种策略:轮询策略、加权轮询策略、ip_hash策略、url_hash策略、fair策略和sticky策略;六种策略可以分为两大类,内置策略(轮询、加权轮询、ip_hash)和扩展策略(url_hash、fair、sticky);默认情况下内置策略自动编译在Nginx中,而扩展策略需要额外安装。
既然是负载,那么我们需要启用多台服务器;这里为了方便演示,我们在一台电脑上运行node脚本来模拟3台服务器;同时为了方便看到每台服务器有多少流量,每访问一次就计数一次:
1 |
|
然后我们修改端口号,这样我们就有8080、8081、8082三个服务器了。
轮询策略
轮询策略,顾名思义,就是按照请求顺序,逐一分配到不同的服务器节点;如果某台服务器出现问题,会自动剔除。
1 |
|
我们还是通过测试工具Apache Bench
来并发100个请求到Nginx:
1 |
|
最后统计每台服务器的结果,每台服务器的请求还是很平均的:
1 |
|
加权轮询策略
加权轮询在基本轮询策略上考虑各服务器节点接受请求的权重,指定服务器节点被轮询的权重,主要用于服务器节点性能不均的情况。
通过在server节点后配置weight来设置权重,weight的大小和访问比率成正比(weight的默认值为1);我们给三台服务器设置访问比是1:3:2
。
1 |
|
压力测试后统计服务器的请求结果,和我们配置的比率还是几乎相同的:
1 |
|
注:由于weight是内置,所以可以直接和其他策略配合使用。
ip_hash策略
ip_hash策略是将前端访问的ip进行hash操作后,然后根据hash的结果将请求分配到不同的节点上,这样使得每个ip都会固定访问服务节点;这样做的好处是用户的session只在一个后端服务器节点上,不必考虑一个session存在多台服务器节点出现session共享问题。
1 |
|
压力测试后统计服务器的请求结果,我们发现所有的请求都到固定一台服务器上了:
1 |
|
url_hash策略
url_hash策略是将url地址进行hash操作,根据hash结果请求定向到同一服务器节点上;url_hash的优点是能够提高后端缓存服务器的效率。
1 |
|
压力测试后统计服务器的请求结果:
1 |
|
如果我们切换不同的url,/home、/list等,都会分配到不同的服务器节点。
fair策略
fair策略请求转发到负载最小的后端服务器节点上。Nginx通过服务器节点对响应时间来判断负载情况,响应时间最短的节点负载就相对较轻,Nginx就会将前端请求转发到此服务器节点上。
注:fair策略默认不被编译进nginx内核,需要额外安装
1 |
|
压力测试后统计服务器的请求结果:
1 |
|
sticky策略
sticky策略是基于cookie的一种负载均衡解决方案,通过分发和识别cookie,使来自同一个客户端的请求落在同一台服务器上,默认cookie标识名为route。
sticky策略看起来和ip_hash策略类似,但是又有一定区别。假设在一个局域网内有3台电脑,他们有3个内网IP,但是他们发起请求时,却只有一个外网IP,如果使用ip_hash方式,则Nginx会将请求分配到同一服务器;如果使用sticky策略,则会把请求分配到不同服务器上,这是ip_hash无法做到的。
注:sticky策略默认不被编译进nginx内核,需要额外安装
1 |
|
sticky默认的cookie的名称是route
,我们可以通过name修改,还有一些其他的cookie参数可以进行修改:
- [name=route] 设置用来记录会话的cookie名称
- [domain=.foo.bar] 设置cookie作用的域名
- [path=/] 设置cookie作用的URL路径,默认根目录
- [expires=1h] 设置cookie的生存期,默认不设置,浏览器关闭即失效
- [hash=index|md5|sha1] 设置cookie中服务器的标识是用明文还是使用md5值,默认使用md5
- [no_fallback] 设置该项,当sticky的后端机器挂了以后,nginx返回502 (Bad Gateway or Proxy Error) ,而不转发到其他服务器,不建议设置
- [secure] 设置启用安全的cookie,需要HTTPS支持
- [httponly] 允许cookie不通过JS泄漏,没用过
我们通过浏览器来访问,在cookie中可以看到sticky下发的cookie
注:由于cookie最初由服务器端下发,如果客户端禁用cookie,则cookie不会生效。
其他参数
upstream还有一些参数我们可以配合负载均衡:
参数 | 描述 |
---|---|
fail_timeout | 与max_fails结合使用 |
max_fails | 设置在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了 |
fail_time | 服务器会被认为停机的时间长度,默认为10s。 |
backup | 标记该服务器为备用服务器。当主服务器停止时,请求会被发送到它这里。 |
down | 标记服务器永久停机了。 |
keepalive | 连接数(keepalive的值)指定了每个工作进程中保留的持续连接到nginx负载均衡器缓存的最大值。如果超过这个设置值的闲置进程想链接到nginx负载均衡器组,最先连接的将被关闭。 |
1 |
|
本网所有内容文字和图片,版权均属谢小飞所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。如需转载请关注公众号【前端壹读】后回复【转载】。