Nginx 网络代理神器?

​目录

2022新年已至,回顾2021一整年我感到十分的可惜,因为没有在平台上留下一丝有(wú)趣(liáo)的打工历程,正好现在借着新年的契机重拾初心,准备分享一些在工作中遇到的问题和知识,供今后的自己和有兴趣的小伙伴们一起参考和讨论。

Nginx

说到Nginx,相信程序猿们对这个名字是又爱又横,它的起源还要追溯于俄罗斯版的百度(http://rambler.ru),而且京东、腾讯、网易等互联网公司也是它的忠实用户,相信它强大的网络代理功能一定能让我们眼前一亮。

首先介绍一下Nginx日常功能: 网关监控,服务器代理和负载均衡控制等等。。。

注:图片资源转载于www.runoob.com

Nginx的意义

在没有代理网络、环境单一的情况下,客户端和服务端都是直接连接的,也就是说客户端向服务端发送请求而服务端则直接响应客户端。

无代理

有了Nginx的参与,我们则可以将它形象的比喻为网络中介的角色,所有来自客户端的请求都通过代理服务器(也就是Nginx)发送给服务端,而相应的响应结果也需要通过代理返回给客户端。代理服务器的存在大大能解决跨域安全和负载均衡的问题,之所以能解决这些问题还要归功于正向和反向代理不同的设计架构。

有代理

架构特征

正向代理(身份验证、安全性)

代理服务器和客户端在同一个局域网下是正向代理 最显著的结构特征,它存在的意义还是在于客户身份验证 安全访问,也可以理解是为了把关客户端请求。对于服务器来说,所有客户端的请求都来自于代理服务器,但具体是哪一台客户端发送的对于服务器确无从得知(高匿名性)。VPN就是这种架构最典型的代表。而且客户端发送请求中还需要包含其验证信息,以此证明来源可靠性(高安全性)。

反向代理(负载均衡)

反观反向代理,它存在于服务端,是服务端信息入口的“守门人”,所有客户端均可访问代理且没有任何身份认证的要求。整个架构则是为了负载均衡 而考虑的,比如Nginx可以根据不同的正则匹配,采取不同的转发策略,比如图片文件结尾的走文件服务器,动态页面走web服务器等,防止单个服务器负载过重而导致宕机。

均衡策略

负载均衡策略: 内置和扩展,经常使用的策略还是集中在内置,常用的有轮询,加权轮询 (weighted round-robin) 和IP Hash。

轮询

加权轮询

IP Hash

结合图片来看逻辑还是挺简单明了的,相关实践代码会在下面给出参考。

聊完了Nginx整体设计,我们试着从代码层面来挑开迷雾。

Nginx运行模式

在Nginx 启动后,会生成一个 master 进程和多个相互独立的 worker 进程。由master来读取配置文件并且监管worker进程,而worker来负责处理连接和请求。

Nginx配置文件

咱们先来看一下nginx.conf配置文件结构

...#全局块 events { #events块 ... } http#http块 { ... #http全局块 server#server块 { ... #server全局块 location [PATTERN] #location块 { ... } location [PATTERN] { ... } } server { ... } ... #http全局块 }

全局块

全局块设置主要影响Nginx服务器整体运行的配置指令。

下图阐述了常用的Nginx服务器的用户(组)、允许生成的worker process数、进程PID存放路径、日志的存放路径参数意义和使用方法。

# 全局块 #user [user] [group]; # 指定可以运行nginx服务的用户和用户组,只能在全局块配置 user nobody nobody; # 将user指令注释掉,或者配置成nobody所有用户都可以运行 # 指定工作线程数,可以制定具体的进程数,也可使用自动模式 worker_processes 4; # 指定4个工作线程,会生成一个master进程和4个worker进程 #worker_processes auto; # 自动模式 # 指定错误日志路径和级别 #error_log [path] [debug|info|notice|warn|error|crit|alert|emerg] #error_log logs/error.log; #error_log logs/error.lognotice; #error_log logs/error.loginfo; #pid logs/nginx.pid;# 指定pid文件存放的路径,这个指令只能在全局块配置

更多全局个性化配置如下:

worker_cpu_affinity 0001 0010 0100 1000; # 对应进程只能在分配好的内核上工作, # 提高缓存命中率。该代码含义为第一个进 # 程工作在0号内核;第二个进程工作在1号 # 内核;第三个进程工作在2号内核;第四 # 个进程工作在3号内核 worker_priority [-20, 20]; # 设定worker进程优先级,值越小,优先级越高 worker_rlimit_nofile 200;# worker进程所能够打开的文件数量上限

events块

events块影响Nginx服务器与用户的网络连接。常用到的设置包括是否开启对多worker process下的网络连接进行序列化,是否允许同时接收多个网络连接,选取哪种事件驱动模型处理连接请求,每个worker process可以同时支持的最大连接数等。

这一部分的指令对Nginx服务器的性能影响较大,在实际配置中应该根据实际情况灵活调整,因为当某一时刻只有一个网络连接到来时,多个睡眠进程会被同时叫醒,但只有一个进程可获得连接。如果每次唤醒的进程数目太多,会影响一部分系统性能。在Nginx服务器的多进程下,就有可能出现这样的问题。

# Events块 events { # 开启的时候,将会对多个Nginx进程接收连接进行序列 accept_mutex on; # 化,防止多个进程对连接的争抢。默认是开启状态。 multi_accept on; # on: 一个工作进程可以同时接受所有的新连接 # off: nginx一个工作进程只能同时接受一个新的连接 # 指定使用哪种网络IO模型 use select|poll|kqueue|epoll|resig|/dev/poll|eventport; # 会根据你操作系统自动选择 # A) 标准事件模型 # Select、poll # B)高效事件模型 # Kqueue: FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS # Epoll: Linux内核2.6版本及以后的系统 # /dev/poll: Solaris 7, IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+ # Eventport: 使用于Solaris 10 worker_connections 1024; # 设置允许每一个worker process同时开启的最大连接数, # 当每个工作进程接受的连接数超过这个值时将不再接收连接 }

http块

http块可以说是Nginx服务器配置中最重要部分,代理、缓存和日志定义等绝大多数的功能和第三方模块的配置都可以放在这个模块中,而且http模块跟之前我们所讲的大有不同,它包含着server块、location块。

http全局块中配置的指令包括文件引入、MIME-Type定义、日志自定义、是否使用sendfile传输文件、连接超时、单连接请求数上限等。

# Http块(协议级别) http {# MIME Type是网络资源的媒体类型,有HTML、XML、 # GIF及Flash等种类。Nginx作为Web服务器,必须能够 include mime.types; # 识别前端请求的资源类型 # include指令,用于包含其他的配置文件 # 配置默认类型,如果不加此指令,默认值为text/plain default_type application/octet-stream; # log_format指令,用于定义日志格式 #log_format main $remote_addr - $remote_user [$time_local] "$request" #$status $body_bytes_sent "$http_referer" #"$http_user_agent" "$http_x_forwarded_for"; # 指记录Nginx服务器提供服务过程应答前端请求的日志 # main格式在上面被定义则可被下面引用 access_log logs/access.log main; #access_log off;# 关闭access_log,可以使用该命令 sendfile on;# 开启关闭sendfile方式传输文件 #sendfile_max_chunk 128k; # 每个worker process每次调用sendfile()传输数。 # 据量最大不能超过这个值(128k), 如果设置为0,则无 # 限制。默认值为0。 # 配置连接超时时间 keepalive_timeout 65; # 服务器端对连接的保持时间。默认值为75s #keepalive_timeout 120s 100s# 在服务器端保持连接的时间设置为120s,发给用户端的 # 应答报文头部中Keep-Alive域的超时时间设置为100s #keepalive_requests 100;# 配置单连接请求数上限 # 用于限制用户通过某一连接向Nginx服务器发送请求的次 # 数,默认为100 gzip on;# 传输压缩功能开启 }

Upstream块

upstream块是包含在http块中的,兼顾设计负载均衡功能,在前面篇幅中也举了一些负载均衡的策略,在这里进一步进行拓展:轮询、加权轮询、IP Hash、Fair和Url Hash等。

#1.轮询 upstream backend { server 192.168.1.101:8888;# 每个请求按照时间顺序轮流分配到不同的后端服务器 server 192.168.1.102:8888;# 如果某个后端服务器宕机后,能自动剔除 server 192.168.1.103:8888; } #2.加权轮询 upstream backend { server 192.168.1.101 weight=1;# 可以指定轮询比率 server 192.168.1.102 weight=2;# weight和访问几率成正比 server 192.168.1.103 weight=3;# 默认weight = 1 } #3.ip_hash upstream backend { ip_hash; server 192.168.1.101:7777;# 每个请求按照访问ip的hash结果分配 server 192.168.1.102:8888;# 每个访客会固定访问一个后端服务器 server 192.168.1.103:9999; } #4.fair upstream backend { server 192.168.1.101; # 公平地按照后端服务器的响应时间(rt)来分配请求 server 192.168.1.102; # 响应时间短即rt小的后端服务器优先分配请求 server 192.168.1.103; fair; } #5.url_hash upstream backend { server 192.168.1.101; # 与ip_hash类似,但是按照访问url的hash结果来分配请求 server 192.168.1.102; # 在有缓存的情况下可以进一步优化 server 192.168.1.103; hash $request_uri; hash_method crc32; } #6.随机 upstream backend-server { random; server 127.0.0.1:8888; server 127.0.0.1:9999; } #7.最少连接数 upstream backend-server { least_conn; server 127.0.0.1:8888;# 根据最少连接决定分配请求 server 127.0.0.1:9999; } #8.平均响应时间最短 upstream backend-server { least_time header; server 127.0.0.1:8888;# 根据最短平均响应时间决定分配请求 server 127.0.0.1:9999; } # max_fails 最大尝试失败次数 | fail_timeout 失败超时时间 upstream backend {# 如果在30s内失败超过3次那就不再向该服务器做相应请求了 server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server 127.0.0.1:8079 backup; # 表示当前server是备用服务器 server 127.0.0.1:8079 down; # 表示当前server已停用 }

server块

接下来的server块和“虚拟”概念有密切联系。

在使用Nginx服务器提供Web服务时,利用虚拟主机的技术就可以避免为每一个要运行的网站提供单独的Nginx服务器,也无需为每个网站对应运行一组Nginx进程。虚拟主机技术使得Nginx服务器可以在同一台服务器上只运行一组Nginx进程,就可以担任运行多个网站的角色。

在前面提到过,每一个http块都可以包含多个server块,而每个server块就相当于一台虚拟主机,它内部可有多台主机联合提供服务,一起对外提供在逻辑上关系密切的一组服务(或网站)。

和http块相同,server块也可以包含自己的全局块,同时可以包含多个location块。在server全局块中,最常见的两个配置项是本虚拟主机的监听配置和本虚拟主机的名称或IP配置。

# Server块(服务器级别) server {# 可以单独制定ip,单独指定端口或者同时指定ip和端口 listen 80; #listen 127.0.0.1:8000;# 只监听来自127.0.0.1这个IP,请求8000端口的请求 #listen 127.0.0.1; # 只监听来自127.0.0.1这个IP,请求80端口的请求 #listen 8000;# 监听来自所有IP,请求8000端口的请求 #listen *:8000;# 和上面效果一样 #listen localhost:8000;# 和第一种效果一致 server_name localhost; # 用于配置虚拟主机的名称,此外还支持正则表达式 # 如果出现一个名称被多个虚拟主机的server_name匹配成功,规定如下: # 优先准确匹配server_name # 通配符在开始时匹配server_name成功 # 通配符在结尾时匹配server_name成功 # 正则表达式匹配server_name成功 root path; # 指定根目录路径 index c.html;# 设置默认首页,一是,用户在发出请求访问网站时,请求地址可以不写首页名称 # 二是,可以对一个请求,根据其请求的内容而设置不用的首页 }

location块

每个server块中可以包含多个location块,而且在整个Nginx配置文档中起着重要的作用。

location块的主要作用是,基于Nginx服务器接收到的请求字符串(例如, server_name/uri-string),对除虚拟主机名称(也可以是IP别名,后文有详细阐述)之外的字符串(前例中“/uri-string”部分)进行匹配,对特定的请求进行处理。地址定向、数据缓存和应答控制等功能都是在这部分实现。许多第三方模块的配置也是在location块中提供功能。

# location块(请求级别) location = / { # 0.访问根目录   root html;# 用于设置请求寻找资源的跟目录   index index.html index.htm; # 设置默认首页 }# 可以在http块、server块或者location块中配置 location /abc {# 1.没有修饰符 表示:必须以指定模式开始  ...... } # e.g. # # ?p1 # / # de location = /abc {# 2. =表示:必须与指定的模式精确匹配  ...... } # e.g. # # ?p1 # 不满足 # 1/ # de location ~ ^/abc$ {# 3. ~ 表示:指定的正则表达式要区分大小写  ...... } # e.g. # # ?p1=11&p2=22 # 不满足 # # / # de location ~* ^/abc$ { # 4. ~* 表示:指定的正则表达式不区分大小写  ...... } # e.g. # # # ?p1=11&p2=22 # 不满足 # / # de location ^~ /abc { # 5. ^~ 表示:无修饰符的行为,也是以指定模式开始,不同的是  ...... # 如果模式匹配,那么就停止搜索其他模式了 } location @ /abc {# 6. @ 表示:定义命名location区段,这些区段客户段不能访问,  ...... # 只可以由内部产生的请求来访问 } # 7. 多情况匹配 location ~ \.(gif|jpg|png|js|css)$ { ...... }# 是根据括号内的大小写进行匹配 # e.g. # # # # location !~ \.html$ {# 8. 排除法 ...... } # e.g. #(则不会匹配) location / { # 9. 最终匹配规则 ...... } # 查找顺序和优先级 # 1:带有“=“的精确匹配优先 # 2:没有修饰符的精确匹配 # 3:正则表达式按照他们在配置文件中定义的顺序 # 4:带有“^~”修饰符的,开头匹配 # 5:带有“~” 或“~*” 修饰符的,如果正则表达式与URI匹配 # 6:没有修饰符的,如果指定字符串与URI开头匹配 #访问实例及匹配先后顺序 #访问根目录/, 比如 将匹配规则0 #访问将匹配规则1,register 则匹配规则9 #访问将匹配规则7 #访问 /a.gif, /b.jpg 将匹配规则1、规则4和规则7,但是规则1顺序优先,规则4、7不起作用 #访问 /a.PNG 则匹配规则4, 而不会匹配规则1,因为规则4不区分大小写。 #访问不会匹配规则8。 #访问 category/id/1111 则最终匹配到规则9,因为以上规则都不匹配,这个时候nginx转发请求给后端应用服务器,比如FastCGI(php),tomcat(jsp),nginx作为方向代理服务器存在。

rewrite重写

rewrite在nginx配置文件的作用是结合正则表达式和标志位实现url重写以及重定向,基本的表达式如下:

rewrite regex replacement [flag]

Flag 标记解释

标记符号说明last本条规则匹配完成后继续向下匹配新的location URI规则break本条规则匹配完成后终止,不在匹配任何规则redirect返回302临时重定向permanent返回301永久重定向location { rewrite regex replacement [flag]# rewrite基本格式 #rewrite ^/(\d+)/(.+)/ /$2?id=$1 last;# //xxxx -> /xxxx?id= } # $1 指代第一个括号内容(\d+), $2 表示第二个括号内容(.+) # 判断条件 # -f和!-f用来判断是否存在文件 # -d和!-d用来判断是否存在目录 # -e和!-e用来判断是否存在文件或目录 # -x和!-x用来判断文件是否可执行 if (-d $request_filename){# 如果存在请求文件路径,则转发目录自动加“/” rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent; } # 全局参数概览 $args # 这个变量等于请求行中的参数。 $content_length # 请求头中的Content-length字段。 $content_type # 请求头中的Content-Type字段。 $document_root# 当前请求在root指令中指定的值。 $host # 请求主机头字段,否则为服务器名称。 $http_user_agent# 客户端agent信息 $http_cookie# 客户端cookie信息 $limit_rate # 这个变量可以限制连接速率。 $request_body_file# 客户端请求主体信息的临时文件名。 $request_method # 客户端请求的动作,通常为GET或POST。 $remote_addr# 客户端的IP地址。 $remote_port# 客户端的端口。 $remote_user# 已经经过Auth Basic Module验证的用户名。 $request_filename # 当前请求的文件路径,由root或alias指令与URI请求生成。 $query_string # 与$args相同。 $scheme # HTTP 方法(如http,https)。 $server_protocol# 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。 $server_addr# 服务器地址,在完成一次系统调用后可以确定这个值。 $server_name# 服务器名称。 $server_port# 请求到达服务器的端口号。 $request_uri# 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。 $uri# 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。 $document_uri # 与$uri相同。 例::88/test1/test2/test.php $host:localhost $server_port:88 $request_uri::88/test1/test2/test.php $document_uri:/test1/test2/test.php $document_root:~/nginx/html $request_filename:~/nginx/html/test1/test2/test.php # 目录对换 # //xxxx -> /xxxx?id= rewrite ^/(\d+)/(.+)/ /$2?id=$1 last; # 例如下面设定nginx在用户使用ie的使用重定向到/nginx-ie目录下: if ($http_user_agent ~ MSIE) { rewrite ^(.*)$ /nginx-ie/$1 break; } # 目录自动加“/” if (-d $request_filename){ rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent; } # 禁止htaccess location ~/\.ht { deny all; } # 禁止多个目录 location ~ ^/(cron|templates)/ { deny all; break; } # 禁止以/data开头的文件 # 可以禁止/data/下多级目录下.log.txt等请求; location ~ ^/data { deny all; } # 禁止单个目录 # 不能禁止.log.txt能请求 location /searchword/cron/ { deny all; } # 禁止单个文件 location ~ /data/sql/data.sql { deny all; } # 给favicon.ico和robots.txt设置过期时间; # 这里为favicon.ico为99 天,robots.txt为7天并不记录404错误日志 location ~(favicon.ico) { log_not_found off; expires 99d; break; } location ~(robots.txt) { log_not_found off; expires 7d; break; } # 设定某个文件的过期时间;这里为600秒,并不记录访问日志 location ^~ /html/scripts/loadhead_1.js { access_log off; root /opt/lampp/htdocs/web; expires 600; break; } # 文件反盗链并设置过期时间 # 这里的return 412 为自定义的http状态码,默认为403,方便找出正确的盗链的请求 location ~* ^.+\.(jpg|jpeg|gif|png|swf|rar|zip|css|js)$ { valid_referers none blocked *.c1gstudio.com *.c1gstudio.net localhost 208.97.167.194; if ($invalid_referer) { rewrite ^/ ; return 412; break; } access_log off; root /opt/lampp/htdocs/web; expires 3d; break; } # 只充许固定ip访问网站,并加上密码 root/opt/htdocs/www; allow 208.97.167.194; allow 222.33.1.2; allow 231.152.49.4; denyall; auth_basic "C1G_ADMIN"; auth_basic_user_file htpasswd; # 将多级目录下的文件转成一个文件,增强seo效果 # /job-123-456-789.html 指向/job/123/456/789.html rewrite ^/job-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /job/$1/$2/jobshow_$3.html last; #将根目录下某个文件夹指向2级目录 #如/shanghaijob/ 指向 /area/shanghai/ #如果你将last改成permanent,那么浏览器地址栏显是 /location/shanghai/ rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2 last; #上面例子有个问题是访问/shanghai 时将不会匹配 rewrite ^/([0-9a-z]+)job$ /area/$1/ last; rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2 last; #这样/shanghai 也可以访问了,但页面中的相对链接无法使用, #如./list_1.html真实地址是/area/shanghai/list_1.html会变成/list_1.html,导至无法访问。 #那我加上自动跳转也是不行咯 if (-d $request_filename)它有个条件是必需为真实目录,而我的rewrite不是的,所以没有效果 if (-d $request_filename){ rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent; } rewrite ^/([0-9a-z]+)job$ /$1job/ permanent; rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2 last; #文件和目录不存在的时候重定向: if (!-e $request_filename) { proxy_pass ; } #域名跳转 server { listen 80; server_namejump.c1gstudio.com; index index.html index.htm index.php; root/opt/lampp/htdocs/www; rewrite ^/ ; access_logoff; } # 多域名转向 if ($host ~ "c1gstudio\.net") { rewrite ^(.*) http://www.c1gstudio.com$1 permanent; } # 三级域名跳转 if ($http_host ~* "^(.*)\.i\.c1gstudio\.com$") { rewrite ^(.*) http://top.yingjiesheng.com$1; break; } # 域名镜向 server{ listen 80; server_namemirror.c1gstudio.com; index index.html index.htm index.php; root/opt/lampp/htdocs/www; rewrite ^/(.*) $1 last; access_logoff; }

看到这里,是不是对nginx有了个大概的了解,这次篇幅确实过长了些,也欢迎有问题的小伙伴一起交流讨论,后续有新的知识点也会进行查漏补缺,谢谢!