一、简介
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好。
二、windows环境的下载与安装
nginx官网下载地址如下:
http://nginx.org/en/download.html
第一步:下载该文件,下载后的文件如下图所示:
第二步:对该文件进行解压
第三步:控制台中切换到Nginx的解压目录下面,使用start nginx.exe命令启动Nginx
start nginx.exe
第四步: Nginx启动成功后,可以在任务管理器中看到Nginx的进程
第五步:在浏览器中输入127.0.0.1,可以看到nginx欢迎界面
提示:Nginx解压目录\logs目录下面存放着Nginx的日志文件,除了日志文件以外,这个目录下面还会有一个名为“ nginx.pid ”的文件(使用下面的 服务停止命令 以后,该文件会被自动删除)。
打开 nginx.pid 这个文件,可以看到里面是一个进程号:
如果我们想停止nginx服务,则可以使用下述命令:
nginx.exe -s stop
三、Nginx的配置文件说明
#是用来显示是谁运行的NGINX
user nobody;
#指明了nginx要开启的进程数,一般情况下开一个就行了,不到万不得一不要更改这个配置,更改以后需要根据实际情况做一下测试,选出最合适的进程数(取值范围为1-CPU核心数)
worker_processes 1
#设置操作记录日志文件的位置,此处使用的是相对路径
error_log logs/error.log;
#设置日志的级别为notice
error_log logs/error.log notice;
#设置日志的级别为info
error_log logs/error.log info;
#进程号文件保存设置:nginx.pid文件就是一个纯文本文件,里面记录的是进程的pid号,此处默认使用相对路径,一般不建议更改
pid logs/nginx.pid;
#events模块来用指定nginx的工作模式和工作模式下每个进程的连接数上限
#use用来指定Nginx的工作模式。Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。其中select和poll都是标准的工作模式,kqueue和epoll是高效的工作模式,不同的是epoll用在Linux平台上,而kqueue用在BSD系统中,对于Linux系统,epoll工作模式是首选。
#worker_connections用于定义Nginx每个进程的最大连接数,即接收前端的最大请求数,默认是1024。最大客户端连接数由worker_processes和worker_connections决定,即Max_clients=worker_processes*worker_connections。在作为反向代理时,Max_clients变为:Max_clients = worker_processes * worker_connections/4
events {
use epoll;
worker_connections 1024;
}
http {
include mime.types;
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表示设置局部访问记录日志
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 8080;
server_name localhost;
access_log logs/access.log main;
location / {
root html;
index index.html index.htm;
};
error_page 500 502 503 504 /50x.html;
}
}
http模块
include:表示纳入mine.types文件的配置
default_type:如果Web程序没设置,Nginx也没找到对应文件的扩展名的话,就使用默认的Type:default_type application/octet-stream,这是应用程序文件类型的默认值。application/octet-stream是HTTP规范中Content-Type的一种,意思是未知的应用程序文件
3.1 log_format
该配置行的作用为定义日志格式,其有下述9个参数可以配置:
$remote_addr:记录访问网站的客户端地址,例如打印为127.0.0.1
$remote_user:远程客户端用户名,一般没有值,没有值的时候打印为"-"
$time_local:记录访问时间与时区,例如打印为29/Apr/2020:18:51:29 +0800
$request:用户的http请求起始行信息,例如打印为GET / HTTP/1.1
$status:记录请求返回的http状态码,例如:200、301、404等
$body_bytes_sent:服务器发送给客户端的响应body字节数,例如取值为612
$http_referer:记录此次请求是从哪个连接访问过来的,可以根据该参数进行防盗链设置,例如打印为127.0.0.1,如果是直接访问url,则其打印值为"-"
$http_user_agent:浏览页面的访问者在用什么操作系统,例如:浏览器、手机客户端等,例如打印为Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
$http_x_forwarded_for:当前端有代理服务器时,设置web节点记录客户端地址的配置,此参数生效的前提是代理服务器也要进行相关的x_forwarded_for设置(注意:这个参数一般用不上,了解即可)
如下图所示,在nginx.conf配置文件中使用 log_format 定义一个名为main的日志格式并且设置访问日志的打印格式:
3.2 access_log
用来指定访问日志文件的路径及使用何种日志格式记录日志,此处示例中配置的main,其实就是一个日志格式标签名,引用该格式进行日志打印。
access_log默认的配置如上所示,可以看到有两个地方引用了这个。如果没有修改默认的配置,我们直接启动Nginx,可以看到在logs目录下会自动新增一个名为“access.log”的日志文件,
浏览器中访问下述地址,这时可以看到在“access.log”日志文件中会新增一些访问日志:
http://127.0.0.1/
虽然第一处的access.log配置没有打开,但其实它是默认生效的!!!
如果同时开启第一处和第二处的access.log配置,可以看到在logs目录下会自动新增“access.log”的和“host.access.log”两个日志文件。
特别提示:开启access_log配置时,log_format配置也要一起开启!!!
再次访问上述测试地址,可以看到这时只有“host.access.log”文件中有访问日志,而“access.log”日志文件中是没有访问日志。
这时如果我们再新增一个端口8081进行监听,关闭了server模块中的access.log配置,然后再在浏览器中访问下述测试地址:
http://127.0.0.1:8081/
可以看到这时8081服务的访问日志只会在“access.log”日志文件中输出,而“host.access.log”文件中是没有其相关的访问日志。
总结:
- 即使没有开启任何日志输出,默认情况下http模块中的access_log配置会生效。
- http模块中的access_log配置是属于全局的配置,如果server模块中没有开启access_log配置,则生效的是全局配置。
- server模块中开启了access_log配置,则http模块中的access_log全局配置相对于所监听端口服务而言会失效,访问日志只会输出在指定文件中。
- 多个端口监听时,可以通过各自server模块中的access_log配置使其访问日志输出在各自的日志文件。
3.3 sendfile
nginx的http模块中有一个sendfile指令,默认是开启状态。
简单来说就是启用sendfile()系统调用来替换read()和write()函数调用,减少系统上下文切换从而提高性能,当nginx是静态文件服务器时,能极大提高nginx的性能表现,而当nginx是反向代理服务器时,则没什么用了。
下面我们来分析一下这个sendfile的工作原理:
首先我们需要知道sendfile()和read()、write()之间最大的区别就是前者是属于系统调用,而后者是属于函数调用,我们来看下面这幅图。
我们不难看出,nginx是属于Applicaiton的,而read()、write()属于函数调用,也就是在Lib Func这一层,sendfile()属于系统调用,位于System Call这一层,而想要对硬盘进行操作,是Kernel才有的权限,上面的那些层都需要往下调用。
作为对比我们先来看一下正常情况下如果nginx调用read()和write()函数的操作过程:我们都知道数据是存储在硬盘上面的,当数据被调用的时候会被加载进内存再被层层递进最后被CPU使用,这里这个过程我们进行简化,共包含下述4个步骤。
- 步骤一:首先nginx调用read函数,这时data从Hard Disk被加载进Kernel Buffer(Hard Disk)中,此时是从一开始的用户态(user mode)陷入内核态(kernel mode)才能完成操作。
- 步骤二:接着由于data需要被write()函数进行操作,所以data还需从Kernel Buffer(Hard Disk)传输到User Buffer中,此时从内核态(kernel mode)切换回用户态(user mode)
- 步骤三:再接着data被write()函数从user buffer写入到Kernel Buffer(Socket Engine),此时从用户态(user mode)陷入内核态(kernel mode)
- 步骤四:data从Kernel Buffer(Socket Engine)传输到Socket Engine,此时从内核态(kernel mode)切换回用户态(user mode)
这里需要说明两点,一是用户态和内核态之间的切换是需要执行上下文切换操作的,这是十分耗费系统资源和时间的操作,二是因为read()、write()属于函数调用,它们是没有权限在kernel中操作,无法将data直接从Kernel Buffer(Hard Disk)传输到Kernel Buffer(Socket Engine)。
那么使用sendfile()呢?由于是系统调用,所以在步骤二和步骤三的时候就可以不需要再将数据传输到User Buffer,直接在kernel中进行操作,省去了两次状态切换,也就是省去了两次的上下文切换,从而大幅度提升了性能。
我们来看一下下面的这幅图:
最后我们再来解释一下,为什么当 nginx 是反向代理服务器时,sendfile()就没什么用了呢。
顾名思义,sendfile()的作用是发送文件,也就是接收数据的一段是文件句柄,发送数据的那一端是socket。而在做反向代理服务器的时候,两端都是socket,此时无法使用sendfile(),也就不存在性能提升这一说了。
sendfile设置为on表示启动高效传输文件的模式。sendfile可以让Nginx在传输文件时直接在磁盘和tcp socket之间传输数据。如果这个参数不开启,会先在用户空间(Nginx进程空间)申请一个buffer,用read函数把数据从磁盘读到cache,再从cache读取到用户空间的buffer,再用write函数把数据从用户空间的buffer写入到内核的buffer,最后到tcp socket。开启这个参数后可以让数据不用经过用户buffer。
参考资料:
官网的文档: http://nginx.org/en/docs/http/ngx_http_core_module.html#sendfile
aio:http://nginx.org/en/docs/http/ngx_http_core_module.html#aio
read_ahead:http://nginx.org/en/docs/http/ngx_http_core_module.html#read_ahead
3.4 keepalive_timeout
该参数表示是一个请求完成之后还要保持连接多久,不是请求时间多久,目的是保持长连接,减少创建连接过程给系统带来的性能损耗,类似于线程池,数据库连接池。
keepalive_timeout的值为0时,则表示不启用长连接。
3.5 listen
server模块中有一个listen指令,表示所监听的端口
如上所示,该server模块监听的端口为80,当所部署的机器收到80端口的相关请求时就会被Nginx监听到,然后再按照其配置规则进行处理。
3.6 server_name
用于设置虚拟主机服务名称,这个还没有测试出具体的效果,真正使用时再深入一下。
3.7 root
server模块中的root设置的是根文件夹的目录路径,该路径可以是一个相对路径(相对于nginx的安装目录来说的)
例如在E:\nginx-1.23.1\html目录下创建了一个newpage目录
然后将原本位于E:\nginx-1.23.1\html目录下的index.html文件迁移到E:\nginx-1.23.1\html\newPage目录中
index.html文件中的内容如下,这是一个非常好看的前端访问页面的HTML代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
html {
height: 100%;
}
body {
height: 100%;
}
.container {
height: 100%;
background-image: linear-gradient(to right, #fbc2eb, #a6c1ee);
}
.login-wrapper {
background-color: #fff;
width: 358px;
height: 588px;
border-radius: 15px;
padding: 0 50px;
position: relative;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.header {
font-size: 38px;
font-weight: bold;
text-align: center;
line-height: 200px;
}
.input-item {
display: block;
width: 100%;
margin-bottom: 20px;
border: 0;
padding: 10px;
border-bottom: 1px solid rgb(128, 125, 125);
font-size: 15px;
outline: none;
}
.input-item:placeholder {
text-transform: uppercase;
}
.btn {
text-align: center;
padding: 10px;
width: 100%;
margin-top: 40px;
background-image: linear-gradient(to right, #a6c1ee, #fbc2eb);
color: #fff;
}
.msg {
text-align: center;
line-height: 88px;
}
a {
text-decoration-line: none;
color: #abc1ee;
}
</style>
</head>
<body>
<div class="container">
<div class="login-wrapper">
<div class="header">Login</div>
<div class="form-wrapper">
<input type="text" name="username" placeholder="username" class="input-item">
<input type="password" name="password" placeholder="password" class="input-item">
<div class="btn">Login</div>
</div>
<div class="msg">
Don't have account?
<a href="#">Sign up</a>
</div>
</div>
</div>
</body>
</html>
这时如果我们想在访问下述地址时,可以访问到该登录页面,有两种配置方式。
http://127.0.0.1/
第一种配置方式:使用相对路径
特别提示:如果html目录下创建的是index这么一个目录,那么此处的配置可以为 html\index。这个地方要留意下斜杠和反斜杠的方向,上述示例中不能写成 html\newpage,因为root 路径中的 \n 会被识别成了换行符号,这样会导致root路径错误,Nginx服务启动不会报错,但是在访问相关页面时就会报错。
针对上述问题,其解决方案如下:
第一种:把 \n 换成 /n
第二种:把 \n 换成 \\n
第二种配置方式:使用绝对路径
或者
3.8 index
设置启动Nginx后默认的访问根文件夹下面的哪一个页面,如下图所示,当找不到index.html时,就会去找下一个index.htm文件,依次类推
3.9 error_page
error_page的作用是当发生错误的时候能够显示一个预定义的uri,如下图所示,Nginx的配置文件nginx.conf中默认有两处error_page的使用:
第一处虽然是使用 # 注释起来了,但是这个是默认生效的,如果我们访问一个不存在的资源时就会跳转到默认的404页面。
第二处的error_page配置是开启的,当访问出现502、503的时候就能返回50x.html中的内容,这里需要注意是否可以找到50x.html页面,所以加了个location保证找到你自定义的50x页面。
同时我们自己也可以定义上述这种情况下的返回状态码,比如:
error_page 500 502 503 504 =200 /50x.html;
location = /50x.html {
root html;
}
这样用户访问产生500、502 、503的时候给用户的返回状态是200,内容是50x.html。
自定义404错误页面配置中有无等号的区别:
- error_page 404 /404.html 可显示自定义404页面内容,正常返回404状态码。
- error_page 404 = /404.html 可显示自定义404页面内容,但返回200状态码。
server {
listen 80;
server_name test.com;
index index.html index.htm;
location / {
proxy_pass http://online;
error_page 404 = @fallback;
proxy_intercept_errors on;
}
location @fallback {
proxy_pass http://backend;
}
}
upstream online {
server 192.168.1.108:80;
server 192.168.1.109:80;
}
upstream backend {
server 192.168.1.110:80;
}
由于在nginx配置中,设置了limit_req的流量限制,导致许多请求返回503错误代码,在限流的条件下,为提高用户体验,希望返回正常 Code 200,且返回操作频繁的信息:
location /test {
...
limit_req zone=zone_ip_rm burst=1 nodelay;
error_page 503 =200 /dealwith_503?callback=$arg_callback;
}
location /dealwith_503{
set $ret_body '{"code": "V00006","msg": "操作太频繁了,请坐下来喝杯茶。"}';
if ( $arg_callback != "" )
{
return 200 'try{$arg_callback($ret_body)}catch(e){}';
}
return 200 $ret_body;
}
PS:上述这一段限流配置我测试过,但是没有按照我的预期生效。
如果在服务Down掉或者频繁访问导致超时时,仍然返回http status为200的json格式数据,最简单的格式如下:
server{
# 错误页面返回JSON配置
error_page 403 = /respon_403.json;
location = /respon_403.json {
default_type application/json;
return 200 '{"code":"403","data":null,"message":"请登录"}';
}
error_page 404 = /respon_404.json;
location = /respon_404.json {
default_type application/json;
return 200 '{"code":"404","data":null,"message":"没找到请求,请稍后再试~"}';
}
}
进行接口调用测试,这时就会把上述定义好的信息返回:
四、反向代理
反向代理和负载均衡配置:
官方配置:http://nginx.org/en/docs
Nginx反向代理proxy与负载均衡upstream
1.配置反向代理proxy
location ~ \.jsp${ //区分大小写,以jsp结尾的请求,适合动静分离
//设置客户端真实的ip地址
proxy_set_header X-real-ip $remote_addr;
//负载均衡反向代理
proxy_pass http://192.168.1.171:8080
}
2.配置负载均衡upstream
upstream myapp{
server 192.168.1.171:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.172:8080 weight=1 max_fails=2 fail_timeout=30s;
}
注意:此处的upstream是配置在server模块外面的,不在server模块里面
weight:表示权重
max_fails:表示如果有两次请求过来都连接失败的话,表示该节点挂了
fail_timeout:表示连接失败的超时时间为30s
location{
//设置客户端真实的ip地址
proxy_set_header X-real-ip $remote_addr;
//负载均衡反向代理
proxy_pass http://myapp;
}