前置知识
1.什么是代理
实践中客户端
无法直接跟服务端
发起请求的时候,我们就需要代理服务
。代理可以实现客户端
与服务端
之间的通信,我们的Nginx
也可以实现相应的代理服务。代理分为正向代理
和反向代理
。
2.正向代理
3.反向代理
总的来说就是,正向代理和反向代理代理的对象是不同的,正向代理,代理的是客户端,反向代理代理的是服务器
安装Nginx服务器
Nginx 进程模型解析
ps -ef|grep nginx
nginx模型有两种进程,
master
进程和worker
进程。master
进程主要用来管理worker
进程。管理包含:
1.
接收来自外界的信号,2.
向各worker进程发送信号,3.
监控worker进程的运行状态,4.
当worker进程退出后(异常情况下),会自动重新启动新的worker进程而基本的网络事件,则是放在
worker
进程中来处理了。多个worker
进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个
worker进程
中处理,一个worker进程
,不可能处理其它进程的请求。worker进程
的个数是可以设置的,一般我们会设置与机器cpu核数
一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。/etc/nginx/nginx.conf
worker_processes 2;
怎样操作Nginx?
如上所知,master进程
主要是用来管理worker进程
的,所以操作Nginx只需要操作master进程
就好,我们通过发送信号的方式来操作master进程
。master进程
会接收来自外界发送来的消息,然后 master进程
给子worker进程
发送信号,worker进程
收到信号后执行相关操作,如启动、关闭等
以 nginx -s reload
命令为例,来说说nginx
的处理流程
1.
向 master 进程发送 HUP 信号
2.
master进程先检测 nginx.conf 文件是否配置正确
3.
master进程打开新的监听端口
4.
master进程用新配置启动新的 Worker 进程
5.
master进程向老的 Worker进程发送 QUIT 信号
6.
老worker进程关闭监听句柄,处理完正在进行的请求后结束进程
worker 抢占机制
worker进程
之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80
端口的http
服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?
首先,每个worker进程
都是从master
进程fork(继承)
过来,在master进程
里面,先建立好需要listen
的socket
之后,然后再fork
出多个worker进程
,这样每个worker进程
都可以去accept
这个socket
(当然不是同一个socket
,只是每个进程的这个socket
会监控在同一个ip地址与端口,这个在网络协议里面是允许的)。
一般来说,当一个连接进来后,所有在accept
在这个socket
上面的进程,都会收到通知,而只有一个进程可以accept
这个连接,其它的则accept
失败,这是所谓的惊群现象。
当然,nginx
也不会视而不见,所以nginx
提供了一个accept_mutex
这个东西,从名字上,我们可以看这是一个加在accept
上的一把共享锁。有了这把锁之后,同一时刻,就只会有一个进程在accpet
连接,这样就不会有惊群问题了。accept_mutex
是一个可控选项,我们可以显示地关掉,默认是打开的。
当一个worker进程
在accept
这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程
来处理,而且只在一个worker进程
中处理。
如何实现高并发?
/etc/nginx/config/nginx.conf
events {
# 默认使用epoll
usr epoll;
# 每个worker允许连接的客户端最大连接数
worker_connections 10240;
}
传统服务器的事件处理
1.
当客户端Client 1
发送请求,同时该请求因为某种原因(跟我一样后端写的很渣),导致该请求被阻塞。2.
此时Client2,Client3也发送了请求到worker 1
,此时由于Client1
一直被阻塞,导致worker 1
还被阻塞着,所以没办法处理Client1
和Client2
发送的请求。3.
此时Master看不下去了,又fork
出了一个worer 2
,接下来由worker 2
去处理Client 2
和 Client 3
发送的请求4.``worker 2
去处理Client 2
和 Client 3
发送的请求时,发现Client 2
发送的请求也会导致阻塞,没办法Master
又fork
出了一个worker 3
。
Nginx服务器的事件处理
nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是可以同时处理成千上万个请求的(据说单个worker
进程就可以处理6-8万个并发请求)。
1.
当客户端Client 1
发送请求,同时该请求因为某种原因,导致该请求被阻塞。2.
此时Client2
,Client3
也发送了请求到worker 1
,虽然Client1
发送的请求仍然被阻塞的,但是由于Nginx采用的异步非阻塞的模型,Client1
和Client2
发送的请求仍然会被处理,不会再开启新的线程(worker
)为什么采用异步非阻塞的方式?
首先我们回顾一下一个请求过来的处理过程,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然是不可操作的,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了再继续。
阻塞调用会进入内核等待,cpu
就会让出去给别人用了,对单线程的worker
来说,显然不合适,当网络事件越多时,大家都在等待,cpu
空闲下来没人用,cpu
利用率自然上不去了,更别谈高并发了。
好吧,你说加进程数,这跟apache
的线程模型有什么区别,注意,别增加无谓的上下文切换 ?所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,马上返回EAGAIN
,告诉你,事件还没准备好,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。
虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。
所以,才会有了异步非阻塞的事件处理机制,它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。
nginx.conf 的配置结构
nginx.conf 配置详解
user nginx; # 进程运行的用户
worker_processes 2;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
# 默认使用epoll
usr epoll;
# 每个worker允许连接的客户端最大连接数
worker_connections 10240;
}
http {
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;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
user nginx
sendfile
指令说明
语法: sendfile on | off;
默认值: sendfile off;
上下文: http,server,location,if in location
指定是否使用sendfile
系统调用来传输文件。
sendfile
系统调用在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了数据在内核缓冲区和用户缓冲区之间的拷贝,操作效率很高,被称之为零拷贝。
原理解释
read/write
在传统的文件传输方式(read
、write/send
方式),具体流程细节如下:
调用read函数,文件数据拷贝到内核缓冲区
read函数返回,数据从内核缓冲区拷贝到用户缓冲区
调用write/send函数,将数据从用户缓冲区拷贝到内核socket缓冲区
数据从内核socket缓冲区拷贝到协议引擎中
在这个过程当中,文件数据实际上是经过了四次拷贝操作:
硬盘—>内核缓冲区—>用户缓冲区—>内核socket缓冲区—>协议引擎
sendfile
sendfile
系统调用则提供了一种减少拷贝次数,提升文件传输性能的方法。
sendfile
系统调用利用DMA
引擎将文件数据拷贝到内核缓冲区,之后数据被拷贝到内核socket
缓冲区中
DMA
引擎将数据从内核socket
缓冲区拷贝到协议引擎中
这里没有用户态和内核态之间的切换,也没有内核缓冲区和用户缓冲区之间的拷贝,大大提升了传输性能。
这个过程数据经历的拷贝操作如下:
硬盘—>内核缓冲区—>内核socket缓冲区—>协议引擎
带有DMA
收集拷贝功能的sendfile
对于带有DMA
收集拷贝功能的sendfile
系统调用,还可以再减少一次内核缓冲区之间的拷贝。具体流程如下:
sendfile
系统调用利用DMA
引擎将文件数据拷贝到内核缓冲区,之后,将带有文件位置和长度信息的缓冲区描述符添加到内核socket
缓冲区中
DMA
引擎会将数据直接从内核缓冲区拷贝到协议引擎中
tcp_nodelay
怎么可以强制socket
在它的缓冲区里发送数据?
一个解决方案是 TCP 堆栈的 tcp_nodelay
选项。这样就可以使缓冲区中的数据立即发送出去。
Nginx
的 tcp_nodelay
选项使得在打开一个新的 socket
时增加了tcp_nodelay
选项。
但这时会造成一种情况:
终端应用程序每产生一次操作就会发送一个包,而典型情况下一个包会拥有一个字节的数据以及40个字节长的包头,于是产生4000%的过载,很轻易地就能令网络发生拥塞。
为了避免这种情况,TCP
堆栈实现了等待数据 0.2
秒钟,因此操作后它不会发送一个数据包,而是将这段时间内的数据打成一个大的包。这一机制是由Nagle算法保证。
Nagle化后来成了一种标准并且立即在因特网上得以实现。它现在已经成为默认配置了,但有些场合下把这一选项关掉也是合乎需要的。现在假设某个应用程序发出了一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待产生更多的数据然后再一次发送两种策略。
如果我们马上发送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。如果请求立即发出那么响应时间也会快一些。以上操作可以通过设置套接字的 tcp_nodelay = on
选项来完成,这样就禁用了Nagle 算法。(不需要等待0.2s)
tcp_nopush
在 nginx
中,tcp_nopush
配置和 tcp_nodelay
"互斥"。它可以配置一次发送数据的包大小。也就是说,它不是按时间累计 0.2
秒后发送包,而是当包累计到一定大小后就发送。在 nginx 中,tcp_nopush
必须和sendfile
搭配使用。
keepalive_timeout
keepalive_timeout
参数是一个请求完成之后还要保持连接多久,不是请求时间多久,目的是保持长连接,减少创建连接过程给系统带来的性能损耗,类似于线程池,数据库连接池。
当完成此次响应之后,服务端又发来新的请求,nginx
就会复用该连接。
gzip
nginx中gzip
的主要作用就是用来减轻服务器的带宽问题,经过gzip
压缩后的页面大小可以变为原来的30%甚至更小,这样用户浏览页面时的速度会快很多。gzip
的压缩页面需要浏览器和服务器双方都支持,实际上就是服务器端压缩,传到浏览器后浏览器解压缩并解析。目前的大多数浏览器都支持解析gzip
压缩过的页面。
参数说明:
gzip
语法:gzip on | off;
默认值:gzip off;
作用域:http, server, location, if in location
说明:
启用或禁用gzip压缩模块,on表示启用,off表示禁用
gzip_min_length
语法:gzip_min_length length;
默认值:gzip_min_length 20;
作用域:http, server, location
说明:
设置允许压缩的页面最小字节数,页面字节数从header头中的Content-Length中进行获取。因为过小的文件内容压缩之后效果不明显,甚至会比不压缩时更大,所以一般建议长度不小于1000或1k。
响应头响应浏览器使用gzip解压
gzip_buffers
语法:gzip_buffers number size;
默认值:gzip_buffers 32 4k|16 8k;
作用域:http, server, location
说明:
设置response响应的缓冲区大小。32 4k代表以4k为单位将响应数据以4k的32倍(128k)的大小申请内存。如果没有设置,缓冲区的大小默认为整个响应页面的大小。
gzip_comp_level
语法:gzip_comp_level level;
默认值:gzip_comp_level 1;
作用域:http, server, location
说明:
设置gzip的压缩级别,可接受的范围是从1到9,数字越大压缩率越高,但更消耗CPU,一般设置6即可。
gzip_types
语法:gzip_types mime-type …;
默认值:gzip_types text/html;
作用域:http, server, location
说明:
指定哪些类型的相应才启用gzip压缩,多个用空格分隔。通配符”*”可以匹配任意类型。不管是否指定”text/html”类型,该类型的响应总是启用压缩。一般js、css等文本文件都启用压缩,如application/x-javascript text/css application/xml 等。具体的文件类型对应的mimi-type可以参考conf/mime.types文件。
gzip_http_version
语法:gzip_http_version 1.0 | 1.1;
默认值:gzip_http_version 1.1;
作用域:http, server, location
说明:
设置gzip压缩所需要的请求的最小HTTP版本,低于该版本不使用gzip压缩。一般不用修改,默认即可。
浏览器和服务器进行gzip压缩的请求和处理返回过程
整个请求过程来看,开启gzip和不开启gip功能,其http的请求和返回过程是一致的,不同的是参数。
当开启HTTP的gzip功能时,客户端发出http请求时,会通过headers中的Accept-Encoding属性告诉服务器“我支持gzip解压,解压格式(算法)deflate,sdch为:”。Accept-Encoding:gzip,deflate,sdch
注意,不是request说自己支持解压,Nginx返回response数据的时候就一定会压缩。这还要看本次Nginx返回数据的格式是什么,如果返回数据的原始数据格式,和设置的gzip_types相符合,这时Nginx才会进行压缩。
nginx 常用指令
/usr/sbin
./nginx -s stop 强制关闭
./nginx -s quit 用户无感知关闭,服务当前的正在请求的http请求,阻止新的http请求,直到无http请求时,才将nginx服务器进行关闭
[root@VM_0_12_centos sbin]# ./nginx -h
nginx version: nginx/1.12.2
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-T : test configuration, dump it and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: /usr/share/nginx/)
-c filename : set configuration file (default: /etc/nginx/nginx.conf)
-g directives : set global directives out of configuration file
日志切割
./nginx -V
手动切割
1.在目录/usr/sbin
下增加脚本nginxlog.sh
#/bin/bash
cutlog_path='/var/log/cutnginx'
nglog_path='/var/log/nginx'
if [ ! -d ${cutlog_path} ]
then
mkdir -p ${cutlog_path}
fi
mv $nglog_path/access.log $cutlog_path/access.$(date -d "yesterday" +%Y-%m-%d+%H:%M).log
mv $nglog_path/error.log $cutlog_path/error.$(date -d "yesterday" +%Y-%m-%d+%H:%M).log
# 向nginx主进程发送信号,用于重新打开日志文件
kill -USR1 `cat /var/run/nginx.pid`
也可设置为天
mv $nglog_path/access.log $cutlog_path/access.$(date -d "yesterday" +%Y-%m-%d).log
mv $nglog_path/error.log $cutlog_path/error.$(date -d "yesterday" +%Y-%m-%d).log
2.为脚本赋予权限
chmod +x nginxlog.sh
定时任务切割
1.安装定时任务插件
yum install crontabs # 安装定时任务插件
2.查看当前定时任务列表
crontab -l
3.添加定时任务
crontab -e
*/1 * * * * /usr/sbin/nginxlog.sh
4.重启定时任务
service crond restart
常用的定时任务命令
service crond start
service crond stop
service crond restart
crontab -e # 编辑任务
crontab -e # 任务列表
常用表达式
*/1 * * * * #每分钟执行
59 23 * * * #每天晚上23:59执行
0 1 * * * #每天凌晨1点执行
静态资源服务器
imooc.conf
server {
listen 90;
server_name localhost;
location / {
root /home/foodie-shop;
index index.html;
}
location /imooc {
root /home;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
alias:别名 增加路径的安全性
location /static {
alias /home/imooc;
}
静态文件压缩
server {
listen 90;
server_name localhost;
location / {
root /home/foodie-shop;
index index.html;
}
location /static {
gzip on; # 开启gzip 压缩功能,目的:提高传输效率,节约带宽成本
gzip_min_length 1; # 限制最小压缩的文件大小 小于该字节不压缩
gzip_comp_level 3; # 压缩级别 1-9 文件越大 压缩的越小 但是CPU占用会增多
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png image/jpg;
alias /home/imooc;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}