前言:
nginx作为当今火爆的、高性能的http及反向代理服务,不管前端还是后端,都需要全面去了解,学习,实操。一句话:搞懂nginx如何使用以及工作逻辑对于程序员来说是必不可少的!
我们看看本文的大纲 先了解一下本文都讲了哪些东西,大纲如下:
- nginx介绍
- nginx安装
- nginx目录一览
- nginx.conf文件解读
- location路由匹配规则
- 反向代理
- 负载均衡
- 动静分离
- 跨域
- 缓存
- 黑白名单
- nginx限流
- https配置
- 压缩
- 其他一些常用指令与说明
- 重试策略
- 最后总结
一些说明:
- 系统: centos7
- 本文使用nginx版本:nginx/1.24.0
- 关于nginx如何安装(本文不再赘述),参考之前我的一篇文章:Centos7中安装nginx
- 在学习nginx前,最好需要知道或者了解事件驱动思想以及几种常见多路复用I/O模型和Reactor模式,这样你才能从底层 更深刻的理解nginx的架构设计
1、nginx 介绍
为了有一个全面的认知,接下来我们先来看看nginx的架构以及一些特点。
1.1、nginx 特点
- 处理响应请求快(异步非阻塞I/O,零拷贝,mmap,缓存机制)
- 扩展性好(模块化设计)
- 内存消耗低(异步非阻塞,多阶段处理)
- 具有很高的可靠性(无数次的生产验证,很多头部公司都在用)
- 热部署
- 高并发连接(事件驱动模型,多进程机制)
- 自由的BSD许可协议(可以自己修改代码后发布,包容性极强)
1.2、nginx 架构
从上边这张图,我们可以一览nginx的架构设计,首先我们可以直观得出nginx的几大特点:
-
事件驱动&异步非阻塞:
本质来说,事件驱动是一种思想(事实上它不仅仅局限于编程) ,事件驱动思想是实现 异步非阻塞特性 的一个重要手段。对于web服务器来说,造成性能拉胯不支持高并发的常见原因就是由于使用了传统的I/O模型造成在
内核没有可读/可写事件(或者说没有数据可供用户进程读写)时
,用户线程一直在等待
(其他事情啥也干不了就是干等等待内核上的数据可读/可写),这样的话其实是一个线程(ps:线程在Linux系统也是进程)对应一个请求,请求是无限的,而线程是有限的从而也就形成了并发瓶颈。而大佬们为了解决此类问题,运用了事件驱动思想来对传统I/O模型做个改造,即在客户端发起请求后,用户线程不再阻塞等待内核数据就绪
,而是立即返回
(可以去执行其他业务逻辑或者继续处理其他请求)。当内核的I/O操作完成后,内核系统
会向用户线程发送一个事件通知
,用户线程才来处理这个读/写操作,之后拿到数据再做些其他业务后响应给客户端,从而完成一次客户端请求的处理。事件驱动的I/O模型中,程序不必阻塞等待I/O操作的完成,也无需为每个请求创建一个线程,从而提高了系统的并发处理能力和响应速度。事件驱动型的I/O模型通常也被被称为I/O多路复用
,即这种模型可以在一个线程中,处理多个连接(复用就是指多个连接复用一个线程,多路也即所谓的 多个连接),通过这种方式避免了线程间切换的开销,同时也使得用户线程不再被阻塞,提高了系统的性能和可靠性。nginx支持事件驱动是因为他利用了操作系统提供的I/O多路复用接口,如Linux系统中,常用的I/O多路复用接口有select/poll,epoll。这些接口可以监视多个文件描述符的状态变化,当文件描述符可读或可写时,就会向用户线程发送一个事件通知。用户线程通过事件处理机制(读取/写入数据)来处理这个事件,之后进行对应的业务逻辑完了进行响应。简单一句话概括:事件驱动机制就是指当有读/写/连接事件就绪时 再去做读/写/接受连接这些事情,而不是一直在那里傻傻的等,也正应了他的名词: 【事件驱动!】,基于事件驱动思想设计的多路复用I/O(如select/poll,epoll),相对于传统I/O模型,达到了异步非阻塞的效果!
既然提到了select/poll,epoll 那么我们就简单说一下(注意我这里是简单描述,后续有时间会对相关知识点从源码层面做个系统的整理和图解):
select: 将已连接的 Socket 都放到一个文件描述符集合,然后用户态调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。
poll: poll函数的话其实和select大差不差,唯一区别可能就是socket列表的结构有所不同,不再受FD_SETSIZE的限制。这里就不多说了。
epoll: epoll在前边两者的基础上做了很大的优化,select/poll都需要遍历整个socket列表,当检测到传入的socket可读/可写时,则copy socket列表给用户空间,用户态仍然需要遍历(因为内核copy给用户态的是整个socket列表) ,而epoll则是通过红黑树结构将需要监控的socket插入到进去,然后当有socket可读时会通过回调机制来将其添加到可读列表中,然后内核将可读列表copy给用户态即可(据说此处使用了mmap这里我们不去验证探究,后续写相关文章时在深究吧),整个过程少了无效的遍历以及不用copy整个socket集合。
-
多进程机制:
另外可以得知nginx有两种类型的进程,一种是Master主进程,一种是Worker工作进程。主进程主要负责3项工作:
加载配置
、启动工作进程
及非停升级
。另外work进程是主进程启动后,fork而来的。假设 Nginx fork了多个(具体在于你的配置)Worker进程,并且在Master进程中通过 socket 套接字监听(listen)80端口。然后每个worker进程都可以去 accept 这个监听的 socket。 当一个连接进来后,所有Worker进程,都会收到消息,但是只有一个Worker进程可以 accept 这个连接,其它的则 accept 失败,Nginx 保证只有一个Worker去accept的方式就是加锁(accept_mutex)。有了锁之后,在同一时刻,就只会有一个Worker进程去 accpet 连接,在 Worker 进程拿到 Http 请求后,就开始按照worker进程内的预置模块去处理该 Http 请求,最后返回响应结果并断开连接。其实如果熟悉reactor模型你会发现,nginx的设计有reactor的影子,只不过reactor的主reactor是会负责accept的,而nginx的主进程(对应主reactor) 是不会去accept的,而是交给了worker进程来处理。worker进程除了accept连接之外,还会执行:网络读写、存储读写、内容传输、以及请求分发等等。而其代码的模块化设计,也使得我们可以根据需要对功能模块 进行适当的选择和修改,编译成符合特定需要/业务的服务器
-
proxy cache(服务端缓存):
proxy cache主要实现 nginx 服务器对客户端数据请求的快速响应。nginx 服务器在接收到被代理服务器的响应数据之后,一方面将数据传递给客户端,另一方面根据proxy cache的配置将这些数据缓存到本地硬盘上。当客户端再次访问相同的数据时,nginx服务器直接从硬盘检索到相应的数据返回给用户,从而减少与被代理服务器交互的时间。在缓存数据时,运用了零拷贝以及mmap技术,使得数据copy性能大幅提升。
-
反向代理:
nginx的强大之处其中一个就是他的反向代理,通过反向代理,可以隐藏真正的服务,增加其安全性,同时便于统一管理处理请求,另外可以很容易的做个负载均衡,更好的面对高并发的场景。
1.3、nginx模块
nginx服务器由n多个模块组成,每个模块就是一个功能,某个模块只负责自身的功能,所以说对于
“高内聚,低耦合“
的编程规则,在nginx
身上可谓体现的淋漓尽致
。
nginx模块示意图如下:
- 核心模块 :是nginx 服务器正常运行必不可少的模块,提供错误日志记录、配置文件解析、事件驱动 机制、进程管理等核心功能
- 标准HTTP模块 :提供 HTTP 协议解析相关的功能,如:端口配置、网页编码设置、HTTP 响应头设 置等
- 可选HTTP模块 :主要用于扩展标准的 HTTP 功能,让nginx能处理一些特殊的服务,如:Flash 多 媒体传输、解析 GeoIP 请求、SSL 支持等
- 邮件服务模块 :主要用于支持 nginx 的邮件服务,包括对 POP3 协议、IMAP 协议和 SMTP 协议的支持
- 第三方模块 :是为了扩展 Nginx 服务器应用,完成开发者自定义功能,如:Json 支持、Lua 支持等
1.4、nginx常见应用场景
nginx常用场景挺多的,比如:
- 反向代理
- 负载均衡
- 缓存
- 限流
- 黑/白名单
- 静态资源服务
- 动静分离
- 防盗链
- 跨域
- 高可用
- .......
其中我认为 最最 基础的也是应用最多的就是 反向代理,这里我们画个图简单看下什么是反向代理 (ps:其他的那些使用场景,我们先不做展开,放到下边一个个哔哔。)
所谓反向代理,其实很好理解就是代理的服务端(与之对应的正向代理一般代理的是客户端),nginx反向代理如下示意:
好了介绍了这么多,想必到这里应该对nginx有个大体的了解了吧,接下来我们安装并一个一个的分析介绍nginx的知识点。
2、nginx安装
关于nginx的安装在这里不再赘述,参考我之前的一篇文章:Centos7中安装nginx
3、nginx目录一览
我们使用 tree /usr/local/nginx/ -L 2 命令查看一下nginx的目录,对其结构有个初步的认识:
[root@localhost /]# tree /usr/local/nginx/ -L 2
/usr/local/nginx/
├── conf #存放一系列配置文件的目录
│ ├── fastcgi.conf #fastcgi程序相关配置文件
│ ├── fastcgi.conf.default #fastcgi程序相关配置文件备份
│ ├── fastcgi_params #fastcgi程序参数文件
│ ├── fastcgi_params.default #fastcgi程序参数文件备份
│ ├── koi-utf #编码映射文件
│ ├── koi-win #编码映射文件
│ ├── mime.types #媒体类型控制文件
│ ├── mime.types.default#媒体类型控制文件备份
│ ├── nginx.conf #主配置文件
│ ├── nginx.conf.default#主配置文件备份
│ ├── scgi_params #scgi程序相关配置文件
│ ├── scgi_params.default #scgi程序相关配置文件备份
│ ├── uwsgi_params #uwsgi程序相关配置文件
│ ├── uwsgi_params.default#uwsgi程序相关配置文件备份
│ └── win-utf #编码映射文件
├── html #存放网页文档
│ ├── 50x.html #错误页码显示网页文件
│ └── index.html #网页的首页文件
├── logs #存放nginx的日志文件
├── nginx-1.23.0.tar.gz # 我把压缩包下载到url/local/nginx/目录了,不用管这个
├── sbin #存放启动程序
│ ├── nginx #nginx启动程序
│ └── nginx.old
└── test # 我自己建的目录,不用管这个
├── abc
└── cba
15 directories, 26 files
从输出可以看到nginx分的很清晰,有配置目录,html目录,log目录,启动程序目录。
- 关于目录的一点小说明: 上边的仅仅是nginx的主目录,事实上,生效的主配置文件一定是/usr/local/nginx/conf.conf ?这不一定,而是取决于你启动nginx时候有没有指定nginx.conf,实际使用中我发现我机器上有好几个地方都存在nginx.conf文件,使用 locate nginx.conf看一下 如下图所示: 那如何确定nginx当前生效的是哪个nginx.conf呢,很简单使用nginx -T命令即可查看当前生效的nginx.conf,如下:可以看到我当前生效的是 /etc/nginx/nginx.conf这个文件(我是使用的 systemctl start nginx.service命令启动的,未指定用哪个文件启动,所以可以看出默认使用的是 /etc/nginx/nginx.conf这个配置文件) 另外还有一个就是nginx的日志,我发现我的nginx日志就不是在 /usr/local/nginx/logs/这个目录下,而是放到了/var/log/nginx/ 这个目录下了(ps:log文件的存放和我的nginx.conf文件中的 access_log配置有关系)。如下演示:
好了在了解了nginx整体的目录结构后,就来看看 nginx.conf 这个文件这个文件是nginx的核心配置,想玩转nginx,读懂这个配置文件是必不可少的一项基本功!
4、nginx.conf文件 解读
首先我们要知道nginx.conf文件是由一个一个的指令块组成的
,nginx用{}标识一个指令块,指令块中再设置具体的指令(注意 指令必须以 ; 号结尾),指令块有全局块
,events块
,http块
,server块
和location块 以及 upstream块
。精简后的结构如下:
全局模块
event模块
http模块
upstream模块
server模块
location块
location块
....
server模块
location块
location块
...
....
各模块的功能作用如下描述:
- 全局模块: 配置影响nginx全局的指令,比如运行nginx的用户名,nginx进程pid存放路径,日志存放路径,配置文件引入,worker进程数等。
- events块: 配置影响nginx服务器或与用户的网络连接。比如每个进程的最大连接数,选取哪种事件驱动模型(select/poll epoll或者是其他等等nginx支持的)来处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
- http块: 可以嵌套多个server,配置代理,缓存,日志格式定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
- server块: 配置虚拟主机的相关参数比如域名端口等等,一个http中可以有多个server。
- location块: 配置url路由规则
- upstream块: 配置上游服务器的地址以及负载均衡策略和重试策略等等
下面看下nginx.conf长啥样并对一些指令做个解释:
# 注意:有些指令是可以在不同指令块使用的(需要时可以去官网看看对应指令的作用域)。我这里只是演示
# 这里我以/usr/local/nginx/conf/nginx.conf文件为例
[root@localhost /usr/local/nginx]# cat /usr/local/nginx/conf/nginx.conf
#user nobody; # 指定Nginx Worker进程运行用户以及用户组,默认由nobody账号运行
worker_processes 1; # 指定工作进程的个数,默认是1个。具体可以根据服务器cpu数量进行设置, 比如cpu有4个,可以设置为4。如果不知道cpu的数量,可以设置为auto。 nginx会自动判断服务器的cpu个数,并设置相应的进程数
#error_log logs/error.log; # 用来定义全局错误日志文件输出路径,这个设置也可以放入http块,server块,日志输出级别有debug、info、notice、warn、error、crit可供选择,其中,debug输出日志最为最详细,而crit输出日志最少。
#error_log logs/error.log notice;
#error_log logs/error.log info; # 指定error日志位置和日志级别
#pid logs/nginx.pid; # 用来指定进程pid的存储文件位置
events {
accept_mutex on; # 设置网路连接序列化,防止惊群现象发生,默认为on
# Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll,其中select和poll都是标准的工作模式,kqueue和epoll是高效的工作模式,不同的是epoll用在Linux平台上,而kqueue用在BSD系统中,对于Linux系统,epoll工作模式是首选
use epoll;
# 用于定义Nginx每个工作进程的最大连接数,默认是1024。最大客户端连接数由worker_processes和worker_connections决定,即Max_client=worker_processes*worker_connections在作为反向代理时,max_clients变为:max_clients = worker_processes *worker_connections/4。进程的最大连接数受Linux系统进程的最大打开文件数限制,在执行操作系统命令“ulimit -n 65536”后worker_connections的设置才能生效
worker_connections 1024;
}
# 对HTTP服务器相关属性的配置如下
http {
include mime.types; # 引入文件类型映射文件
default_type application/octet-stream; # 如果没有找到指定的文件类型映射 使用默认配置
# 设置日志打印格式
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#
#access_log logs/access.log main; # 设置日志输出路径以及 日志级别
sendfile on; # 开启零拷贝 省去了内核到用户态的两次copy故在文件传输时性能会有很大提升
#tcp_nopush on; # 数据包会累计到一定大小之后才会发送,减小了额外开销,提高网络效率
keepalive_timeout 65; # 设置nginx服务器与客户端会话的超时时间。超过这个时间之后服务器会关闭该连接,客户端再次发起请求,则需要再次进行三次握手。
#gzip on; # 开启压缩功能,减少文件传输大小,节省带宽。
sendfile_max_chunk 100k; #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。
# 配置你的上游服务(即被nginx代理的后端服务)的ip和端口/域名
upstream backend_server {
server 172.30.128.65:8080;
server 172.30.128.65:8081 backup; #备机
}
server {
listen 80; #nginx服务器监听的端口
server_name localhost; #监听的地址 nginx服务器域名/ip 多个使用英文逗号分割
#access_log logs/host.access.log main; # 设置日志输出路径以及 级别,会覆盖http指令块的access_log配置
# location用于定义请求匹配规则。 以下是实际使用中常见的3中配置(即分为:首页,静态,动态三种)
# 第一种:直接匹配网站根目录,通过域名访问网站首页比较频繁,使用这个会加速处理,一般这个规则配成网站首页,假设此时我们的网站首页文件就是: usr/local/nginx/html/index.html
location = / {
root html; # 静态资源文件的根目录 比如我的是 /usr/local/nginx/html/
index index.html index.htm; # 静态资源文件名称 比如:网站首页html文件
}
# 第二种:静态资源匹配(静态文件修改少访问频繁,可以直接放到nginx或者统一放到文件服务器,减少后端服务的压力),假设把静态文件我们这里放到了 usr/local/nginx/webroot/static/目录下
location ^~ /static/ {
alias /webroot/static/; 访问 ip:80/static/xxx.jpg后,将会去获取/url/local/nginx/webroot/static/xxx.jpg 文件并响应
}
# 第二种的另外一种方式:拦截所有 后缀名是gif,jpg,jpeg,png,css.js,ico这些 类静态的的请求,让他们都去直接访问静态文件目录即可
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
root /webroot/static/;
}
# 第三种:用来拦截非首页、非静态资源的动态数据请求,并转发到后端应用服务器
location / {
proxy_pass http://backend_server; #请求转向 upstream是backend_server 指令块所定义的服务器列表
deny 192.168.3.29; #拒绝的ip (黑名单)
allow 192.168.5.10; #允许的ip(白名单)
}
# 定义错误返回的页面,凡是状态码是 500 502 503 504 总之50开头的都会返回这个 根目录下html文件夹下的50x.html文件内容
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
# 其余的server配置 ,如果有需要的话
#server {
......
# location / {
....
# }
#}
# include /etc/nginx/conf.d/*.conf; # 一般我们实际使用中有很多配置,通常的做法并不是将其直接写到nginx.conf文件,
# 而是写到新文件 然后使用include指令 将其引入到nginx.conf即可,这样使得主配置nginx.conf文件更加清晰。
}
以上就是nginx.conf文件的配置了,主要讲了一些指令的含义,当然实际的指令有很多,我在配置文件并没有全部写出来,准备放到后边章节详细阐述这些东西,比如:location匹配规则,反向代理,动静分离,负载均衡策略,重试策略,压缩,https,限流,缓存,跨域这些 我们都没细说,这些东西比较多比较细不可能把使用规则和细节都写到上边的配置文件中,所以我们下边一一解释说明关于这些东西的配置和使用方式。(另外值的注意的是: 因为有些指令是可以在不同作用域使用的,如果在多个作用域都有相同指令的使用,那么nginx将会遵循就近原则或者我愿称之为 内层配置优先。 eg: 你在 http配了日志级别,也在某个server中配了日志级别,那么这个server将使用他自己配置的已不使用外层的http日志配置)
5、location 路由匹配规则
什么是location? : nginx根据用户请求的URI来匹配对应的location模块,匹配到哪个location,请求将被哪个location块中的配置项所处理。
location配置语法:location [修饰符] pattern {…}
常见匹配规则如下:
修饰符 | 作用 |
---|---|
空 | 无修饰符的前缀匹配,匹配前缀是 你配置的(比如说你配的是 /aaa) 的url |
= | 精确匹配 |
~ | 正则表达式模式匹配,区分大小写 |
~* | 正则表达式模式匹配,不区分大小写 |
^~ | ^~类型的前缀匹配,类似于无修饰符前缀匹配,不同的是,如果匹配到了,那么就停止后续匹配 |
/ | 通用匹配,任何请求都会匹配到(只要你域名对,所有请求通吃!) |
5.1、前缀匹配(无修饰符)
首先我提前创建了prefix_match.html文件,之后改一下nginx.conf文件(给前缀是 /prefixmatch 的请求返回 /etc/nginx/locatest/prefix_match.html 这个文件) ,如下:
然后在宿主机hosts中配置域名 172.30.128.65 www.locatest.com 映射后,观察到nginx服务器返回内容如下:
curl http://www.locatest.com/prefixmatch ✅ 301
curl http://www.locatest.com/prefixmatch? ✅ 301
curl http://www.locatest.com/PREFIXMATCH ❌ 404
curl http://www.locatest.com/prefixmatch/ ✅ 200
curl http://www.locatest.com/prefixmatchmmm ❌ 404
curl http://www.locatest.com/prefixmatch/mmm ❌ 404
curl http://www.locatest.com/aaa/prefixmatch/❌ 404
可以看到 域名/prefixmatch
和域名/prefixmatch?
返回了301 ,原因在于prefixmatch映射的 /etc/nginx/locatest/ 是个目录,而不是个文件所以nginx提示我们301,这个我们不用管没关系,总之我们知道:域名/prefixmatch
,域名/prefixmatch?
和域名/prefixmatch/
这三个url通过我们配置的 无修饰符前缀匹配规则 都能匹配上就行了。
ps:为了方便,我们下边的几个location规则演示不再跳转静态文件了,而是直接return一句话。
5.2、精确匹配( = )
为了演示精确匹配,我们再给nginx.conf文件增加一个location配置,如下标红处:
实际效果如下:
http://www.locatest.com/exactmatch ✅ 200
http://www.locatest.com/exactmatch? ✅ 200
http://www.locatest.com/exactmatch/ ❌ 404
http://www.locatest.com/exactmatchmmmm ❌ 404
http://www.locatest.com/EXACTMATCH ❌ 404
可以看出来精确匹配就是精确匹配,差一个字也不行!
5.3、前缀匹配( ^~ )
我们上边说了不带任何修饰符的前缀匹配(5.1小节),这里我们看下 修饰符是 ^~的 前缀匹配和不带修饰符的前缀匹配有啥区别,先在ngnx.conf文件增加个location并配好如下: curl效果如下:
curl http://www.locatest.com/exactprefixmatch ✅ 200
curl http://www.locatest.com/exactprefixmatch/ ✅ 200
curl http://www.locatest.com/exactprefixmatch? ✅ 200
curl http://www.locatest.com/exactprefixmatchmmm ✅ 200
curl http://www.locatest.com/exactprefixmatch/mmm ✅ 200
curl http://www.locatest.com/aaa/exactprefixmatch ❌ 404
curl http://www.locatest.com/EXACTPREFIXMATCH ❌ 404
可以看到带修饰符(^~
)的前缀匹配 像:域名/exactprefixmatchmmm
和域名/exactprefixmatch/mmm
是可以匹配上的,而不带修饰符的前缀匹配这两个类型的url是匹配不上的直接返回了404 ,其他的和不带修饰符的前缀匹配似乎都差不多。
5.4、正则匹配(~ 区分大小写)
ps:正则表达式的匹配,需要你对正则语法比较熟悉,熟悉语法后写匹配规则也就得心应手了。
添加个location并配置,如下:( ^表示开头,$表示结尾) 实际效果如下:
curl http://www.locatest.com/regexmatch ✅ 200
curl http://www.locatest.com/regexmatch/ ❌ 404
curl http://www.locatest.com/regexmatch? ✅ 200
curl http://www.locatest.com/regexmatchmmm ❌ 404
curl http://www.locatest.com/regexmatch/mmm ❌ 404
curl http://www.locatest.com/REGEXMATCH ❌ 404
curl http://www.locatest.com/aaa/regexmatch ❌ 404
curl http://www.locatest.com/bbbregexmatch ❌ 404
可以看到~修饰的正则是区分大小写的。接下来我们看下 不区分大小写的匹配。
5.5、正则匹配(~* 不区分大小写)
改下location 在修饰符~后加个 * 看下实际效果: 可以看到这次 curl www.locatest.com/REGEXMATCH 是可以匹配上的,说明 ~* 确实是不区分大小写的。
5.6、通用匹配( / )
通用匹配使用一个 / 表示,可以匹配所有请求,一般nginx配置文件最后都会有一个通用匹配规则,当其他匹配规则均失效时,请求会被路由给通用匹配规则处理,如果没有配置通用匹配,并且其他所有匹配规则均失效时,nginx会返回404错误。 通用匹配实际效果: 可以看到通用匹配很好理解,只要你域名写对了,那么所有的url都会被匹配上,来者不拒的感觉。
5.7、关于location 匹配优先级
上边我们说了6种location匹配规则,那么如果存在多个到底走哪个location呢?这就的说说location的匹配优先级了。先来看下nginx官网和stackoverflow上的资料如下: 综上资料我们对location匹配优先级的总结如下:
- 优先走
精确匹配
,精确匹配命中时,直接走对应的location,停止之后的匹配动作。 无修饰符类型的前缀匹配
和^~ 类型的前缀匹配
命中时,收集命中的匹配,对比出最长的那一条并存起来(最长指的是与请求url匹配度最高的那个location)。如果
步骤2中最长的那一条匹配是^~类型的前缀匹配
,直接走此条匹配对应的location并停止
后续匹配动作;如果步骤2最长的那一条匹配
不是^~类型的前缀匹配(也就是无修饰符的前缀匹配
),则继续往下
匹配- 按location的声明顺序,执行正则匹配,当找到第一个命中的正则location时,停止后续匹配。
- 都没匹配到,走通用匹配( / )(如果有配置的话),如果没配置通用匹配的话,上边也都没匹配上,到这里就是404了。
如果非要给修饰符排个序的话就是酱样子:
=
> ^~
> 正则
> 无修饰符的前缀匹配
> /
ok关于location就到这里,location是一个很重要的点,学好这个才知道nginx到底是咋匹配url的。
6、反向代理
反向代理示意图我们上边说过,这里再次粘一下:
接下来我们开始用一个小demo来演示反向代理的使用
6.1、服务准备
首先将我本地的一个服务打成 胖jar: 然后使用java -jar方式启动服务,且指定端口为8081:
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8081
最后使用postman测试接口是否正常(注意此时还没被nginx代理,而是直接调的服务): 启动服务并验证接口无误后,接下来我们修改nginx配置文件。让nginx反向代理我们的服务。
6.2、修改nginx.conf文件
要让 nginx 代理
我们的 服务
很简单,简单描述一下就是 两步:
- 通过upstream指令块来定义我们的上游服务(即被代理的服务)
- 通过location指令块中的 proxy_pass指令,指定该location要路由到哪个upstream
配置好1和2后,如果来了请求后 会通过url路由到对应的location, 然后nginx会将请求打到upstream定义的服务地址中去,下边我们看看:
使用 vi /etc/nginx/nginx.conf命令修改nginx.conf文件,如下:
(注意上边的 proxy_pass http://mybackendserver/ 后边这个斜线加和不加区别挺大的,加的话不会拼接/backend
, 而不加的话会拼接 /backend
,这一点我们在15.5.3小节会讲到,这里留意下就好)
6.3、测试反向代理
修改完后我们执行 nginx -s reload 命令重新加载nginx配置,然后再potsman中调用一下,如下:
ps:在第一次调用中出现了一个错误: failed (13: Permission denied) while connecting to upstream: 解决办法很简单在虚拟机执行命令:setenforce 0 来关闭
seLinux的限制
即可,或者参考:stackoverflow上的解决方案
解决后再次调用发现可以了:(注意 www.proxytest.com 是我随便写的域名没做dns解析,我是在请求发出方 宿主机 配了host,所以才能请求通) 同时也可使用命令 tail -n +1 -f access.log 观察到nginx日志输出如下:
6.4、反向代理流程与原理
对于上边演示的反向代理案例的流程与原理,我们来个示意图如下:(这个图比较重要)
接下来我们演示下负载均衡。
7、负载均衡
说到负载均衡很多人应该并不陌生,总而言之负载均衡就是:避免高并发高流量时请求都聚集到某一个服务或者某几个服务上,而是让其均匀分配(或者能者多劳),从而减少高并发带来的系统压力,从而让服务更稳定。对于nginx来说,负载均衡就是从 upstream
模块定义的后端服务器列表中按照配置的负载策略选取一台服务器接受用户的请求。
7.1、准备3个不同端口的springboot服务
想要演示负载均衡,我们首先得多搞几个服务,搞一个服务是没法儿演示的。所以我启动了3个不同端口(8081,8082,8083)的springboot服务,如下:
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8081
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8082
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8083
使用 command+D 对iterm2进行分屏 ,最左侧是8081端口,中间是8082,右侧是8083: 接下来我们说一下负载策略再开始。
7.2、nginx常用的负载策略:
负载策略 | 描述 | 特点 |
---|---|---|
轮询 | 默认方式 | 1. 每个请求会按时间顺序逐一分配到不同的后端服务器 2. 在轮询中,如果服务器down掉了,会自动剔除该服务器 3. 缺省配置就是轮询策略 4. 此策略适合服务器配置相当,无状态且短平快的服务使用 |
weight | 权重方式 | 1. 在轮询策略的基础上指定轮询的几率 2. 权重越高分配到的请求越多 3. 此策略可以与least_conn和ip_hash结合使用 4. 此策略比较适合服务器的硬件配置差别比较大的情况 |
ip_hash | 依据ip的hash值来分配 | 1. 在nginx版本1.3.1之前,不能在ip_hash中使用权重(weight) 2. ip_hash不能与backup同时使用 3. 此策略适合有状态服务,比如session 4. 当有服务器需要剔除,必须手动down掉 |
least_conn | 最少连接方式 | 1. 此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况 |
fair(第三方) | 响应时间方式 | 1. 根据后端服务器的响应时间来分配请求,响应时间短的优先分配 2. Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块 |
url_hash(第三方) | 依据URL分配方式 | 1. 按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器 2. Nginx本身不支持url_hash,如果需要这种调度算法,则必须安装Nginx的hash软件包 |
7.2.1、轮询
轮询策略是默认的,所以只需要如下这样修改配置文件就可以了: 重启nginx后观察一下: 以上图片可以看到,是按upstream中的先后顺序来进行轮询的。
7.2.2、weight
weight指令用于指定轮询机率,weight的默认值为1,weight的数值与访问比率成正比。 接下来我们指定8082端口的服务的weight=2,如下: 看下权重策略下的请求结果: 可以看到在一轮轮询中,8081命中1次,8082由于配置了 weight=2所以命中了2次,8083命中了1次。即配置了weight=2的8082服务,命中几率是8081或者8083的两倍
7.2.3、ip_hash
设定ip哈希很简单,就是在你的upstream中 指定 ip_hash;
即可,如下:
重启nginx后看下效果:
可以看到,由于我的访问ip总是固定的宿主机的172.30.128.64 根据hash算法我的ip被匹配给了8083端口的服务,所以只要我不换ip 不管我请求多少次,请求都是被 转发到了8083的服务上了。
7.2.4、least_conn
同ip_hash一样,设定最小连接数策略也很简单,就是在你的upstream中 指定 least_conn;
即可,如下:
由于我这里最小连接数看不出啥效果,所以就不演示截图了,知道怎么配置最小连接数即可。关于第三方的负载策略。不做过多说明了,可以看看:nginx官方文档 获取其他的网上资料。
8、动静分离
在说动静分离前,我们要知道为何要做动静分离以及他能解决啥问题,首先,我们常见的web系统中会有大量的静态资源文件比如掘金主页面刷新后的f12如下: 可以看到有很多静态资源,如果将这些资源都搞到后端服务的话,将会提高后端服务的压力且占用带宽增加了系统负载(要知道,静态资源的访问频率其实蛮高的)所以为了避免该类问题我们可以把不常修改的静态资源文件放到nginx的静态资源目录中去,这样在访问静态资源时直接读取nginx服务器本地文件目录之后返回,这样就大大减少了后端服务的压力同时也加快了静态资源的访问速度,何为静,何为动呢?:
- 静: 将不常修改且访问频繁的静态文件,放到nginx本地静态目录(当然也可以搞个静态资源服务器专门存放所有静态文件)
- 动: 将变动频繁/实时性较高的比如后端接口,实时转发到对应的后台服务
接下来我们将构造一个html页面,然后点击按钮后发送get请求到后端接口。流程如下:
8.1、准备工作
首先我们搞个html(请原谅我这粗糙的前端代码