前端仔必备Nginx避坑指南

2,603 阅读5分钟

本文正在参与 “性能优化实战记录-NGINX专属赛道”话题征文活动

写在前面

Nginx是一款开源的、高性能的HTTP服务器和反向代理服务器;同时也是一个IMAP、POP3、SMTP代理服务器;Nginx可以作为一个HTTP服务器进行网站的发布处理,另外Nginx也能作为反向代理进行负载均衡的实现。很多网站和平台都选用nginx来做代理服务器,以便优化提升系统性能,今天就讲讲nginx使用过程中爬过的坑。

一、Nginx连接限制

Nginx在高并发场景下极易遇到连接过多的情况,用户发起一个请求,就会产生一个文件句柄,文件句柄会随着请求连接的增加而增加,但是Nginx和系统的文件句柄是有限制的。超出限制Nginx就会报错。

以CentOS为例,可以在命令行输入如下命令来查看服务器默认配置的最大文件句柄数。

# ulimit -n
1024

可以看到,在CentOS服务器中,默认的最大文件句柄数为1024。当Nginx的连接数超过1024时,Nginx的错误日志中就会输出如下错误信息。

[alert13576#0: accept() failed (24: Too many open files) 

解决方案一:

可以把打开文件句柄数设置的足够大,命令如下:

ulimit -n 655350

同时修改nginx.conf,添加如下配置项。

worker_rlimit_nofile 655350

这样就可以解决Nginx连接过多的问题,就能支持高并发。 需要注意的是:用ulimit -n 655350命令修改只对当前的shell有效,退出后失效。

解决方案二:

要想让修改的数值永久生效,则必须修改配置文件,可以修改/etc/security/limits.conf配置文件,如下所示。

vim /etc/security/limits.conf

在文件最后添加如下配置项。

* soft nofile 655360
* hard nofile 655360

星号代表全局,soft为软件,hard为硬件,nofile为这里指可打开的文件句柄数。

二、虚拟主机访问优先级

同一个server_name下多个虚拟主机的访问优先级是不一样的

#/etc/nginx/conf.d文件夹下面文件排序方式
testserver1.conf testserver2.conf

#testserver1.conf
server{
	listen 80;
	server_name testserver1 juejin.com;
	location {
		root /opt/app/code1;
		index index.html;
	}
}

#testserver2.conf
server{
	listen 80;
	server_name testserver2 juejin.com;
	location {
		root /opt/app/code2
		index index.html;
	}
}

此时nginx是按照读取/etc/nginx/conf.d文件下面配置文件顺序决定的。所以先读取testserver1.conf,默认访问/opt/app/code1

三、Nginx Proxy内存占用过大

开启Nginx Proxy Cache后,过一段时间内存使用率到达98%,进而导致性能剧烈下降。 内存占用率高的问题是内核问题,内核使用LRU机制,可以通过修改内核参数来改善:

sysctl -w vm.extra_free_kbytes=6463787
sysctl -w vm.vfs_cache_pressure=10000

如果部署在硬盘上使用Proxy Cache性能差,可以通过tmpfs缓存或nginx共享字典缓存元数据,或者直接上SSD。

四、Nginx Try_files路径匹配如何使用

try_files的语法规则:

//格式1
try_files *file* ... *uri*; 
//格式2
try_files *file* ... =*code*;

可应用的上下文:server,location段

执行流程:

按指定的file顺序查找存在的文件,并使用第一个找到的文件进行请求处理

查找路径是按照给定的root或alias为根路径来查找的

如果给出的file都没有匹配到,则重新请求最后一个参数给定的uri,就是新的location匹配

如果是格式2,如果最后一个参数是 = 404 ,若给出的file都没有匹配到,则最后返回404的响应码

location /images/ {
root /ht/;
try_files $uri $uri/ /images/default.gif;
}

例如请求127.0.0.1/images/test.gif 会依次查

1.文件/ht/test.gif

2.文件夹 /ht/下的index文件

3.如果都找不到会请求127.0.0.1/images/default.gif 需要注意的是,try-files如果不写上$uri/,当直接访问一个目录路径时,并不会去匹配目录下的索引页 。 即访问127.0.0.1/images/ 不会去访问 127.0.0.1/images/index.html。

五、Nginx如何禁止IP直接访问

当用户通过访问IP或者未知域名访问网站的时候,可以禁止显示任何有效内容,给他返回500。

server {
 listen 80;
 server_name www.juejin.com # 这里指定自己的域名
}
server{
 listen 80 default_server; # 默认优先返回
 server_name _; # 空主机头或IP
 return 500; # 返回500错误
}
也可以将流量集中导入自己的网站,只要做以下跳转设置就可以

server {
 listen 80 default_server;
 return 302 https://www.juejin.com;
}

六、Nginx封掉真实恶意攻击的IP地址

针对恶意行为如:爬虫、而已抓取、资源盗用和恶意攻击行为,可以直接封禁IP

server {
 listen 80;
 server_name juejin.com;
 location / {
 set $allow true;
 if ($http_x_forwarded_for ~ "127.0.*.*|127.0.0.120|127.0.0.130"){
 set $allow false;
 }
 if ($allow = false){
 return 403;
 }
 }
}

七、“惊群”问题

Nginx里的工作进程一般是按系统CPU核数配置的,有多少个CPU核心,就会配置多少个工作进程,工作进程启动时就会利用fork函数创建多个工作进程,并且所有的工作进程都监听在 nginx.conf内配置的监听端口,充分利用多核机器的性能。当客户端连接请求到来时, 一个新连接事件会上报,各个作进程就会发生对事件的抢夺,这就是“惊群”问题。工作进程越多,问题越明显,这会造成系统性能下降,所以, 必须避免“惊群”问题

例如:在没有用户请求的时候,所有的工作进程都在休眠,此时,一个用户向服务器发起了连接请求,例如,在poll模式下,内核在收到了TCP的SYN包时, 会唤起所有休眠的作进程,最先接收连接请求的工作进程可以成功建立新连接,其他工作进程的连接会失败。这些失败的唤醒是不必要的,引发了不必要的进程上下文切换,增加了系统开销,这就是“惊群”问题典型场景。

Nginx应用层制定了一个机制解决这个问题:规定同一时刻只能有唯一一个工作进程监听Web 端口,这样,新的连接事件只能唤醒唯一一个工作进程。内部的实现实际上是使用了一个进程间的同步锁,工作进程每次唤醒都先尝试这把锁,保证同一时间只有一个工作进程可以进入锁,获得锁的进程设置监昕连接的读事件,以处理未来的新连接请求,并处理已连接上的事件;未能进入监听锁的工作进程则不监听新连接事件,只处理已连接上的事件,将唤醒的工作进程分为了两类,一类(只有1个)是可以监听新连接的,另一类是正常处理已有连接请求的。设置了连接事件监听的进程在连接事件到来时会被唤醒并检查系统变量,发现新连接队列中有连接则释放锁,并调用对应事件的handler方法。这种技术既解决了叫”惊群”问题,也避免了一个进程过长占用锁使新连接得不到及时处理的问题,接收了一个连接后,把连接放入队列后马上释放锁,如果恰巧有新连接马上进来, 则会由一个新的工作进程接收连接,起到一定的负载均衡作用,放入队列的请求事件会在后续阶段处理。

八、location匹配规则

location [=|~|~*|^~] /uri/ { … }

= 开头表示精确匹配

^~ 开头表示uri以某个常规字符串开头,理解为匹配 url路径即可。nginx不对url做编码,因此请求为/static/20%/aa,可以被规则^~ /static/ /aa匹配到(注意是空格)。

~ 开头表示区分大小写的正则匹配

~* 开头表示不区分大小写的正则匹配

!和!*分别为区分大小写不匹配及不区分大小写不匹配 的正则

/ 通用匹配,任何请求都会匹配到。

多个 location 配置的情况下匹配顺序为: 首先匹配 =

其次匹配 ^~

其次是按文件中顺序的正则匹配

最后是交给 / 通用匹配

当有匹配成功时候,停止匹配,按当前匹配规则处理请求

location = / {
   #规则A
}
location = /login {
   #规则B
}
location ^~ /static/ {
   #规则C
}
location ~ \.(gif|jpg|png|js|css)$ {
   #规则D
}
location ~* \.png$ {
   #规则E
}
location / {
   #规则F
}

那么产生的效果如下: 访问根目录 /, 比如 http://localhost/ 将匹配规则A

访问 http://localhost/login 将匹配规则B,http://localhost/register 则匹配规则F

访问 http://localhost/static/a.html 将匹配规则C

访问 http://localhost/a.gif, http://localhost/b.jpg 将匹配规则D和规则E,但是规则D顺序优先,规则E不起作用,而 http://localhost/static/c.png则优先匹配到规则 C

访问 http://localhost/a.PNG 则匹配规则E,而不会匹配规则D,因为规则E不区分大小写

访问 http://localhost/category/id/1111 则最终匹配到规则F,因为以上规则都不匹配,这个时候应该是nginx转发请求给后端应用服务器。

九、Nginx指定路径时,root与alias的区别

root与alias路径匹配主要区别在于nginx如何解释location后面的uri,这会使两者分别以不同的方式将请求映射到服务器文件上,alias是一个目录别名的定义,root则是最上层目录的定义。

root的处理结果是:root路径+location路径

alias的处理结果是:使用alias路径替换location路径

1.root路径配置实例: 用户访问www.juejin.com/image/test.gif,实际上Nginx会上/code/image/目录下找去找test.gif文件

server {
 listen 80;
 server_name www.juejin.com;
 location /image/ {
 root /code;
 }
}

2.alias配置实例: 用户访问www.juejin.com/image/test.gif,实际上Nginx会上/code/目录下找去找test.gif文件。

server {
 listen 80;
 server_name www.juejin.com;
 location /image/ {
 alias /code;
 }
}

十、nginx反向代理设置自定义错误页面

#代理的配置

# cat proxy.conf
server {
 listen 80;
 server_name test.juejin.com;
 location / {
 proxy_pass http://127.0.0.1:8080;
 proxy_intercept_errors on; #接收后端web4xx,5xx错误
 error_page 500 502 403 404 = /proxy_error.html; #将后端web抛出的错误定向到指定的页面
 }
 #如果有请求proxy_error.html文件的则指定到对应的目录
 location = /proxy_error.html {
 root /code/proxy;
 }
}

#后端web节点配置

# cat web.conf
server {
 listen 8080;
 server_name test.juejin.com;
 root /code/web;
 index index.html;
 error_page 404 /404.html; #如果代理开启proxy_intercept_errors则后端web配置error_page无效
}

总结

以上是Nginx使用过程中经常会遇到的问题,你还遇到过什么Nginx“坑”,欢迎评论区一起交流血泪教训。