什么是 Nginx?优势?缺点?应用场景?

  • 优点
    • 内存占用非常少 :一般情况下,10000 个非活跃的 HTTP Keep-Alive 连接在 Nginx 中仅消耗 2.5MB 的内存,这是 Nginx 支持高并发连接的基础。
    • 高并发 : 单机支持 10 万以上的并发连接
    • 跨平台 :可以运行在 Linux,Windows,FreeBSD,Solaris,AIX,Mac OS 等操作系统上。
    • 扩展性好 :第三方插件非常多!
    • 安装使用简单 :对于简单的应用场景,我们很快就能够上手使用。
    • 稳定性好 :bug 少,不会遇到各种奇葩的问题。
    • 免费 :开源软件,免费使用。
    • 热部署机制。
    • 配置简单。
  • 缺点
    • 动态处理差:nginx 处理静态文件好, 耗费内存少,但是处理动态页面则很鸡肋。
  • 应用场景
    • 静态 http 服务器。Nginx 是一个 http 服务可以独立提供 http 服务。可以做网页静态服务器。
    • 虚拟主机。可以实现在一台服务器虚拟出多个网站,例如个人网站使用的虚拟机。
    • 反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用 nginx 做反向代理。并且多台服务器可以平均分担负载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。
    • nginx 中也可以配置安全管理、比如可以使用 Nginx 搭建 API 接口网关,对每个接口服务进行拦截。

为什么使用 Nginx

  • 跨平台配置简单方向代理高并发连接:处理 2-3 万并发连接数,官方监测能支持 5 万并发,内存消耗小:开启 10 个 nginx 才占 150M 内存,nginx 处理静态文件好,耗费内存少,
  • 而且 Nginx 内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
  • 使用 Nginx 的话还能:
    1. 节省宽带:支持 GZIP 压缩,可以添加浏览器本地缓存
    2. 稳定性高:宕机的概率非常小
    3. 接收用户请求是异步的

在网络模型的位置

Nginx 作为一个高性能的 HTTP 和反向代理服务器,主要工作在网络模型的不同层次上。我们可以从 OSI 七层模型和 TCP/IP 四层模型的角度来理解 Nginx 的作用。

OSI 七层模型

OSI 七层模型是从理论角度描述网络通信的一种方式,它分为七层,从下往上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

  1. 应用层(Layer 7)
    • Nginx 在应用层上提供服务,处理 HTTP 和 HTTPS 请求。当作为 Web 服务器或反向代理服务器使用时,Nginx 解析来自客户端的 HTTP 请求,处理这些请求,并将响应返回给客户端。
    • Nginx 还可以处理 WebSocket 连接,这意味着它在应用层上提供了更广泛的交互能力。
  2. 表示层(Layer 6)
    • 虽然 Nginx 不专门针对表示层设计,但它确实处理编码和解码任务,例如压缩和解压缩响应内容,这涉及到表示层的一些功能。
  3. 会话层(Layer 5)
    • Nginx 支持保持会话状态,例如通过 cookies 和 session 信息来跟踪用户会话。尽管它不是专门为会话层设计的,但它确实实现了部分会话管理功能。
  4. 传输层(Layer 4)
    • Nginx 在传输层上也起作用,特别是在处理 TCP 流量方面。它支持 TCP 和 UDP 协议,并可以作为负载均衡器,将流量分发到不同的后端服务器。
  5. 网络层(Layer 3)
    • 当 Nginx 作为负载均衡器时,它在网络层上工作,根据 IP 地址和端口号将请求转发给不同的后端服务器。

TCP/IP 四层模型

TCP/IP 模型简化了 OSI 模型,将其分为四层:网络接口层、互联网层、传输层和应用层。

  1. 应用层(Layer 4)
    • 在应用层,Nginx 提供了 Web 服务器和反向代理功能,处理 HTTP 和 HTTPS 请求。
  2. 传输层(Layer 3)
    • Nginx 在传输层处理 TCP 流量,并支持负载均衡功能。
  3. 互联网层(Layer 2)
    • Nginx 在互联网层上处理 IP 数据包的转发,特别是当它作为负载均衡器时。
  4. 网络接口层(Layer 1)
    • Nginx 间接地影响网络接口层,因为它需要底层网络设备来发送和接收数据包。

进程模型

  1. master-worker 模式
    在 master-worker 模式下,有一个 master 进程和至少一个的 worker 进程。
  2. 单进程模式。
    单进程模式只有一个进程。

Nginx 启动后,会产生一个 master 主进程,主进程执行一系列的工作后会产生一个或者多个工作进程 worker 进程。master 进程用来管理 worker 进程, worker 进程负责处理网络请求。也就是说 Nginx 采用的是经典的 master- worker 模型的多进程模型。

如何处理一个请求的呢?

  • 首先,nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip 地址,
  • 然后在 nginx 的 master 进程里面,先初始化好这个监控的 socket,再进行 listen
  • 然后再 fork 出多个子进程出来, 子进程会竞争 accept 新的连接。
  • 此时,客户端就可以向 nginx 发起连接了。
  • 当客户端与 nginx 进行三次握手,与 nginx 建立好一个连接后,某一个子进程会 accept 成功,然后创建 nginx 对连接的封装,即 ngx_connection_t 结构体,
  • 接着,根据事件调用相应的事件处理模块,如 http 模块与客户端进行数据的交换,
  • 最后,nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了

Nginx 处理 HTTP 请求的过程大概可以分为 11 个阶段,如下:

  1. Read Request Headers:解析请求头。
  2. Identify Configuration Block:识别由哪一个 location 进行处理,匹配 URL。
  3. Apply Rate Limits:判断是否限速。例如可能这个请求并发的连接数太多超过了限制,或者 QPS 太高。
  4. Perform Authentication:连接控制,验证请求。例如可能根据 Referrer 头部做一些防盗链的设置,或者验证用户的权限。
  5. Generate Content:生成返回给用户的响应。为了生成这个响应,做反向代理的时候可能会和上游服务(Upstream Services)进行通信,然后这个过程中还可能会有些子请求或者重定向,那么还会走一下这个过程(Internal redirects and subrequests)。
  6. Response Filters:过滤返回给用户的响应。比如压缩响应,或者对图片进行处理。
  7. Log:记录日志。

负载均衡

Nginx 负载均衡算法

  • 循环轮询(round-robin)
  • 最少链接(least-connected)
  • IP 哈希(ip-hash)
  • 加权算法(weight)
# 循环轮询
upstream app1 {
    server 10.10.10.1;
    server 10.10.10.2;
}
# 最少链接
upstream app1 {
    least_conn;
    server 10.10.10.1;
    server 10.10.10.2;
}
# IP hash
upstream app1 {
    ip_hash;
    server 10.10.10.1;
    server 10.10.10.2;
}
# 加权
upstream app1 {
    server 10.10.10.1 weight=3;
    server 10.10.10.2;
}

更进一步的配置

upstream proxy_nginx {
	server 192.168.0.254 weight=1 max_fails=2 fail_timeout=10s; 
	server 192.168.0.253 weight=2 max_fails=2 fail_timeout=10s;
	server 192.168.0.252 backup; 
	server 192.168.0.251 down; 
}

Nginx 负载均衡的过程

  • 首先在 http 模块中配置使用 upstream 模块定义后台的 webserver 的池子,并且给池子命名,比如命名为 proxy-web,
  • 在池子中我们可以添加多台后台 webserver,其中状态检查、调度算法都是在池子中配置;
  • 然后在 server 模块中定义虚拟 location 地址,但是这个虚拟 location 地址不指定自己的 web 目录站点,
  • 它将使用 location 匹配 url 然后转发到上面定义好的 web 池子中,
  • 后台的 webserver 的池子,最后根据调度策略再转发到后台 web server 上。

反向代理

什么是正向代理和反向代理?

  • 正向代理
    • 服务端不知道客户端是谁
  • 反向代理
    • 客户端不知道服务端是谁。
    • 我们无法直接访问外网,但是可以借助科学上网工具 VPN 来访问。 VPN 会把访问外网服务器(目标服务器)的客户端请求代理到一个可以直接访问外网的代理服务器上去。代理服务器会把外网服务器返回的内容再转发给客户端。

反向代理的好处

  • 保护了真实的 web 服务器,web 服务器对外不可见,外网只能看到反向代理服务器,而反向代理服务器上并没有真实数据,因此,保证了 web 服务器的资源安全。
  • 反向代理为基础产生了动静资源分离以及负载均衡的方式,减轻 web 服务器的负担,加速了对网站访问速度。
  • 节约了有限的 IP 地址资源,企业内所有的网站共享一个在 internet 中注册的 IP 地址,这些服务器分配私有地址,采用虚拟主机的方式对外提供服务。

高并发与 C10K 问题

C10K 问题

所谓 c10k 问题,指的是服务器如何支持 10k 个并发连接,也就是 concurrent 10000 connection(这也是 c10k 这个名字的由来)。

由于硬件成本的大幅度降低和硬件技术的进步,如果一台服务器能够同时服务更多的客户端,那么也就意味着服务每一个客户端的成本大幅度降低。从这个角度来看,c10k 问题显得非常有意义。

C10K 问题,本质上是操作系统的问题。对于 Web1.0/2.0 时代的操作系统而言,传统的同步阻塞 I/O 模型都是一样的,处理的方式都是 requests per second,并发 10K 和 100 的区别关键在于 CPU。

创建的进程、线程多了,数据拷贝频繁(缓存 I/O、内核将数据拷贝到用户进程空间、阻塞),进程/线程上下文切换消耗大,导致操作系统崩溃,这就是 C10K 问题的本质!

可见,解决 C10K 问题的关键就是:尽可能减少 CPU 等核心资源消耗,从而榨干单台服务器的性能,突破 C10K 问题所描述的瓶颈。

C10K 问题的解决办法

Nginx 高并发的原因

多进程机制 + 异步非阻塞机制 + epoll + 底层优化。

  • 多进程机制
    • 服务器每当收到一个客户端时,就有服务器主进程(master process)生成一个子进程(worker process)出来和客户端建立连接进行交互,直到连接断开,该子进程就结束了。
    • 好处
      • 各个进程之间相互独立,不需要加锁,减少了使用锁对性能造成影响,同时降低编程的复杂度,降低开发成本。
      • 采用独立的进程,可以让进程互相之间不会影响,如果一个进程发生异常退出时,其它进程正常工作, master 进程则很快启动新的 worker 进程,确保服务不会中断,从而将风险降到最低。
    • 操作系统生成一个子进程需要进行内存复制等操作,在资源和时间上会产生一定的开销。当有大量请求时,会导致系统性能下降。
  • 异步非阻塞机制
    • 每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。
    • 当某个工作进程接收到客户端的请求以后,调用 IO 进行处理,如果不能立即得到结果,就去处理其他请求(即为非阻塞);而客户端在此期间也无需等待响应,可以去处理其他事情(即为异步)。
    • 当 IO 返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务去响应客户端请求。

动静分离

动静分离的方式

将静态资源放到 nginx 中,动态资源转发到 tomcat 服务器中。业务操作中,则通常将静态资源转发到 CDN 中?

location/ 可以使用正则表达式匹配。并指定对应的硬盘中的目录。如下:

location /image/ {
    root /usr/local/static/;
    autoindex on;
}
mkdir /usr/local/static/image
cd /usr/local/static/image
# 放一个图片进去 1.jpg
sudo nginx -s reload

打开浏览器输入 server_name/image/1.jpg 就可以访问该静态图片了

Nginx 使用

Nginx 常用命令

  • 启动 nginx 。
  • 停止 nginx -s stop 或 nginx -s quit
  • 重载配置 ./sbin/nginx -s reload(平滑重启) 或 service nginx reload 。
  • 重载指定配置文件 .nginx -c /usr/local/nginx/conf/nginx.conf
  • 查看 nginx 版本 nginx -v
  • 检查配置文件是否正确 nginx -t
  • 显示帮助信息 nginx -h 。

Nginx 路径匹配优先级

URI 的匹配,示例如下:

#优先级1,精确匹配,根路径
location =/ {
    return 400;
}
 
#优先级2,以某个字符串开头,以av开头的,优先匹配这里,区分大小写
location ^~ /av {
    root /data/av/;
}
 
#优先级3,区分大小写的正则匹配,匹配/media*****路径
location ~ /media {
    alias /data/static/;
}
 
#优先级4 ,不区分大小写的正则匹配,所有的****.jpg|gif|png 都走这里
location ~* .*\.(jpg|gif|png|js|css)$ {
    root  /data/av/;
}
 
#优先7,通用匹配
location / {
    return 403;
}
  • Nginx 高并发
    • C10K 问题的本质和解决办法
    • Nginx 如何实现超高并发?
  • Nginx 高可用
    • Nginx 高可用如何实现?
  • Nginx 性能优化
  • Nginx 网络模型
  • Nginx 进程模型
  • Nginx 如何处理 http 请求
  • Nginx 配置文件

Nginx 性能优化

Nginx 常用优化配置

  • 设置 Nginx 运行工作进程个数:一般设置 CPU 的核心数或者核心数 x2;
  • 开启 Gzip 压缩: 这样可以使网站的图片、CSS、JS 等文件在传输时进行压缩,提高访问速度, 优化 Nginx 性能。详细介绍可以参考 Nginx 性能优化功能- Gzip 压缩(大幅度提高⻚面加载速度)这篇文章;
  • 设置单个 worker 进程允许客户端最大连接数 :一般设置为 65535 就足够了;
  • 连接超时时间设置:避免在建立无用连接上消耗太多资源;
  • 设置缓存 :像图片、CSS、JS 等这类一般不会经常修改的文件,我们完全可以设置图片在浏览器本地缓存,提高访问速度,优化 Nginx 性能。
  1. 调整 worker_processes 指定 Nginx 需要创建的 worker 进程数量,刚才有提到 worker 进程数一般设置为和 CPU 核心数一致。grep processor / proc / cpuinfo | wc -l 获得 CPU 核心数。
  2. 调整 worker_connections 设置 Nginx 最多可以同时服务的客户端数。结合 worker_processes 配置可以获得每秒可以服务的最大客户端数。最大客户端数/秒=工作进程*工作者连接数
  3. 启动 gzip 压缩,可以对文件大小进行压缩,减少了客户端 http 的传输带宽,可以大幅度提高页面的加载速度。
  4. 启用缓存,如果请求静态资源,启用缓存是可以大幅度提升性能的。
    location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {  
        expires 365d;  
    }
  5. 禁用日志记录或使用日志缓冲
    # 完全禁用访问日志记录
    access_log off;
    # 或者启用访问日志缓冲
    access_log /var/log/nginx/access.log main buffer=32k flush=1m;
  6. 超时时间的优化
keepalive_timeout 10; //设置客户端保持活动状态的超时时间
client_header_timeout 10; //客户端请求头读取超时时间
client_body_timeout 10; //客户端请求体读取超时时间
reset_timedout_connection on; //在客户端停止响应之后,允许服务器关闭连接,释放socket关联的内存
send_timeout 10; //指定客户端的超时时间,如果在10s内客户端没有任何响应,nginx会自动断开连接
  1. 缓存静态页面的优化 (文件句柄是打开文件的唯一标示)
open_file_cache max=100000 inactive=20s; //设置服务器最大缓存10万个文件句柄,关闭20s内无请求的句柄
open_file_cache_valid 30s;//文件句柄的有效期为30s
open_file_cache_min_uses 2;//最少打开2次才会被缓存
open_file_cache_errors on;
  1. 高效文件传输模式的
sendfile on;
tcp_nopush on;
tcp_nodelay on;

Nginx 服务的高可用?

Nginx 可以结合 Keepalived 来实现高可用。

什么是 Keepalived ? 根据官网介绍:

Keepalived 是一个用 C 语言编写的开源路由软件,是 Linux 下一个轻量级别的负载均衡和高可用解决方案。Keepalived 的负载均衡依赖于众所周知且广泛使用的 Linux 虚拟服务器 (IPVS 即 IP Virtual Server,内置在 Linux 内核中的传输层负载均衡器) 内核模块,提供第 4 层负载平衡。Keepalived 实现了一组检查器用于根据服务器节点的健康状况动态维护和管理服务器集群。

Keepalived 的高可用性是通过虚拟路由冗余协议(VRRP 即 Virtual Router Redundancy Protocol,实现路由器高可用的协议)实现的,可以用来解决单点故障。

Github 地址: https://github.com/acassen/keepalived

Keepalived 不仅仅可以和 Nginx 搭配使用,还可以和 LVS、MySQL、HAProxy 等软件配合使用。

再来简单介绍一下 Keepalived+Nginx 实现高可用的常⻅方法:

  1. 准备 2 台 Nginx 服务器,一台为主服务,一台为备用服务;
  2. 在两台 Nginx 服务器上安装并配置 Keepalived;
  3. 为两台 Nginx 服务器绑定同一个虚拟 IP;
  4. 编写 Nginx 检测脚本用于通过 Keepalived 检测 Nginx 主服务器的状态是否正常;

如果 Nginx 主服务器宕机的话,会自动进行故障转移,备用 Nginx 主服务器升级为主服务。并且,这个切换对外是透明的,因为使用的虚拟 IP,虚拟 IP 不会改变。

相关阅读:

  • Nginx 系列教程(五)| 利用 Nginx+Keepalived 实现高可用技术
  • 搭建 Keepalived Nginx 高可用 Web 集群 - 华为云

如何解决跨域问题

什么是跨域

跨域是前端开发中经常会遇到的问题,前端调用后台服务时,通常会遇到 No ‘Access-Control-Allow-Origin’ header is present on the requested resource 的错误,这是因为浏览器的同源策略拒绝了我们的请求。

所谓同源是指,域名,协议,端口相同,浏览器执行一个脚本时同源的脚本才会被执行。

如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

这个问题我们通常会使用 CORS(跨源资源共享) 或者 JSONP 去解决,这两种方法也是使用较多的方法。

Nginx 解决跨域方案一

解决跨域

这个使用 Nginx 的代理功能即可,在 a 服务器的 Nginx 添加如下示例配置:

location ~ /xxx/ {
	proxy_pass http://b.com;
}

这样就把路径中带有 /xxx/ 的请求都转到了 b.com。如果不需要保存 cookie,保持 session 这样的功能,这样就可以了。

然而,本项目就是要用到 cookie,所以就有了下边的内容。

设置 domain

因为 cookie 当中是有 domain 的,两个服务器的一般不同,比如 a 服务器返回的 Response Headers 中是

Set-Cookie:JSESSIONID=_3y4u02v4cbpBw10DoCrMSnjg7m34xuum1XRWBF1Uno; path=/; domain=a.com

而 b 服务器返回的是

Set-Cookie:JSESSIONID=_3y4u02v4cbpBw10DoCrMSnjg7m34xuum1XRWBF1Uno; path=/; domain=b.com

这时候如果 a 项目的页面调用了 b 的接口,浏览器发现接口返回的 domain 不是 a.com,就不会把 cookie 保存起来,session 也就失效了。Nginx 引入了 proxy_cookie_domain 来解决这个问题。示例:

location ~ /xxx/ {
	proxy_cookie_domain b.com a.com;
	proxy_pass http://b.com;
}

这样就可以在 Nginx 转接请求的时候自动把 domain 中的 b.com 转换成 a.com,这样 cookie 就可以设置成功了。

但是,对于有些情况这样转换不灵光。比如,b 项目的 domain 是 .b.com,前边多了一个小点,那对应的改为 proxy_cookie_domain .b.com a.com; 可以不?通过实践,不行!!!

通过查看 Nginx 文档,找到了解决办法。其实,除了上边那种配置方式外,Nginx 还支持正则配置:

location ~ /xxx/ {
	proxy_cookie_domain ~\.?b.com a.com;
	proxy_pass http://b.com;
}

这样就可以把 domain 中的 .b.com 转换成 a.com 啦。

设置 path

正常情况下完成以上两步就可以了,因为 cookie 中的 path 一般默认的是 path=/,也就是所有请求都可以访问本 cookie。但有些服务器会指定,只允许某个层级下的请求可以访问 cookie,比如:

Set-Cookie:JSESSIONID=_3y4u02v4cbpBw10DoCrMSnjg7m34xuum1XRWBF1Uno; path=/sub/; domain=b.com

这样就只允许相对根路径,以 /sub/ 开头的请求路径才能访问 cookie。这时候就又可能出现 cookie 无效的问题了,为了解决这个问题,可以使用 proxy_cookie_path。示例:

location ~ /xxx/ {
	proxy_cookie_domain ~\.?b.com a.com;
	proxy_cookie_path /sub/ /;
	proxy_pass http://b.com;
}

这样就把只允许 /sub/ 层级下的请求访问 cookie,改为允许所有请求访问 cookie 了。

Nginx 解决跨域方案二

或者,我们也可以直接简单粗暴的设置全局配置了,如下:

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    #连接超时时间,服务器会在这个时间过后关闭连接。
    keepalive_timeout  10;
    # gizp压缩
    gzip  on;
    # 直接请求nginx也是会报跨域错误的这里设置允许跨域
    # 如果代理地址已经允许跨域则不需要这些, 否则报错(虽然这样nginx跨域就没意义了)
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers X-Requested-With;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    # srever模块配置是http模块中的一个子模块,用来定义一个虚拟访问主机
    server {
        listen       80;
        server_name  localhost;
        # 根路径指到index.html
        location / {
            root   html;
            index  index.html index.htm;
        }
        # localhost/api 的请求会被转发到192.168.0.103:8080
        location /api {
            rewrite ^/b/(.*)$ /$1 break; # 去除本地接口/api前缀, 否则会出现404
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://192.168.0.103:8080; # 转发地址
        }
        # 重定向错误页面到/50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Nginx 限流怎么做的?

Nginx 限流就是限制用户请求速度,防止服务器受不了

限流有 3 种

  1. 正常限制访问频率(正常流量)
  2. 突发限制访问频率(突发流量)
  3. 限制并发连接数

Nginx 的限流都是基于漏桶流算法,底下会说道什么是漏桶流

正常限制访问频率(正常流量)

限制一个用户发送的请求,我 Nginx 多久接收一个请求。

Nginx 中使用 ngx_http_limit_req_module 模块来限制的访问频率,限制的原理实质是基于漏桶算法原理来实现的。在 nginx.conf 配置文件中可以使用 limit_req_zone 命令及 limit_req 命令限制单个 IP 的请求处理频率。

# 定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
 
# 绑定限流维度
server{
	location/seckill.html{
		limit_req zone=zone;	
		proxy_pass http://lj_seckill;
	}
}
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;

server {
    location /login/ {
        limit_req zone=ip_limit;
        proxy_pass http://login_upstream;
    }
}

突发限制访问频率(突发流量)

限制一个用户发送的请求,我 Nginx 多久接收一个。

上面的配置一定程度可以限制访问频率,但是也存在着一个问题:如果突发流量超出请求被拒绝处理,无法处理活动时候的突发流量,这时候应该如何进一步处理呢?Nginx 提供 burst 参数结合 nodelay 参数可以解决流量突发的问题,可以设置能处理的超过设置的请求数外能额外处理的请求数。我们可以将之前的例子添加 burst 参数以及 nodelay 参数:

# 定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
  
# 绑定限流维度
server{
  	location/seckill.html{
  		limit_req zone=zone burst=5 nodelay;
  		proxy_pass http://lj_seckill;
  	}
  }

为什么就多了一个 burst=5 nodelay; 呢,多了这个可以代表 Nginx 对于一个用户的请求会立即处理前五个,多余的就慢慢来落,没有其他用户的请求我就处理你的,有其他的请求的话我 Nginx 就漏掉不接受你的请求

限制并发连接数

Nginx 中的 ngx_http_limit_conn_module 模块提供了限制并发连接数的功能,可以使用 limit_conn_zone 指令以及 limit_conn 执行进行配置。接下来我们可以通过一个简单的例子来看下:

http {
    limit_conn_zone $binary_remote_addr zone=myip:10m;
    limit_conn_zone $server_name zone=myServerName:10m;
}
    
server {
    location / {
        limit_conn myip 10;
        limit_conn myServerName 100;
        rewrite / http://www.lijie.net permanent;
    }
}

上面配置了单个 IP 同时并发连接数最多只能 10 个连接,并且设置了整个虚拟服务器同时最大并发数最多只能 100 个链接。当然,只有当请求的 header 被服务器处理后,虚拟服务器的连接数才会计数。刚才有提到过 Nginx 是基于漏桶算法原理实现的,实际上限流一般都是基于漏桶算法和令牌桶算法实现的。接下来我们来看看两个算法的介绍:

限流了解吗,怎么限流的?

控制速率基本原理

我们从最简单的限流配置开始:

limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
 
server {
    location /login/ {
        limit_req zone=ip_limit;
        proxy_pass http://login_upstream;
    }
}
  • $binary_remote_addr 针对客户端 ip 限流;
  • zone=ip_limit:10m 限流规则名称为 ip_limit,允许使用 10MB 的内存空间来记录 ip 对应的限流状态;
  • rate=10r/s 限流速度为每秒 10 次请求
  • location /login/ 对登录进行限流

限流速度为每秒 10 次请求,如果有 10 次请求同时到达一个空闲的 nginx,他们都能得到执行吗?

空桶

漏桶漏出请求是匀速的。

10r/s 是怎样匀速的呢?每 100ms 漏出一个请求。

在这样的配置下,桶是空的,所有不能实时漏出的请求,都会被拒绝掉。

所以如果 10 次请求同时到达,那么只有一个请求能够得到执行,其它的,都会被拒绝。

这不太友好,大部分业务场景下我们希望这 10 个请求都能得到执行。

Burst

我们把配置改一下,解决上一节的问题

limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
 
server {
    location /login/ {
        limit_req zone=ip_limit burst=12;
        proxy_pass http://login_upstream;
    }
}
  • burst=12 漏桶的大小设置为 12

Burst

逻辑上叫漏桶,实现起来是 FIFO 队列,把得不到执行的请求暂时缓存起来。

这样漏出的速度仍然是 100ms 一个请求,但并发而来,暂时得不到执行的请求,可以先缓存起来。只有当队列满了的时候,才会拒绝接受新请求。

这样漏桶在限流的同时,也起到了削峰填谷的作用。

在这样的配置下,如果有 10 次请求同时到达,它们会依次执行,每 100ms 执行 1 个。

虽然得到执行了,但因为排队执行,延迟大大增加,在很多场景下仍然是不能接受的。

NoDelay

继续修改配置,解决 Delay 太久导致延迟增加的问题

limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
 
server {
    location /login/ {
        limit_req zone=ip_limit burst=12 nodelay;
        proxy_pass http://login_upstream;
    }
}

nodelay 把开始执行请求的时间提前,以前是 delay 到从桶里漏出来才执行,现在不 delay 了,只要入桶就开始执行

NoDelay

要么立刻执行,要么被拒绝,请求不会因为限流而增加延迟了。

因为请求从桶里漏出来还是匀速的,桶的空间又是固定的,最终平均下来,还是每秒执行了 5 次请求,限流的目的还是达到了。

但这样也有缺点,限流是限了,但是限得不那么匀速。以上面的配置举例,如果有 12 个请求同时到达,那么这 12 个请求都能够立刻执行,然后后面的请求只能匀速进桶,100ms 执行 1 个。如果有一段时间没有请求,桶空了,那么又可能出现并发的 12 个请求一起执行。

大部分情况下,这种限流不匀速,不算是大问题。不过 nginx 也提供了一个参数才控制并发执行也就是 nodelay 的请求的数量。

limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
 
server {
    location /login/ {
        limit_req zone=ip_limit burst=12 delay=4;
        proxy_pass http://login_upstream;
    }
}

delay=4 从桶内第 5 个请求开始 delay

DelayNum

这样通过控制 delay 参数的值,可以调整允许并发执行的请求的数量,使得请求变的均匀起来,在有些耗资源的服务上控制这个数量,还是有必要的。

控制速率基本原理讲完了,

接下来,开始控制速率限流的基础配置

控制速率限流的基础配置:

基于客户端 192.168.1.1 进行限流,

定义了一个大小为 10M,名称为 myLimit 的内存区,用于存储 IP 地址访问信息。
rate 设置 IP 访问频率,rate=5r/s 表示每秒只能处理每个 IP 地址的 5 个请求。

http {
	limit_req_zone 192.168.1.1 zone=myLimit:10m rate=5r/s;
}
 
server {
	location / {
		limit_req zone=myLimit;
		rewrite / http://www.hac.cn permanent;
	}
}

参数解释:

limit_req_zone: 定义需要限流的对象。
zone: 定义共享内存区来存储访问信息。
rate: 用于设置最大访问速率。

Nginx 限流是按照毫秒级为单位的,也就是说 1 秒处理 5 个请求会变成每 200ms 只处理一个请求。如果 200ms 内已经处理完 1 个请求,但是还是有有新的请求到达,这时候 Nginx 就会拒绝处理该请求。

突发流量限制访问频率

上面 rate 设置了 5r/s,

如果有时候流量突然变大,超出的请求就被拒绝返回 503 了,突发的流量影响业务就不好了。

这时候可以加上 burst 参数,一般再结合 nodelay 一起使用。

server {
	location / {
		limit_req zone=myLimit burst=20 nodelay;
		rewrite / http://www.hac.cn permanent;
	}
}

burst=20 nodelay 表示这 20 个请求立马处理,不能延迟,相当于特事特办。不过,即使这 20 个突发请求立马处理结束,后续来了请求也不会立马处理。

burst=20 相当于缓存队列中占了 20 个坑,即使请求被处理了,这 20 个位置也只能按 100ms 一个来释放。

控制并发连接数

ngx_http_limit_conn_module 提供了限制连接数功能。

主要是利用 limit_conn_zone 和 limit_conn 两个指令。

利用连接数限制某一个用户的 ip 连接的数量来控制流量。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m; 
 
server {  
    listen       80;
    server_name  localhost;
    charset utf-8;
    location / {
        limit_conn perip 10;      # 单个客户端ip与服务器的连接数.
        limit_conn perserver 100; #限制与服务器的总连接数
        root   html;
        index  index.html index.htm;
    }
}

limit_conn perip 10 作用的 key 是 server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

Nginx 压缩模块

如何开启压缩?

开启 nginx gzip 压缩后,图片、css、js 等静态资源的大小会减小,可节省带宽,提高传输效率,但是会消耗 CPU 资源。

gzip on;  
#开启gzip压缩功能
gzip_min_length 1k;
#设置允许压缩的页面最小字节数,页面字节数从header头的content-length中获取。默认值是0,不管页面多大都进行压缩。建议设置成大于1k。如果小于1k可能会越压越大。
gzip_buffers 4 16k;
#压缩缓冲区大小。表示申请4个单位为16k的内容作为压缩结果流缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果。
gzip_http_version 1.0;
#压缩版本(默认1.1,前端为squid2.5时使用1.0)用于设置识别http协议版本,默认是1.1,目前大部分浏览器已经支持gzip解压,使用默认即可。
gzip_comp_level 2;
#压缩比率。用来指定gzip压缩比,1压缩比量小,处理速度快;9压缩比量大,传输速度快,但处理最慢,也必将消耗cpu资源。
gzip_types text/plain application/x-javascript text/css application/xml;
#用来指定压缩的类型,“text/html”类型总是会被压缩。
gzip_vary on;
#vary header支持。该选项可以让前端的缓存服务器缓存经过gzip压缩的页面,例如用squid缓存经过nginx压缩的数据。

要注意:需要和不需要压缩的对象

(1)大于 1k 的纯文本文件 html,js,css,xml,html.
(2)图片,视频等不要压缩,因为不但不会减小,在压缩时消耗 cpu 和内存资源。

gzip 配置参数

参数描述
gzip on决定是否开启 gzip 模块,on 表示开启,off 表示关闭
gzip_min_length 1k设置允许压缩的页面最小字节(从 header 头的 Content-Length 中获取) ,当返回内容大于此值时才会使用 gzip 进行压缩,以 K 为单位,当值为 0 时,所有页面都进行压缩。建议大于 1k
gzip_buffers 4 16k设置 gzip 申请内存的大小,其作用是按块大小的倍数申请内存空间,param2:int(k) 后面单位是 k。这里设置以 16k 为单位,按照原始数据大小以 16k 为单位的 4 倍申请内存
gzip_http_version 1.1识别 http 协议的版本,早起浏览器可能不支持 gzip 自解压,用户会看到乱码
gzip_comp_level 2设置 gzip 压缩等级,等级越底压缩速度越快文件压缩比越小,反之速度越慢文件压缩比越大;等级 1-9,最小的压缩最快但是消耗 cpu
gzip_types text/plain设置需要压缩的 MIME 类型,非设置值不进行压缩,即匹配压缩类型
gzip_vary on启用应答头”Vary: Accept-Encoding”
gzip_proxied offnginx 做为反向代理时启用
gzip_disable msie6IE5.5 和 IE6 SP1 使用 msie6 参数来禁止 gzip 压缩 )指定哪些不需要 gzip 压缩的浏览器(将和 User-Agents 进行匹配),依赖于 PCRE 库

将请求压缩到上游

您可以使用 Nginx 模块 gunzip 将请求压缩到上游。gunzip 模块是一个过滤器,它可以对不支持“gzip”编码方法的客户机或服务器使用“内容编码:gzip”来解压缩响应。

Nginx 与其他软件的对比

Nginx