Nginx进阶配置——优化篇

发布于:2023-10-25 ⋅ 阅读:(134) ⋅ 点赞:(0)

前言

上一篇文章介绍了nginx的基本使用配置,这篇文章我们讨论下nginx的一些优化用法。

1. nginx运行优化

1.1 利用多核CPU的配置
  • worker_processes 8;

工作进程数,一般建议设置为与CPU的核心数相同或者两倍,最大支持开启8个;

  • worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

用于将不同worker分配到不同核的CPU上运行,以提升CPU的亲和力。
从低位到高位每一位表示一个CPU核,0表示不启用,1表示启用。

上面的示例为8核CPU开启8个进程的配置,为方便理解,再举两个例子:

  • 两核CPU开启8个进程
worker_processes 8;
# 有4个进程在核心1上运行,另外4个进程在核心2上运行
worker_cpu_affinity 01 10 01 10 01 10 01 10;
  • 8核CPU开启2个进程
worker_processes 2;
# 10101010表示进程1开启了第2,4,6,8内核
# 01010101表示进程2开启了第1,3,5,7内核
worker_cpu_affinity 10101010 01010101;
1.2 最大打开文件数配置
  • worker_rlimit_nofile 2000;

一个worker进程最多打开文件句柄数,如果不设置的话,这个值为操作系统的限制(ulimit -a)

1.3 事件处理模型配置
events {
  use epoll;                # 采用epoll模型处理事件,需要根据不同操作系统来分别配置
  worker_connections 65535; # 单个worker进程允许的客户端最大连接数
}

2. nginx传输优化

与网络传输相关的主要有三个配置项:

  • tcp_nodelay on;

TCP默认会缓冲0.2秒再发送数据,但有些情况需要响应更及时,则可以开启此选项来 强制TCP缓冲区不缓冲数据,每收到一个包就立即发送出去。

  • tcp_nopush on;

开启此选项表示数据包要累积到一定大小再发送,用于提升数据传输效率,依赖于sendfile打开;

  • sendfile on;

打开此选项可以减少磁盘读取和socket写入时的上下文切换,同时能减少用户态和内核态之间的数据拷贝,用于提高web服务器传输静态资源的性能;

深入了解参考:sendfile & tcp_nodelay

3. nginx打开长连接

当使用nginx作为反向代理时,为了支持长连接,需要做到两点:

  • 从client到nginx的连接是长连接
  • 从nginx到server的连接是长连接
3.1 保持与client的长连接

nginx默认会开启对client的keepalive支持。配置示例如下:

http {
    keepalive_timeout  120s 120s;  # 客户端连接的超时值,默认75s
    keepalive_requests 100;           # 一个keepalive连接可以服务的最大请求数量,默认100
}

对于QPS较大的场景,可能需要适当调高keepalive_requests, 原因为:

  • keepalive_requests=100意味着每100个请求就会断开一次连接
  • 拿QPS=10000的服务器来说,相当于平均每1秒就要断开100个连接;
  • 频繁创建和销毁连接会在短时间内出现大量TIME_WAIT。
3.2 保存与server的长连接

默认nginx访问后端都是用的短连接(HTTP1.0),一个请求来了,Nginx 新开一个端口和后端建立连接,后端执行完毕后主动关闭该链接。为了保持长连接,需要在Location和upstream中分别做一定的配置。

  1. Location配置示例:
  • proxy_http_version 1.1 :用于升级协议版本,只有1.1才支持长连接;
  • proxy_set_header Connection "" : 用于清理客户端过来的Connection请求头,目的是忽略Client到nginx的长连接配置,始终保持nginx与upstream之间是长连接
http {
    server {
        location /  {
            proxy_http_version 1.1; 
            proxy_set_header Connection "";
        }
    }
}
  1. upstream配置示例:
  • keepalive: 设置到upstream服务器的空闲keepalive连接的最大数量,最好根据QPS来设置,设置过小会造成流量高峰时连接数反复震荡;
http {
    upstream  BACKEND {
        server   192.168.0.1:8080  weight=1 max_fails=2 fail_timeout=30s;
        server   192.168.0.2:8080  weight=1 max_fails=2 fail_timeout=30s;
        keepalive 300;        // 保持keepalive的连接数量
    }
}

4. 支持CORS跨域访问

什么是跨域限制?

简单描述就是,A网站只允许向A站点发送请求,如果试图向B站点发送请求,则会因为A、B两个站点域名不同而被拒绝。

为何浏览器会有跨域限制? 我们先看下如果没有限制会出现什么:

  1. 用户登录了一家著名的银行网站https://www.icbc.com.cn,该网站会在浏览器的cookie种下当前用户的身份凭证;
  2. 用户又打开了一家恶意网站http://evil.com/,该网站内置了一些对icbc网站的跨站攻击脚本;
  3. 脚本的功能是:偷偷的代替用户向icbc网站发送查帐请求,请求会自动携带icbc站点下的用户cookie信息;
  4. 银行后台从请求cookie中提取到了用户的身份凭证,验证合法,就给恶意站点http://evil.com/返回了帐户存款信息,信息就这样被泄漏;
  5. 由于这一切都是ajax脚本在后台自动执行,用户根本无法感知。

是不是很危险?

这就是常说的CSRF跨站攻击,而跨域限制则是浏览器为这类攻击提供的一种安全防护策略,当在站点http://evil.com/下,是不允许向另外的站点https://www.icbc.com.cn发送请求的。

有些场景需要进行跨域访问,如何支持?

CORS就是一种跨域资源共享的机制,WEB服务器通过显式配置一些Http响应头,即可允许指定站点跨域访问它们的资源。

配置示例如下:

 add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD"; 
 add_header "Access-Control-Max-Age" 3600; 
 add_header "Access-Control-Allow-Origin" "http://l.qsh1.cn"; 
 add_header Access-Control-Allow-Credentials true; 
 add_header "Access-Control-Allow-Headers" $http_access_control_request_headers; 
  
if ($request_method = OPTIONS){ 
    return 200; 
}

释义如下:

  • Access-Control-Allow-Methods: 允许客户端使用的方法类型;
  • Access-Control-Max-Age:预检命令的缓存,如果不缓存每次会发送两次请求。发送跨域请求之前,浏览器一般会先发起Options预检请求,以检查该服务器是否允许访问;
  • Access-Control-Allow-Origin: 允许跨域的源站域名(带协议头),也可以设置* 表示允许任何站点访问,但这样会面临跨站攻击风险;
  • Access-Control-Allow-Headers:允许的自定义请求头
  • Access-Control-Allow-Credentials: true 如果浏览器发送了cookie身份凭证信息,服务器需要回复此Header,浏览器才会将响应体返回给请求者。

实际项目中遇到的延伸问题:The ‘Access-Control-Allow-Origin’ header contains multiple values’x, *', but only one is allowed。

  • 原因:一条请求链上有多处配了跨域头,但跨域头只允许有一个。
  • 解决方法:只保留一处跨域头配置,其它的跨域头删掉。

5. 防盗链之Referer

何为盗链?

一些小网站通过一定的技术手段来盗取大网站的内容(包括文字、图片、音频、视频、软件等),在自己的网站上播放,并加上一些有广告利益的内容。

如何防止盗链呢?

Referer 请求头可以达到这个目的,大概原理:

  • Refer是http协议定义的一个Header,它指向当前请求页面的来源页面的地址,即当前请求源自哪个源链接;
  • 通过它可以跟踪到目标网页的来源网页,如果是资源文件,则可以跟踪到显示它的网页地址;
  • 然后在反向代理上检测来源不是本站的请求即进行阻止或返回指定的页面;

具体到nginx配置,则通过valid_referers来指定可信referer列表,并校验当前请求referer是否可信。语法为:

valid_referers none | blocked | server_names | string ...;

可信refer列表的可选值有:

  • none:表示允许空的来路,也就是直接访问,比如直接在浏览器打开一个文件;
  • blocked:表示允许被防火墙标记过的来路,意思是本来有referer头,但被代理服务器或防火墙隐藏导致refer拿不到,设为blocked时这种情况也允许访问;
  • server_names:表示当前vhost下的server_names都可信;
  • 除上面以外,还可以直接指定域名,例如*.php100.com表示php100.com下的所有子域名允许访问。

vaLid_referers指令的校验结果存放在$invalid_referer变量中,当允许访问时变量值为空,当不允许访问时变量值为1。

下面是一个按文件类型的防盗链配置示例:

location ~* \.(gif|jpg|png|swf|flv|bmp)$ {
  # 来源于php100.com及其下子域名时允许访问
  # 没有引用来源或引用来源被屏蔽时也允许访问
  # 其它情况均是无效访问返回403
  valid_referers none blocked *.php100.com php100.com;
  if ($invalid_referer) {
      #rewrite ^/ http://www.php100.com/403.html;
      return 403;
  }
}

6. nginx之gzip压缩

开启Gzip作用:可以使网站的css、js 、xml、html等静态资源在压缩后再传输, 减少数据传输量,同时优化页面加载速度。

配置示例说明:

# 打开或关闭gzip
gzip on;

# 设置用于处理请求压缩的缓冲区数量和大小,32 4K表示按照内存页大小4K为单位,申请32倍的内存空间。
gzip_buffers 32 4k;

# 定义压缩的级别(压缩比),两点注意:
# 1. 值太高会影响CPU处理性能,所以压缩比并不是越高越好。2. 压缩一定要和静态资源缓存相结合,缓存压缩后的版本,如果每次都压缩,高负载下服务器的CPU吃不消。
gzip_comp_level 3;

#当返回内容大于此值时才会使用gzip进行压缩,以K为单位,当值为0时,所有页面都进行压缩。
gzip_min_length 100;

#早期的浏览器不支持gzip压缩,用户会看到乱码,http/1.0的协议下不开启gzip压缩。
gzip_http_version 1.1;

# 需要压缩的内容类型
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php;

# ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable "MSIE [1-6]\."; 

# Nginx做为反向代理的时候是否启用压缩,any表示压缩所有数据
gzip_proxied any

# 增加响应头”Vary: Accept-Encoding”
gzip_vary on;

gzip压缩比较适合js、css、html等文本类型资源,对于图片、视频类型则不适合,原因是:

  • 图片如jpg、png文件本身就已经做了压缩,对这类文件开启gzip前后效果并不大,白白浪费CPU资源;
  • 视频mp4也类似,这些图片、视频格式基本都做了有损压缩,比gzip的无损压缩算法效果更好,再使用无损压缩效果不大;

7. 接口缓存配置

nginx支持对接口响应的缓存,具体配置过程分为两步。

  1. 在http块增加如下配置,来指明缓存路径和一些缓存参数:
proxy_cache_path /var/cache levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
​

参数释义:

  • /var/cache:Nginx缓存资源的本地存放路径;
  • levels=1:2 :指定2级目录来存储缓存文件,其中第一级目录用1位16进制命名,如a;第二级目录用2位16进制命名,如3a。所以此例中一级目录有16个,二级目录有1616=256个,总目录数为16256=4096个。
  • key_zone:一块命名的内存区域,用来存放缓存的 key 和 metadata;
  • max_size :最大缓存空间, 当达到上限后会删除最少使用的 cache;
  • inactive:某个缓存在inactive指定的时间内如果不访问,将会从缓存中删除;
  1. 在相应URL的Location块中添加如下配置,来启用缓存:
server {
    location ~* ^/urlserver(/.*) {
        proxy_cache my_cache;                         
        proxy_cache_valid 200 302 10m;
        proxy_cache_key $host$uri$is_args$args;
        add_header Nginx-Cache "$upstream_cache_status";
        ……
    }
}

配置项释义如下:

  • proxy_cache:用于启用cache,并指明要使用的key_zone;
  • proxy_cache_valid:缓存HTTP响应状态码为200和302的请求,并将它们缓存10分钟;
  • add_header Nging-Cache “$upstream_cache_status”:用于在响应头中判断是否命中缓存,HIT表示命中缓存,MISS表示未命中缓存;
  • proxy_cache_key: 表示将主机名、URI、问号及查询参数连接起来作为缓存key;

8. 流量镜像

Nginx内置了一个模块nginx_http_mirror_module,用于将实时流量的副本发给被镜像的服务,提前发现可能存在的问题。

镜像的流量不会影响到源站的请求响应,因为Mirror的响应nginx会自动丢弃,下面分几个场景来示例下如何使用。

基本使用:

分为原地址original和镜像地址mirror两部分配置

# original配置
location / { # 指定源uri为/缓存
    mirror /mirror; # 开启流量镜像,并指定镜像uri为/mirror
    mirror_request_body off; # off | on:指定是否镜像请求body部分,默认是on
    proxy_pass http://127.0.0.1:8081; # 原始上游server的地址
}

# mirror配置
location = /mirror {
    internal; # 指定此location只能被“内部的”请求调用,外部的调用请求会返回”Not found” (404)xml
    proxy_pass http://127.0.0.1:8082$request_uri; # 镜像上游server的地址
    proxy_set_header X-Original-URI $request_uri; # 设置镜像流量的头部io
}
流量放大:

只需要多加一个mirror指令即可

   # 源站配置  
   location / {     
       mirror /mirror;     
       # 多加一份mirror,流量放大一倍     
       mirror /mirror;      
       mirror_request_body on;      
       proxy_pass http://127.0.0.1:8081;  
   }  

流量缩小:

mirror 指令没有更多的配置项,它只会将所有的请求复制一份, Nginx中有个专门的模块可以做客户端分流:split_clients
使用方式如下:

  • 第一步,在http块中按来源IP分割流量,一部分支持mirror,另一部分不支持mirro;
	 # split_clients 会将左边的变量 $remote_addr(requests remote address)经过 MurmurHash2 算法进行哈希,
	 # 得出的值如果在前 `50%`(从 0 到 2147483500),那么 $mirror_backend 的值为 test_backend
	 # 如果不在前 50%,那么 $mirror_backend 的值为空字符 ""
	split_clients $remote_addr $mirror_backend {
	    50% test_backend; 
	    *   ""; 
	}
  • 第二步,在镜像地址的location配置块中,根据$mirror_backend来判断是否要转发到后端。
	server {
	   location = /mirror {
	       internal;
	       if ($mirror_backend = "") {
	            return 400;
	       } 
	       proxy_pass http://$mirror_backend$request_uri;
	   }
	}

nginx的mirror虽然好用,但存在一个弊端:复制的镜像请求和原始请求是相关联的,只要镜像请求没有处理完成,原始请求就会被阻塞。这一点在下面参考阅读中有贴专门的文章链接,在产线使用的时候需要留意。

参考阅读:

本文含有隐藏内容,请 开通VIP 后查看