nginx学习
Nginx(发音同“engine x”)是一个高性能的反向代理和 Web 服务器软件。
Nginx作为负载均衡服务:Nginx 既可以在内部直接支持 Rails 和 PHP 程序对外进行服务,也可以支持作为 HTTP代理服务对外进行服务。Nginx采用C进行编写,不论是系统资源开销还是CPU使用效率都比 Perlbal 要好很多。
处理静态文件,索引文件以及自动索引;
无缓存的反向代理加速,简单的负载均衡和容错。
正向代理与反向代理
代理就是在客户端和服务器之间的一层服务器,代理将接收到客户端的请求转发给其他服务器,然后将服务的响应反馈给客户端。
无论是正向代理还是反向代理都是这么实现的
正向代理
正向代理意思就是位于客户端和目标服务器之间的服务器,为了从目标服务器获取内容,客户端发送了一个请求并指定目标,然后代理服务器向目标服务器转发请求并将获得的响应内容返回给客户端。类似一个跳板机,代理访问外部资源。
比如国内访问谷歌,直接访问访问不到,我们可以通过一个正向代理服务器,请求发到代理服务器,代理服务器能够访问谷歌,代理服务器转发请求给谷歌并将响应内容返回给我们。这样就实现了对谷歌的访问。
正向代理是为客户端服务的,目标服务器并不知道该请求是谁发起的。
反向代理
反向代理是以代理服务器来接受客户端的请求,并转发给内部网络上的服务器,并从各服务器获取响应返回给客户端。
比如你访问百度,百度的服务器有很多很多台,百度这个网址就是一个反向代理的存在,他会把请求转发给一个服务器,通常是根据地区来给你分配一个较近的服务器,然后它把请求的响应返回给浏览器。当然了对于你来说具体是哪个服务器无关紧要,你要的只是能够访问到百度。
反向代理是为服务器服务的,客户并不知道给出响应的服务器是哪一台。
负载均衡
如果对于某个服务请求量很多,单个服务器支撑不了,这个时候我们可以选择多加几个服务器,然后把请求分发到各个服务器上,将原先的请求集中到单个服务器的情况改为请求分发到多个服务器上,就是负载均衡。
nginx的负载均衡对应于upstream配置。(具体upstream的使用说明可以自己去搜索,在这里不展开了)
具体实现如下:
# 1.在这里我们通过node启动两个服务,用于接收请求
# server8081.js
const express = require('express')
const http = require('http')
const logger = require('morgan')
const app = express()
app.use(logger('dev'))
app.get('/user', (req, res) => {
console.log(res.headers)
res.send('8081端口')
})
http.createServer(app).listen(8081)
// server8082.js
const express = require('express')
const http = require('http')
const logger = require('morgan')
const app = express()
app.use(logger('dev'))
app.get('/user', (req, res) => {
console.log(res.headers)
res.send('8082端口')
})
http.createServer(app).listen(8082)
# 2. 创建html页面,在页面中请求接口
# index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to webnginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
<p>请求端口为:<em id="port"></em></p>
<script>
console.log('webnginx')
const portEl = document.querySelector('#port')
var xhr = new XMLHttpRequest()
xhr.open('get', '/api/users')
xhr.send()
xhr.onreadystatechange = function(e) {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
portEl.innerHTML = xhr.responseText
}
}
</script>
</body>
</html>
# 3.创建nginx.conf及app目录文件, 用于挂载到容器内
user nginx; # 用户/用户组
worker_processes auto; # 启动的工作进程数量,默认1,可为数字或auto
error_log /var/log/nginx/error.log notice; # 保存错误日志的文件路径
pid /var/run/nginx.pid; # 保存主进程id的文件路径
events {
worker_connections 1024; # 工作进程并发数指令
}
http {
# 负载均衡;两个端口服务
# 注意:本地调试这里只能是ip不能是127.0.0.1及localhost
upstream alone {
server 192.168.44.221:8081 weight=1;
server 192.168.44.221:8082 weight=1;
}
server {
listen 80; # 监听端口/绑定ip
server_name localhost; # 设定主机名
location / {
root /usr/share/nginx/html; # 当前作用域根目录
index index.html index.htm; # 首页处理:当用户没有明确指定请求的文件名时,返回的内容
}
location ~^/api/ {
proxy_pass http://alone;
}
}
include /etc/nginx/mime.types; # types 被请求文件扩展名与 MIME 类型映射表
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 /var/log/nginx/access.log main; # 保存访问日志文件的路径
sendfile on; # 零拷贝,减少文件的读写次数,提升了本地文件的网络传输速度
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
# 4.运行nginx,并与宿主机挂载
docker run -d -p 81:80 --name webnginx -v /Users/shaoalong/program/learn/docker/nginx-app/nginx/app:/usr/share/nginx/html -v /Users/shaoalong/program/learn/docker/nginx-app/nginx/nginx1.conf:/etc/nginx/nginx.conf nginx
# 5.在浏览器中输入localhost进行访问
刷新页面可看到请求响应是有8081服务和8082服务轮替访问
动静分离
在前端开发中,通常来说,动态资源其实就是指那些后台资源基本都是ajax请求,而静态资源就是指HTML,JavaScript,CSS,img等文件。
一般来说都需要将动态资源和静态资源分开,将静态资源部署在nginx上,当一个请求来的时候,如果是静态资源的请求,就直接到nginx配置的静态资源目录下面获取资源,如果是动态资源(ajax)的请求,nginx利用反向代理的原理,把请求转发给后台应用去处理,从而实现动静分离。
nginx.conf配置
nginx的配置系统由一个主配置文件和一些辅助的配置文件构成。这些配置文件均是纯文本文件,全部位于nginx安装目录下的conf目录下。
配置文件中以#开始的行,或者是前面有若干空格或者TAB,然后再跟#的行,都被认为是注释行,也就是支队编辑查看文件的用户有意义,程序在读取这些注释行的时候,其内容是被忽略的。
在nginx.conf文件中,包含若干配置项,每个配置项由配置指令和指令参数两部分构成。指令参数也就是指令对应的配置值,配置指令是一个字符串,可以用单引号或者双引号括起来,也可以不括。但是如果配置指令包含空格,那一定要引起来。
指令参数
指令参数使用一个或者多个空格或者TAB与指令分开。指令的参数有一个或者多个TOKEN串组成。TOKEN串之间由空格或者TAB键分隔。
TOKEN串分为简单字符串和复合配置块。复合配置块即由大括号括起来的一堆内容。一个复合配置块中可能包含若干其他的配置指令。
如果一个配置指令的参数全部由简单的字符串构成,也就是不包含复合配置块,那么我们就说这个配置指令是一个简单配置项,否则称之为复杂配置项。例如 error_page 500 504 =200 /50x.html 就是一个简单配置项。
对于简单配置项,结尾要使用分号结束。对于复杂配置项,包含多个TOKEN串的,一般都是简单的TOKEN串放在前面,复合配置块一般位于最后,而且其结尾并不需要分号结束。例如下面这个复杂配置项:
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
指令上下文
nginx.conf中的配置信息,根据其逻辑上的意义,分成了多个指令域,或者称之为配置指令上下文。
- main: 根级别指令域。该区域的配置指令是全局有效的,指令名为隐性显示。
- events: 事件驱动相关的配置指令域。
- http: HTTP核心配置指令域,包括客户端完整HTTP请求过程中每个过程的处理方法的配置指令。
- upstream: 用于定于被代理服务器组的指令域,也成 上游服务器。
- server: 用来定义服务IP,绑定端口及服务相关的指令域。
- location: 对用户URI进行访问路由处理的指令域。
- stream: Nginx对TCP协议实现代理的配置指令域。
- types: 定义被请求文件扩展名与MIME类型映射表的指令域。
- if: 按照选择条件判断为真时使用的配置指令域。
常用指令
-
alias 顾名思义别名;指定的路径是location的别名,不管location的值怎么写,资源的 真实路径都是 alias 指定的路径。 例如:
location /sc/ { alias /data/static/; }
当请求url为http://localhost/sc/img.png 访问的真实路径为http://localhost/data/static/img.png 。这可在location中
-
root 当前作用域的根目录; 当 root 指令在 location 指令域时,root 设置的是 location 匹配访问路径的上一层目录。例如:
location /flv/ { root /data/web; }
当请求url为http://localhost/flv/aaa 访问的真实路径为http://localhost/data/web/flv/aaa
-
proxy_set_header 请求上游服务器时,添加额外的请求头
-
add_header 响应数据时,要告诉浏览器一些头信息。例如跨域请求(CORS)
-
server_tokens 是否显示nginx的版本
-
client_max_body_size 20m 请求体的最大体量,默认为1m
-
proxy_intercept_errors on | off 默认为off
当上游服务器响应头回来后,可以根据响应状态码的值进行拦截错误处理,与error_page 指令相互结合。用在访问上游服务器出现错误的情况下。
error_page 是对本nginx服务做错误处理 对于上游服务的错误无法处理;如果想要对上游服务也做相应处理,需要把proxy_intercept_errors设置为on
附上一项目nginx.conf文件
worker_processes 5;
error_log logs/error.log debug;
pid logs/nginx.pid;
events {
use epoll;
worker_connections 200000;
}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
client_max_body_size 20m;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_min_length 1k;
gzip_comp_level 2;
gzip_types *;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
gzip_buffers 32 4k;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$upstream_addr" "$upstream_status"'
'$request_time $upstream_response_time';
server {
listen 0.0.0.0:80;
access_log logs/hot.access.log access;
# QO查询接口处理 加 /executeQO后缀
rewrite ^/performance/(.*)/qo/(.*) /performance/$1/qo/$2/executeQO;
# 重定向驾驶舱
rewrite ^/performance/cockpitPage/ /performance;
location / {
root /wls/html;
index index.html index.htm;
}
# publicServer
localhost ~ ^/performance/publicServer/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Connection '';
proxy_http_version 1.1;
rewrite ^/performance/(.*) /performance-public-web/$1 break;
proxy_pass http://performance-public-serveice-svc:8080;
}
# configServer
localhost ~ ^/performance/configServer/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Connection '';
proxy_http_version 1.1;
rewrite ^/performance/(.*) /performance-config-web/$1 break;
proxy_pass http://performance-public-serveice-svc:8080;
}
proxy_intercept_errors on;
recursive_error_pages on;
error_page 500 400 404 =200 /50x.html;
location = /50x.htm {
root /wls/apache/nginx/nginx-1.13.3/html;
}
localhost /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow 30.79.14.204;
deny all;
}
}
}
location匹配规则
语法规则 location [=||*|^~] /uri/ { … } 匹配方式有:
=
表示精确匹配。如果匹配,则跳出匹配过程。(不再进行正则匹配)^~
表示uri以某个常规字符串开头,理解为匹配url路径即可。如果匹配,则跳出匹配过程。(不再进行正则匹配)。nginx不对url做编码,因此请求为/static/20%/aa,可以被规则^~ /static/ /aa匹配到(注意是空格)。不带任何前缀
表示uri以某个常规字符串开头。如果匹配,会继续匹配,直到匹配到最长前缀的匹配或者有匹配的正则匹配。也就是说这个方式优先级最低。~
表示区分大小写的正则匹配~*
表示不区分大小写的正则匹配!~
和!~*
分别为区分大小写不匹配及不区分大小写不匹配 的正则/
通用匹配,任何请求都会匹配到。
不同方式有它匹配的规则:精确匹配 > 以xx开头匹配 > 按文件中顺序的正则匹配 > / 通用匹配。
首先匹配 =
,其次匹配^~
, 其次是按文件中顺序的正则匹配
,最后是交给 /
通用匹配。当有匹配成功时候,停止匹配,按当前匹配规则处理请求。
有如下location配置:
location = / {
# 规则A, 字符串全匹配
# 精确匹配 / ,主机名后面不能带任何字符串
}
location = /login {
# 规则B, 字符串全匹配
# 精确匹配 /login ,主机名后只能是/login
}
location ^~ /static/ {
# 规则C, 以某个字符串开头
# 匹配任何以 /static/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。
}
location ~ \.(gif|jpg|png|js|css)$ {
# 规则D,正则匹配(区分大小写)
# 匹配任何以 .gif|jpg|png|js|css 结束的地址,匹配符合以后,停止往下搜索正则,采用这一条
}
location ~* \.png$ {
# 规则E,正则匹配(不区分大小写)
# 匹配任何以 .png 结束的地址,匹配符合以后,停止往下搜索正则,采用这一条
}
location !~ \.xhtml$ {
# 规则F,正则匹配非判断(区分大小写)
# 匹配任何不是以 .xhtml 结束的地址,匹配符合以后,停止往下搜索正则,采用这一条
}
location !~* \.xhtml$ {
# 规则G,正则匹配非判断(不区分大小写)
# 匹配任何不是以 .xhtml 结束的地址,匹配符合以后,停止往下搜索正则,采用这一条
}
location / {
# 规则H,字符串最长前缀匹配
# 都会匹配到这一条,但是继续往下搜索正则,只有其他都没有匹配到才会采用这一条,优先级最低
}
location /login {
# 规则I,字符串最长前缀匹配
# 匹配以 /login 开头的地址,但是继续往下搜索,只有其他都没有匹配到且没有更长的前缀匹配到才会采用这一条
}
那么产生的效果如下:
- 访问根目录/, 比如http://localhost/ 将匹配规则A
- 访问 http://localhost/login 将匹配规则B,http://localhost/register 则匹配规则H
- 访问 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/a.xhtml 不会匹配规则F和规则G,
- http://localhost/a.XHTML不会匹配规则G,(因为!)。规则F,规则G属于排除法,符合匹配规则也不会匹配到,所以想想看实际应用中哪里会用到。
- 访问 http://localhost/category/id/1111 则最终匹配到规则H,因为以上规则都不匹配,这个时候nginx转发请求给后端应用服务器,nginx作为方向代理服务器存在。
- 访问http://localhost/login/in 则匹配到规则I,因为以上规则都不匹配,这个时候nginx转发请求给后端应用服务器,nginx作为方向代理服务器存在。
proxy_pass
语法: proxy_pass URL;
场景: location, if in location, limit_except
说明: 设置后端代理服务器的协议(protocol)和地址(address),以及location中可以匹配的一个可选的URI。协议可以是"http"或"https"。地址可以是一个域名或ip地址和端口,或者一个 unix-domain socket 路径。
详见官方文档: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
server {
listen 80;
server_name localhost;
# 情形1: proxy_pass 不包含 uri部分, 完整路径于代理服务器请求
# 访问 http://localhost/api1/aaaa
# 后端的request_uri为: /api1/aaaa
location ^~ /api1/ {
proxy_pass http://127.0.0.1:81
}
# 情形2: proxy_pass 包含 uri部分, location匹配部分被忽略路径剩余部分拼接在proxy_pass后面请求
# 访问 http://localhost/api2/aaaa
# 后端的request_uri为: /aaaa
location ^~ /api2/ {
proxy_pass http://127.0.0.1:81/
}
# 访问 http://localhost/api2/aaaa
# 后端的request_uri为: /bbbbaaaa
location ^~ /api2/ {
proxy_pass http://127.0.0.1:81/bbbb
}
# 情形3: 通过变量$request_uri, (也可以是其他变量),后端request_uri会是proxy_pass全路径
# 访问 http://localhost/api3/aaaa
# 后端的request_uri为: /bbbb/api3/aaaa
location ^~ /api3/ {
proxy_pass http://127.0.0.1:81/bbbb$request_uri
}
# 情形4: 通过rewrite配合break标志,对url进行改写,并改写后端的request_uri
# 访问 http://localhost/api4/aaaa
# 后端的request_uri为: /aaaa
location ^~ /api4/ {
rewrite /api4/(.*) $1 break;
proxy_pass http://127.0.0.1:81/
}
# 情形5: 情形3与4并存时,rewrite会失效,后端request_uri会是proxy_pass全路径
# 访问 http://localhost/api4/aaaa
# 后端的request_uri为: /aaaa
location ^~ /api5/ {
set $prefix = 'api'
rewrite /api5/(.*) $1 break;
proxy_pass http://127.0.0.1:81/$prefix
}
# 情形6: proxy_pass 包含 uri部分,且也有rewrite,那么最终的request_uri为rewrite为准
# 访问 http://localhost/api4/aaaa
# 后端的request_uri为: /aaaa
location ^~ /api6/ {
rewrite /api5/(.*) $1 break;
proxy_pass http://127.0.0.1:81/hahaha
}
# 情形6: location如果是正则,proxy_pass 不可包含 uri部分,否则报错如下
# 2022/04/08 07:29:18 [emerg] 172#172: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /etc/nginx/nginx.conf:39 nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /etc/nginx/nginx.conf:39
location ~ /api7/ {
proxy_pass http://127.0.0.1:81/haha/
}
}
nginx命令
- nginx -s reload # 重载nginx.conf文件
- nginx -s reopen # 重启nginx
- nginx -s stop # 强制停止Nginx服务
- nginx -s quit # 优雅地停止Nginx服务(即处理完所有请求后再停止服务)
- nginx -p prefix # 设置前缀路径(默认是:/usr/share/nginx/)
- nginx -c filename # 设置配置文件(默认是:/etc/nginx/nginx.conf)
- killall nginx # 杀死所有nginx进程