前言:
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 服务器正常运行必不可少的模块,提供错误日志记录、配置文件解析、事件驱动 机制、进程管理等核心功能
- 标准HTTP模块 :提供 HTTP 协议解析相关的功能,如:端口配置、网页编码设置、HTTP 响应头设 置等
- 可选HTTP模块 :主要用于扩展标准的 HTTP 功能,让nginx能处理一些特殊的服务,如:Flash 多 媒体传输、解析 GeoIP 请求、SSL 支持等
- 邮件服务模块 :主要用于支持 nginx 的邮件服务,包括对 POP3 协议、IMAP 协议和 SMTP 协议的支持
- 第三方模块 :是为了扩展 Nginx 服务器应用,完成开发者自定义功能,如:Json 支持、Lua 支持等
1.4、nginx常见应用场景
nginx常用场景挺多的,比如:
- 反向代理
- 负载均衡
- 缓存
- 限流
- 黑/白名单
- 静态资源服务
- 动静分离
- 防盗链
- 跨域
- 高可用
- .......
其中我认为 最最 基础的也是应用最多的就是 反向代理,这里我们画个图简单看下什么是反向代理 (ps:其他的那些使用场景,我们先不做展开,放到下边一个个哔哔。)
所谓反向代理,其实很好理解就是代理的服务端(与之对应的正向代理一般代理的是客户端),nginx反向代理如下示意:
那如何确定nginx当前生效的是哪个nginx.conf呢,很简单使用nginx -T命令即可查看当前生效的nginx.conf,如下:可以看到我当前生效的是 /etc/nginx/nginx.conf这个文件(我是使用的 systemctl start nginx.service命令启动的,未指定用哪个文件启动,所以可以看出默认使用的是 /etc/nginx/nginx.conf这个配置文件)好了在了解了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 这个文件) ,如下:
curl http://www.locatest.com/prefixmatch ✅ 301curl http://www.locatest.com/prefixmatch? ✅ 301curl http://www.locatest.com/PREFIXMATCH ❌ 404curl http://www.locatest.com/prefixmatch/ ✅ 200curl http://www.locatest.com/prefixmatchmmm ❌ 404curl http://www.locatest.com/prefixmatch/mmm ❌ 404curl 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 ✅ 200http://www.locatest.com/exactmatch? ✅ 200http://www.locatest.com/exactmatch/ ❌ 404http://www.locatest.com/exactmatchmmmm ❌ 404http://www.locatest.com/EXACTMATCH ❌ 404
可以看出来精确匹配就是精确匹配,差一个字也不行!
5.3、前缀匹配( ^~ )
我们上边说了不带任何修饰符的前缀匹配(5.1小节),这里我们看下 修饰符是 ^~的 前缀匹配和不带修饰符的前缀匹配有啥区别,先在ngnx.conf文件增加个location并配好如下:
curl http://www.locatest.com/exactprefixmatch ✅ 200curl http://www.locatest.com/exactprefixmatch/ ✅ 200curl http://www.locatest.com/exactprefixmatch? ✅ 200curl http://www.locatest.com/exactprefixmatchmmm ✅ 200curl http://www.locatest.com/exactprefixmatch/mmm ✅ 200curl http://www.locatest.com/aaa/exactprefixmatch ❌ 404curl http://www.locatest.com/EXACTPREFIXMATCH ❌ 404
可以看到带修饰符(^~
)的前缀匹配 像:域名/exactprefixmatchmmm
和域名/exactprefixmatch/mmm
是可以匹配上的,而不带修饰符的前缀匹配这两个类型的url是匹配不上的直接返回了404 ,其他的和不带修饰符的前缀匹配似乎都差不多。
5.4、正则匹配(~ 区分大小写)
ps:正则表达式的匹配,需要你对正则语法比较熟悉,熟悉语法后写匹配规则也就得心应手了。
添加个location并配置,如下:( ^表示开头,$表示结尾)
curl http://www.locatest.com/regexmatch ✅ 200curl http://www.locatest.com/regexmatch/ ❌ 404curl http://www.locatest.com/regexmatch? ✅ 200curl http://www.locatest.com/regexmatchmmm ❌ 404curl http://www.locatest.com/regexmatch/mmm ❌ 404curl http://www.locatest.com/REGEXMATCH ❌ 404curl http://www.locatest.com/aaa/regexmatch ❌ 404curl 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、反向代理
反向代理示意图我们上边说过,这里再次粘一下:
然后使用java -jar方式启动服务,且指定端口为8081:
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8081
启动服务并验证接口无误后,接下来我们修改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文件,如下:
解决办法很简单在虚拟机执行命令:setenforce 0 来关闭seLinux的限制
即可,或者参考:stackoverflow上的解决方案
解决后再次调用发现可以了:(注意 www.proxytest.com 是我随便写的域名没做dns解析,我是在请求发出方 宿主机 配了host,所以才能请求通)
6.4、反向代理流程与原理
对于上边演示的反向代理案例的流程与原理,我们来个示意图如下:(这个图比较重要)
接下来我们说一下负载策略再开始。
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、轮询
轮询策略是默认的,所以只需要如下这样修改配置文件就可以了:
以上图片可以看到,是按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;
即可,如下:
可以看到,由于我的访问ip总是固定的宿主机的172.30.128.64 根据hash算法我的ip被匹配给了8083端口的服务,所以只要我不换ip 不管我请求多少次,请求都是被 转发到了8083的服务上了。
7.2.4、least_conn
同ip_hash一样,设定最小连接数策略也很简单,就是在你的upstream中 指定 least_conn;
即可,如下:
可以看到有很多静态资源,如果将这些资源都搞到后端服务的话,将会提高后端服务的压力且占用带宽增加了系统负载(要知道,静态资源的访问频率其实蛮高的)所以为了避免该类问题我们可以把不常修改的静态资源文件放到nginx的静态资源目录中去,这样在访问静态资源时直接读取nginx服务器本地文件目录之后返回,这样就大大减少了后端服务的压力同时也加快了静态资源的访问速度,何为静,何为动呢?:
- 静: 将不常修改且访问频繁的静态文件,放到nginx本地静态目录(当然也可以搞个静态资源服务器专门存放所有静态文件)
- 动: 将变动频繁/实时性较高的比如后端接口,实时转发到对应的后台服务
接下来我们将构造一个html页面,然后点击按钮后发送get请求到后端接口。流程如下:
之后使用
scp /Users/hzz/fsdownload/index_page.html root@172.30.128.65:/usr/local/nginx/test/static
命令将index_page.html 文件上传到虚拟机。
8.2、修改nginx.conf文件
首先我们配置俩location规则,一个( /frontend )是读取静态文件,一个(/backend)是转发到 我们配置的upstream服务中去。如下:
接着我们输入要查询的人名,之后点击 "调用get接口" 按钮,如下:
查看nginx访问日志可以看到两次请求的输出:
之后我修改index_page中的后端地址:
可以看到浏览器提示我们受同源规则影响我们不能跨域访问资源。造成的原因是我的两个域名解析出来的端口不一致 一个是80一个是90。不符合同源策略,所以必然会有跨域报错。
9.3、nginx解决跨域
首先想解决跨越就得避免不同源,而我们可不可以 把对后端的代理 放在前端的server中呢(也就是说让前后端统一使用一个端口,一个server_name)?答案是可以的,因为server支持多个location配置呀(一个location处理前端,一个location转发后端),我们改下配置文件试一把如下:
上边/page请求返回了html页面之后我们输入参数点击“调用get接口”查看到后端接口的调用如下:
从上边可以看到,我上边设想的方式是可行的。
- 当然有些资料上有说使用 设置header的方式解决跨域,但是在实际测试中,设置header的方式始终没解决跨域,试了好久也没解决掉😂😂,有试过此方式解决的大佬帮忙看看我这是哪里配错了还是咋的在此提前感谢了。
10.2、nginx缓存使用与效果演示
首先我们想要的效果是: 将 url+参数一样 的请求的结果,缓存到nginx。
接下来我们修改下nginx.conf文件,如下:
http{ ... # 指定缓存存放目录为/usr/local/nginx/test/nginx_cache_storage,并设置缓存名称为mycache,大小为64m, 1天未被访问过的缓存将自动清除,磁盘中缓存的最大容量为1gb proxy_cache_path /usr/local/nginx/test/nginx_cache_storage levels=1:2 keys_zone=mycache:64m inactive=1d max_size=1g; ... server{ ... # 指定 username 参数中只要有字母 就不走nginx缓存 if ($arg_username ~ [a-z]) { set $cache_name "no cache"; } location /interface { proxy_pass http://mybackendserver/; # 使用名为 mycache 的缓存空间 proxy_cache mycache; # 对于200 206 状态码的数据缓存2分钟 proxy_cache_valid 200 206 1m; # 定义生成缓存键的规则(请求的url+参数作为缓存key) proxy_cache_key $host$uri$is_args$args; # 资源至少被重复访问2次后再加入缓存 proxy_cache_min_uses 3; # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取 proxy_cache_lock on; # 上面的锁 超时时间为4s,超过4s未获取数据,其他请求直接去后端 proxy_cache_lock_timeout 4s; # 对于请求参数中有字母的 不走nginx缓存 proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存 # 在响应头中添加一个缓存是否命中的状态(便于调试) add_header Cache-status $upstream_cache_status; } ...}
接下来我们
分别查询 张无忌 和 hzznb
来看看缓存命中情况:查询usernam=
张无忌;
第一次(未命中,因为此 url+参数 之前没请求过缓存中确实没有):第四次(命中):
第2,3次也都是miss 即未命中(截图略)
第四次:(仍然是未命中,说明我们在nginx.conf中配置的规则:”参数中带字母则不缓存“ 生效了!):
可以看到ip 可以是ipv4 也可以是ipv6 也可以按照网段来配置,当然ip黑白配置可以在 http,server,location和limit_except这几个域都可以区别只是作用粒度大小问题。当然nginx建议我们使用 ngx_http_geo_module这个库,ngx_http_geo_module库支持 按地区、国家进行屏蔽,并且提供了IP库,当需要配置的名单比较多或者根据地区国家屏蔽时这个库可以帮上大忙。
下面我们配置并演示一下:
11.2、黑白名单演示
允许任何ip访问前端,然后禁止172.30.128.64访问后端,nginx.conf文件如下:
访问后端,走interface这个location(显示403被禁止了):
我们下面使用 ngx_http_limit_req_module 模块中的limit_req_zone和 limit_req 这两个指令来达到限制单个IP的请求速率 的目的。
12.1、nginx限流配置解释
在 nginx.conf 中添加限流配置如下:
http{ ... # 对请求速率限流 limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=5r/s; server{ location /interface{ ... limit_req zone=myRateLimit burst=5 nodelay; limit_req_status 520; limit_req_log_level info; } }}
上边的配置意味着每秒最多处理5次同样ip的请求,我们使用jmeter设置1个线程循环10次,间隔时间为100ms,效果如下(5成功5失败):
运行jemeter发现间隔200ms访问一次的请求都成功了:
之后我们配置线程数量为15,每隔100ms掉一次,效果如下:
12.2.3、打开nodelay
我们上边说过打开nodlay的话,代表放到burst队列的请求直接处理 ,不再按速率 200ms/次 拿了,配置如下:
可以很明显的看到:开启nodelay后响应时间10几秒明显比不开启nodelay快很多,但是请求成功的还是6个,因为就像我们上边说的ngdelay虽然会即时处理,但是释放坑位是200ms释放一个
(也就是说即时开启了nodelay 但释放令牌的速度是不变的)
,所以nodelay参数本质上并没有提高访问速率,而仅仅是让处于burst队列的请求”被快速处理“
罢了。12.3、nginx限流(针对连接数量)
针对连接数量的限流和速率不一样,即使你速率是1ms一次,只要你连接数量不超过设置的,那么也访问成功。如果连接数超过设置的值将会请求失败。值得注意的是他是 ngx_http_limit_conn_module模块中的,不要和 速率限流的 ngx_http_limit_req_module模块搞混了。
配置如下:
http{ # 针对ip 对请求连接数限流 ... limit_conn_zone $binary_remote_addr zone=myConnLimit:10m; ... server{ ... limit_conn myConnLimit 12; }}
可以看到由于我们配置的并发数是12,所以20个连接中有8个都被限了。这个理解起来似乎比速率限流(ngx_http_limit_req_module)简单些我们就不过多解释了。
13、https配置
说到https大家应该并不陌生,我这里不啰嗦介绍了。一般我们安装的nginx模块都是不包含ssl模块的,所以需要手动安装下。安装完之后我们再说如何配置https。
13.1、https_ssl模块安装
首先我们使用
nginx -V
(大写) 看下有没有安装https_ssl模块:下边的安装https_ssl仅仅是为了内容全面,而不是真正使用1.23.0版本的nginx或者1.23.0版本的https_ssl模块(本文使用的版本都是1.24.0 ,1.23.0是我之前的一个nginx版本)。
没有ssl模块情况下,首先我们
进入nginx解压目录
:我的是 /usr/local/nginx/nginx-1.23.0/ 总之就是找到你的nginx解压目录) ,之后在该目录执行命令./configure --prefix=/usr/local/nginx --with-http_ssl_module
来安装ssl模块,如下图:13.2、域名购买&解析&ssl证书申请与验证
要配置ssl最好是有个域名,所以我花一杯酱香拿铁的💰买了一个域名(在买域名前需要进行域名模板实名,具体操作去腾讯云官网看这里不啰嗦了),我买的域名是:
hzznb-xzll.xyz
ps: 如果我没记错的话这是我的第一个域名,虽然他10块钱但是我很珍惜他😄😄,接下来可以在我的域名中看到已经成功了。
配置好解析后,我们开始申请一个免费的ssl证书并将其和我上边的域名绑定,如下:
之后我们在我的证书看到已经完成验证,此时就可以下载ssl证书,然后配置我们的nginx了:
13.3、上传并配置nginx以及演示
下载到本地后是个zip我们解压之后会看到里边有4个文件分别是:
hzznb-xzll.xyz_bundle.crt 证书文件
hzznb-xzll.xyz_bundle.pem 证书文件(可忽略该文件)
hzznb-xzll.xyz.key 私钥文件
hzznb-xzll.xyz.csr CSR 文件 (CSR 文件是申请证书时由您上传或系统在线生成的,提供给 CA 机构。安装时可忽略该文件。)之后我们仅需要把 hzznb-xzll.xyz.key 和 hzznb-xzll.xyz_bundle.crt 这俩货上传到我新建的 certificate文件夹:/usr/local/nginx/certificate/ ,操作如下:
ssl证书准备好后,我们需要配置一个https的server(如下配置:)
下边的指令名称都有注释说明了各个指令是干啥的,我也就不啰嗦了:# --------------------HTTPS 配置--------------------- server { #SSL 默认访问端口号为 443 listen 443 ssl; #填写绑定证书的域名 server_name www.hzznb-xzll.xyz hzznb-xzll.xyz; #请填写证书文件的相对路径或绝对路径 ssl_certificate /usr/local/nginx/certificate/hzznb-xzll.xyz_bundle.crt; #请填写私钥文件的相对路径或绝对路径 ssl_certificate_key /usr/local/nginx/certificate/hzznb-xzll.xyz.key; #停止通信时,加密会话的有效期,在该时间段内不需要重新交换密钥 ssl_session_timeout 5m; #服务器支持的TLS版本 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; #开启由服务器决定采用的密码套件 ssl_prefer_server_ciphers on; }
可以看到我们上传到 /usr/local/nginx/certificate/目录下的 .crt和.key文件被使用到了。
现在我们仅仅是配好一个https类型的server,光一个server没法访问也没意思,我们需要让给他配置上游服务以及路由规则,这里我们直接使用我们上边的 80端口那个server中的location配置,直接copy过来,https server的配置 如下:
之后我们将index_page.html上传到nginx的 /usr/local/nginx/test/static/ 目录,并重启nginx(nginx -s reload) 之后在浏览器访问试试,
注意:我最开始在配置dns解析时记录值配的是公网ip,但是我发现好像不好使,总是连接失败:
修改云解析dns记录值为172.30.128.65后,访问效果如下:
从上边可以看到,我们可以通过https方式访问前端页面和后台接口了。
13.4、http跳转https
一般情况下为了安全都不使用http方式访问页面or后台服务,但是有的人就是喜欢使用http访问怎么办?好说,我给http加个跳转,你访问 xxx.com 我给你跳转到xxx.com, 想达到此效果需要先修改下nginx.conf文件:
server_name www.hzznb-xzll.xyz hzznb-xzll.xyz;# 重定向到目标地址return 301 https://$server_name$request_uri;
请求(这一步浏览器会自动发起请求,无需手动点击或刷新啥的)定向后的目标地址: www.hzznb-xzll.xyz/page/
其次为了方便看出效果,我们先将之前那个index_page.html文件加点图片,加点文字给他的文件大小弄大点,如下:
接口响应压缩:
last配置: 可以看到我们定义 访问 hzznb-xzll.xyz/1/ 的请求被替换为 hzznb-xzll.xyz/2/ 之后再被替换为 hzznb-xzll.xyz/3/ 最后找到/usr/local/nginx/test/static/location_last_test.html 这个文件并返回。
redirect配置: 当访问 hzznb-xzll.xyz/temp_redir/ 这个请求会临时(302)重定向到百度页面
permanent配置: 当访问 hzznb-xzll.xyz/forever_red… 这个请求会永久(301)重定向到百度页面效果如下:
之后我们配置nginx, 使得访问 hzznb-xzll.xyz/book/ 时,返回 /usr/local/nginx/test/book/目录下的书籍,配置如下:
location /book/ { root /usr/local/nginx/test; autoindex on; # 打开 autoindex,,可选参数有 on | off autoindex_format html; # 以html的方式进行格式化,可选参数有 html | json | xml autoindex_exact_size on; # 修改为off,会以KB、MB、GB显示文件大小,默认为on以bytes显示出⽂件的确切⼤⼩ autoindex_localtime off; # 显示⽂件时间 GMT格式 }
15.5、root&alias
root和alias这俩货一般都是用于指定静态资源目录,但是还是有挺大区别的,虽然这是个小知识点但是如果你不清楚规则,很容易走弯路,所以这里阐明并演示这俩的区别。
15.5.1、root
说先说root:
语法: root path;
默认值: root html;
作用域: http
,server
,location
,if in location
为请求设置根目录。例如,使用以下配置:
location /static/ { root /usr/local/nginx/test;}
此时,当你请求 www.hzznb-xzll.xyz/static/imag… 时,/usr/local/nginx/test/static/image2.jpg 文件将被作为响应内容响应给客户端,也就是说 :
root指令会 将 /static/ 拼接到 /usr/local/nginx/test 后边
即完整目录路径为: /usr/local/nginx/test/static/ 演示效果如下:上边配置的意思就是当前你访问 www.hzznb-xzll.xyz/static/imag… 时,nginx会去alias配置的路径:/usr/local/nginx/test/目录下找 image2.jpg 这个文件从而返回。比如我现在的/usr/local/nginx/test/目录下没有image2.jpg 文件则返回404,如下:
interface 这个location不会拼接 interface到 http://mybackendserver/ 后边,如下:
看出来了吗,proxy_pass 值后边加斜线和不加斜线 ,区别是很大的。不加斜线 有点像root(会拼接),加了斜线 有点像alias (不会进行 拼接)。要牢记这个事情。
15.6、upstream 中常用的几个指令
在upstream中,有些指令也是比较常用的所以我们这里也列一下:
参数 描述 server 反向服务地址 weight 权重 fail_timeout 与max_fails结合使用。 max_fails 设置在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了。 max_conns 允许最大连接数 fail_time 服务器会被认为停机的时间长度,默认为10s backup 标记该服务器为备用服务器,当主服务器停止时,请求会被发送到它这里。 down 标记服务器永久停机了 slow_start 当节点恢复,不立即加入 16、重试策略
16.1、服务不可用重试
关于重试策略我们这里也说一下,重试是在发生错误时的一种不可缺少的手段,这样当某一个或者某几个服务宕机时(因为我们现在大多都是多实例部署),如果有正常服务,那么将请求 重试到正常服务的机器上去。
下边我们先修改下nginx.conf文件:
upstream mybackendserver { # 60秒内 如果请求8081端口这个应用失败 # 3次,则认为该应用宕机 时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败, # 则继续等待 60 秒...以此循环,直到恢复 server 172.30.128.64:8081 fail_timeout=60s max_fails=3; server 172.30.128.64:8082; server 172.30.128.64:8083; }
可以看到8082因为超时,重试给8081 然后8081也不可用重试给8083 最终8083返回数据。
16.3、关于backup
Nginx 支持设置备用节点,当所有线上节点都异常时会启用备用节点,同时备用节点也会影响到失败 重试的逻辑。
我们可以通过 backup 指令来定义备用服务器,backup有如下特征:
- 正常情况下,请求不会转到到 backup 服务器,包括失败重试的场景
- 当所有正常节点全部不可用时,backup 服务器生效,开始处理请求
- 一旦有正常节点恢复,就使用已经恢复的正常节点
- backup 服务器生效期间,不会存在所有正常节点一次性恢复的逻辑
- 如果全部 backup 服务器也异常,则会将所有节点一次性恢复,加入存活列表
- 如果全部节点(包括 backup)都异常了,则 Nginx 返回 502 错误
接着我们修改下nginx.conf文件演示下backup的作用:
upstream mybackendserver { server 172.30.128.64:8081 fail_timeout=60s max_fails=3; # 60秒内 如果请求某一个应用失败3次,则认为该应用宕机 时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败,则继续等待 60 秒...以此循环,直到恢复 server 172.30.128.64:8082; server 172.30.128.64:8083 backup; # 设置8083位备机}
可以看到即使8081 不可用也只是去8082重试而不会到备机重试,如果8081 8082都不可用则请求重试到备机:8083
17、最后
为了方便粘贴以及观察,这里贴出我机器上完整的 nginx.conf文件,如下:
17.1、贴出完整nginx.conf文件
user nginx;worker_processes auto;error_log /var/log/nginx/error.log notice;pid /var/run/nginx.pid;events { worker_connections 1024;}http { include /etc/nginx/mime.types; default_type application/octet-stream; # log_format debug '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; # log_format debug ' $remote_user [$time_local] $http_x_Forwarded_for $remote_addr $request ' '$http_x_forwarded_for ' '$upstream_addr ' 'ups_resp_time: $upstream_response_time ' 'request_time: $request_time'; access_log /var/log/nginx/access.log debug; sendfile on; #tcp_nopush on; keepalive_timeout 65; upstream mybackendserver { server 172.30.128.64:8081 fail_timeout=60s max_fails=3; # 60秒内 如果请求某一个应用失败3次,则认为该应用宕机 时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败,则继续等待 60 秒...以此循环,直到恢复 server 172.30.128.64:8082; server 172.30.128.64:8083 backup; # 设置8083位备机 } # 开启/关闭 压缩机制 gzip on; # 根据文件类型选择 是否开启压缩机制 gzip_types text/plain application/javascript text/css application/xml text/javascript image/jpeg image/jpg image/gif image/png application/json; # 设置压缩级别,越高资源消耗越大越耗时,但压缩效果越好 gzip_comp_level 9; # 设置是否携带Vary:Accept-Encoding 的响应头 gzip_vary on; # 处理压缩请求的 缓冲区数量和大小 gzip_buffers 32 64k; # 对于不支持压缩功能的客户端请求 不开启压缩机制 gzip_disable "MSIE [1-6]/."; # 比如低版本的IE浏览器不支持压缩 # 设置压缩功能所支持的HTTP最低版本 gzip_http_version 1.1; # 设置触发压缩的最小阈值 gzip_min_length 2k; # off/any/expired/no-cache/no-store/private/no_last_modified/no_etag/auth 根据不同配置对后端服务器的响应结果进行压缩 gzip_proxied any; # 指定缓存存放目录为/usr/local/nginx/test/nginx_cache_storage,并设置缓存名称为mycache,大小为64m, 1天未被访问过的缓存将自动清除,磁盘中缓存的最大容量为1gb proxy_cache_path /usr/local/nginx/test/nginx_cache_storage levels=1:2 keys_zone=mycache:64m inactive=1d max_size=1g; # 对请求速率限流 #limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=5r/s; # 对请求连接数限流 limit_conn_zone $binary_remote_addr zone=myConnLimit:10m; # --------------------HTTP 演示 配置--------------------- server { listen 80 default; charset utf-8; server_name www.hzznb-xzll.xyz hzznb-xzll.xyz; #location /static/ { # root /usr/local/nginx/test; # /usr/local/nginx/test/static/xxx.jpg #} location /static { # 注意一般 alias的 url都不带后边的/ alias /usr/local/nginx/test/; # 使用alias时 这里的目录最后边一定要加/ 否则就404 } # 测试autoindex效果 location /book/ { root /usr/local/nginx/test; autoindex on; # 打开 autoindex,,可选参数有 on | off autoindex_format html; # 以html的方式进行格式化,可选参数有 html | json | xml autoindex_exact_size on; # 修改为off,会以KB、MB、GB显示文件大小,默认为on以bytes显示出⽂件的确切⼤⼩ autoindex_localtime off; # 显示⽂件时间 GMT格式 } # 临时重定向 location /temp_redir { rewrite ^/(.*) https://www.baidu.com redirect; } # 永久重定向 location /forever_redir { rewrite ^/(.*) https://www.baidu.com permanent; } # rewrite last规则测试 location /1 { rewrite /1/(.*) /2/$1 last; } location /2 { rewrite /2/(.*) /3/$1 last; } location /3 { alias '/usr/local/nginx/test/static/'; index location_last_test.html; } } # --------------------HTTP配置--------------------- server { listen 80; charset utf-8; #server_name www.xxxadminsystem.com; server_name www.hzznbc-xzll.xyz hzznbc-xzll.xyz; # 重定向,会显示跳转的地址server_name,如果访问的地址没有匹配会默认使用第一个,即www.likeong.icu return 301 https://$server_name$request_uri; # # 指定 username 参数中只要有字母 就不走nginx缓存 # if ($arg_username ~ [a-z]) { # set $cache_name "no cache"; # } # # 前端页面资源 # location /page { # alias '/usr/local/nginx/test/static/'; # index index_page.html; # allow all; # } # # 后端服务 # location /interface { # proxy_pass http://mybackendserver/; # # 使用名为 mycache 的缓存空间 # proxy_cache mycache; # # 对于200 206 状态码的数据缓存2分钟 # proxy_cache_valid 200 206 1m; # # 定义生成缓存键的规则(请求的url+参数作为缓存key) # proxy_cache_key $host$uri$is_args$args; # # 资源至少被重复访问2次后再加入缓存 # proxy_cache_min_uses 3; # # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取 # proxy_cache_lock on; # # 上面的锁 超时时间为4s,超过4s未获取数据,其他请求直接去后端 # proxy_cache_lock_timeout 4s; # # 对于请求参数中有字母的 不走nginx缓存 # proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存 # # 在响应头中添加一个缓存是否命中的状态(便于调试) # add_header Cache-status $upstream_cache_status; # limit_conn myConnLimit 12; # limit_req zone=myRateLimit burst=5 nodelay; # limit_req_status 520; # limit_req_log_level info; #} } # --------------------HTTPS 配置--------------------- server { #SSL 默认访问端口号为 443 listen 443 ssl; #填写绑定证书的域名 server_name www.hzznb-xzll.xyz hzznb-xzll.xyz; #请填写证书文件的相对路径或绝对路径 ssl_certificate /usr/local/nginx/certificate/hzznb-xzll.xyz_bundle.crt; #请填写私钥文件的相对路径或绝对路径 ssl_certificate_key /usr/local/nginx/certificate/hzznb-xzll.xyz.key; #停止通信时,加密会话的有效期,在该时间段内不需要重新交换密钥 ssl_session_timeout 5m; #服务器支持的TLS版本 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; #开启由服务器决定采用的密码套件 ssl_prefer_server_ciphers on; # 指定 username 参数中只要有字母 就不走nginx缓存 if ($arg_username ~ [a-z]) { set $cache_name "no cache"; } # 前端页面资源 location /page { alias '/usr/local/nginx/test/static/'; index index_page.html; allow all; } # 后端服务 location /interface { proxy_pass http://mybackendserver/; # 指定哪些错误状态才执行 重试 proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404; # 使用名为 mycache 的缓存空间 proxy_cache mycache; # 对于200 206 状态码的数据缓存2分钟 proxy_cache_valid 200 206 1m; # 定义生成缓存键的规则(请求的url+参数作为缓存key) proxy_cache_key $host$uri$is_args$args; # 资源至少被重复访问2次后再加入缓存 proxy_cache_min_uses 3; # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取 proxy_cache_lock on; # 上面的锁 超时时间为4s,超过4s未获取数据,其他请求直接去后端 proxy_cache_lock_timeout 4s; # 对于请求参数中有字母的 不走nginx缓存 proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存 # 在响应头中添加一个缓存是否命中的状态(便于调试) add_header Cache-status $upstream_cache_status; limit_conn myConnLimit 12; # limit_req zone=myRateLimit burst=5 nodelay; # limit_req_status 520; # limit_req_log_level info; } # 后端服务 location /interface2 { proxy_pass http://mybackendserver; # 使用名为 mycache 的缓存空间 proxy_cache mycache; # 对于200 206 状态码的数据缓存2分钟 proxy_cache_valid 200 206 1m; # 定义生成缓存键的规则(请求的url+参数作为缓存key) proxy_cache_key $host$uri$is_args$args; # 资源至少被重复访问2次后再加入缓存 proxy_cache_min_uses 3; # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取 proxy_cache_lock on; # 上面的锁 超时时间为4s,超过4s未获取数据,其他请求直接去后端 proxy_cache_lock_timeout 4s; # 对于请求参数中有字母的 不走nginx缓存 proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存 # 在响应头中添加一个缓存是否命中的状态(便于调试) add_header Cache-status $upstream_cache_status; limit_conn myConnLimit 12; # limit_req zone=myRateLimit burst=5 nodelay; # limit_req_status 520; # limit_req_log_level info; } } # include /etc/nginx/conf.d/*.conf;}
17.2、最后
这篇文章也基本补齐了对nginx的认识和使用。另外值的一说的是,nginx可不止本文这些东西,功能海了去了,更多的请查阅nginx官网,里边每一个模块,每一个指令以及作用都说的清清楚楚,官网戳这里:nginx官网文档 。