Nginx是一款高性能、轻量级的Web服务器和代理服务器,它采用事件驱动的异步模型,可以支持高并发的访问量。
Nginx既可以作为静态资源服务器,又可以支持动态请求,同时还能够快速地处理HTTP请求。因此,除了用作Web服务器之外,许多大型网站和应用程序也将Nginx作为负载均衡服务器使用,以提高可用性和性能。
【Nginx的优点】
选择Nginx的核心理由还是它能在支持高并发请求的同时保持高效的服务。
Nginx在启动后,在unix系统中会以daemon(守护线程)的方式在后台运行,后台进程包含一个master进程和多个worker进程。master进程主要用来管理worker进程
,包含:接收来自外界的信号、向各worker进程发送信号和监控worker进程的运行状态等。当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理。
多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的
。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致
,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。
Nginx的进程模型:
master来管理worker进程,所以我们只需要与master进程通信就行了。master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要通过kill向master进程发送信号就行了。比如kill -HUP pid,则是告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master进程在接收到HUP信号后是怎么做的呢?首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。当然,直接给master进程发送信号,这是比较老的操作方式,nginx在0.8版本之后,引入了一系列命令行参数,来方便我们管理。比如,./nginx -s reload,就是来重启nginx,./nginx -s stop,就是来停止nginx的运行。如何做到的呢?我们还是拿reload来说,我们看到,执行命令时,我们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道我们的目的是控制nginx来重新加载配置文件了,它会向master进程发送信号,然后接下来的动作,就和我们直接向master进程发送信号一样了。
worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。
nginx采用这种进程模型有什么好处呢?首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。
nginx采用多worker的方式来处理请求,每个worker里面只有一个主线程,那能够处理的并发数很有限啊,多少个worker就能处理多少个并发,何来高并发呢?非也,这就是nginx的高明之处,nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是可以同时处理成千上万个请求的。
推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。
1、当HTTP请求交给Nginx处理时,首先Nginx会取出header头中的Host ,然后将其与所有的配置文件中的每个server段中的server_name进行匹配,以此决定到底有哪个server模块来处理这个请求(当然有时也可能一个Host与多个server块中的server_name都匹配,这时会根据匹配的优先级选择实际处理的server块)。
2、HTTP请求和server_name匹配后,这样接下来nginx就会根据header中的Request URI字段进行与location匹配。
server { # 第一个Server区块开始
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
3、接下来继续由Nginx进行反向代理实现。
4、根据真正的目标服务器地址,再次进行代理请求。
5、当代理HTTP请求到达目标IP地址后,若目标IP地址也是Nginx,则服务器会对其进行类似上面的Nginx处理HTTP请求一样,进行server_name 和 location匹配,并将相应的请求资源返回代理服务器。
6、Nginx反向代理服务器接收到目标服务器的返回资源后,再将其返回给客户端浏览器。
图示:
正向代理隐藏了用户,用户的请求被代理服务器接收代替
,服务器并不知道用户是谁。
图示:
使用反向代理服务器接受请求,再用均衡负载将请求分布给多个真实的服务器。既能提高效率还有一定的安全性
。
正向代理与反向代理最简单的区别:正向代理隐藏的是用户,反向代理隐藏的是服务器
。
server {
listen 80;
server_name localhost;
client_max_body_size 1024M; #限制请求体的大小,若超过所设定的大小,返回413错误。
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host:$server_port; #用来设定被代理服务器接收到的header信息
}
}
负载均衡配置一般都需要同时配置反向代理,通过反向代理跳转到负载均衡
。upstream test {
server localhost:8080 weight=9;
server localhost:8081 weight=1;
}
Nginx本身也是一个静态资源的服务器
,当只有静态资源的时候,就可以使用Nginx来做服务器,同时现在也很流行动静分离,就可以通过Nginx来实现。server {
listen 80;
server_name localhost;
client_max_body_size 1024M;
location / {
root E:/wwwroot;
index index.html;
}
}
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来
,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。upstream test{
server localhost:8080;
server localhost:8081;
}
server {
listen 80;
server_name localhost;
location / {
root e:/wwwroot;
index index.html;
}
# 所有静态请求都由nginx处理,存放目录为html
location ~ .(gif|jpg|jpeg|png|bmp|swf|css|js)$ {
root e:/wwwroot;
}
# 所有动态请求都转发给tomcat处理
location ~ .(jsp|do)$ {
proxy_pass http://test;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root e:/wwwroot;
}
}
这样我们就可以吧HTML以及图片和css以及js放到wwwroot目录下,而tomcat只负责处理jsp和请求,例如当我们后缀为gif的时候,Nginx默认会从wwwroot获取到当前请求的动态图文件返。当然这里的静态文件跟Nginx是同一台服务器,我们也可以在另外一台服务器,然后通过反向代理和负载均衡配置过去就好了,只要搞清楚了最基本的流程,很多配置就很简单了。另外localtion后面其实是一个正则表达式,所以非常灵活。
使用Nginx转发请求:把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。
示例(原先):
1)调试页面的IP及端口:
http://192.168.1.100:8080/
。
2)请求的接口是:http://ni.hao/api/get/info
。
http://192.168.1.100:8080/api/get/info
,这样就解决了跨域问题。server{
listen 8888;
server_name 192.168.1.100;
location /{
proxy_pass http://192.168.1.100:8080;
}
location /api{
proxy_pass http://ni.hao/api;
}
}
保存后,启动Nginx。访问http://192.168.1.100:8888/
即可。
location的作用是根据用户请求的URI来执行不同的应用,也就是根据用户请求的网站URL进行匹配,匹配成功即进行相关的操作。
location [ = | ~ | ~* | ^~ ] uri { .... }
# | 指令 | | 匹配标识 | |匹配的网站网址| |匹配URI之后要执行的配置段|
#优先级1,精确匹配,根路径
location =/ {
return 400;
}
#优先级2,以某个字符串开头,以av开头的,优先匹配这里,区分大小写
location ^~ /av {
root /data/av/;
}
#优先级3,区分大小写的正则匹配,匹配/media*****路径
location ~ /media {
alias /data/static/;
}
#优先级4 ,不区分大小写的正则匹配,所有的****.jpg|gif|png 都走这里
location ~* .*\.(jpg|gif|png|js|css)$ {
root /data/av/;
}
#优先7,通用匹配
location / {
return 403;
}
变量 | 说明 |
---|---|
$args | 变量中存放了请求URL中的请求指令。比如http://192.168.200.133:8080?arg1=value1&args2=value2中的"arg1=value1&arg2=value2" |
$http_user_agent | 变量存储的是用户访问服务的代理信息(如果通过浏览器访问,记录的是浏览器的相关版本信息) |
$host | 变量存储的是访问服务器的server_name值 |
$document_uri | 变量存储的是当前访问地址的URI。比如http://192.168.200.133/server?id=10&name=zhangsan中的"/server",功能和$uri一样 |
$document_root | 变量存储的是当前请求对应location的root值,如果未设置,默认指向Nginx自带html目录所在位置 |
$content_length | 变量存储的是请求头中的Content-Length的值 |
$content_type | 变量存储的是请求头中的Content-Type的值 |
$http_cookie | 变量存储的是客户端的cookie信息,可以通过add_header Set-Cookie 'cookieName=cookieValue’来添加cookie数据 |
$limit_rate | 变量中存储的是Nginx服务器对网络连接速率的限制,也就是Nginx配置中对limit_rate指令设置的值,默认是0,不限制。 |
$remote_addr | 变量中存储的是客户端的IP地址 |
$remote_port | 变量中存储了客户端与服务端建立连接的端口号 |
$remote_user | 变量中存储了客户端的用户名,需要有认证模块才能获取 |
$scheme | 变量中存储了访问协议 |
$server_addr | 变量中存储了服务端的地址 |
$server_name | 变量中存储了客户端请求到达的服务器的名称 |
$server_port | 变量中存储了客户端请求到达服务器的端口号 |
$server_protocol | 变量中存储了客户端请求协议的版本,比如"HTTP/1.1" |
$request_body_fifile | 变量中存储了发给后端服务器的本地文件资源的名称 |
$request_method | 变量中存储了客户端的请求方式,比如"GET","POST"等 |
$request_fifilename | 变量中存储了当前请求的资源文件的路径名 |
$request_uri | 变量中存储了当前请求的URI,并且携带请求参数,比如http://192.168.200.133/server?id=10&name=zhangsan中的"/server?id=10&name=zhangsan" |
流量限制,可以用来限制用户在给定时间内HTTP请求的数量。请求,可以是一个简单网站首页的GET请求,也可以是登录表单的POST请求。
流量限制可以用作安全目的,比如可以减慢暴力密码破解的速率。通过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(通过日志),还可以用来抵御 DDOS 攻击。更常见的情况,该功能被用来保护上游应用服务器不被同时太多用户请求所压垮。
“流量限制”配置两个主要的指令:limit_req_zone
和limit_req
。示例:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /login/ {
limit_req zone=mylimit;
proxy_pass http://my_upstream;
}
}
limit_req_zone指令定义了流量限制相关的参数,而limit_req指令在出现的上下文中启用流量限制(示例中,对于”/login/”的所有请求)。limit_req_zone指令通常在 HTTP 块中定义,使其可在多个上下文中使用,它需要以下三个参数:
- Key:定义应用限制的请求特性。示例中的Nginx变量remote_addr,占用更少的空间)
- Zone:定义用于存储每个IP地址状态以及被限制请求URL访问频率的共享内存区域。保存在内存共享区域的信息,意味着可以在Nginx的worker进程之间共享。定义分为两个部分:通过zone=keyword标识区域的名字,以及冒号后面跟区域大小。16000个IP地址的状态信息,大约需要1MB,所以示例中区域可以存储160000个IP地址。
- Rate:定义最大请求速率。在示例中,速率不能超过每秒10个请求。Nginx实际上以毫秒的粒度来跟踪请求,所以速率限制相当于每100毫秒1个请求。因为不允许”突发情况”,这意味着在前一个请求100毫秒内到达的请求将被拒绝。
limit_req_zone指令设置流量限制和共享内存区域的参数,但实际上并不限制请求速率。所以需要通过添加limit_req指令,将流量限制应用在特定的location或者server块。在上面示例中,我们对/login/请求进行流量限制。每个IP地址被限制为每秒只能请求10次 /login/。更准确地说,在前一个请求的100毫秒内不能请求该URL。
流量整形或速率限制
时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量
。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。也就是我们刚才所讲的情况。漏桶算法提供的机制实际上就是刚才的案例:突发流量会进入到一个漏桶,漏桶会按照我们定义的速率依次处理请求,如果突发流量过大就会直接溢出,则多余的请求会被拒绝
。所以漏桶算法能控制数据的传输速率。令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送
。令牌桶算法的机制如下:存在一个大小固定的令牌桶,会以恒定的速率源源不断产生令牌。如果令牌消耗速率小于生产令牌的速度,令牌就会一直产生直至装满整个令牌桶
。 为了避免服务器崩溃,大家会通过负载均衡的方式来分担服务器压力。将对台服务器组成一个集群,当用户访问时,先访问到一个转发服务器,再由转发服务器将访问分发到压力更小的服务器。
Nginx负载均衡实现的策略有以下五种:
每个请求按时间顺序逐一分配到不同的后端服务器
,如果后端某个服务器宕机,能自动剔除故障系统。upstream backserver {
server 192.168.0.12;
server 192.168.0.13;
}
weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下
。其次是为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。upstream backserver {
server 192.168.0.12 weight=2;
server 192.168.0.13 weight=8;
}
权重越高,在被访问的概率越大,如上例,分别是20%,80%。
每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题
。upstream backserver {
ip_hash;
server 192.168.0.12:88;
server 192.168.0.13:80;
}
upstream backserver {
server server1;
server server2;
fair;
}
哪个服务器的响应速度快,就将请求分配到那个服务器上。
upstream backserver {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
使用user_agent控制客户端浏览器访问,示例:
## 不允许谷歌浏览器访问 如果是谷歌浏览器返回500
if ($http_user_agent ~ Chrome) {
return 500;
}
worker_processes 1; # worker进程的数量
events { # 事件区块开始
worker_connections 1024; # 每个worker进程支持的最大连接数
} # 事件区块结束
http { # HTTP区块开始
include mime.types; # Nginx支持的媒体类型库文件
default_type application/octet-stream; # 默认的媒体类型
sendfile on; # 开启高效传输模式
keepalive_timeout 65; # 连接超时
server { # 第一个Server区块开始
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户
location = /50x.html { # location区块开始,访问50x.html
root html; # 指定对应的站点目录为html
}
}
......
Nginx是一个高性能的Web服务器,能够同时处理大量的并发请求,使用的机制是:多进程机制和异步非阻塞方式。
主程序Master process启动后,通过一个for循环来接收和处理外部信号 。
主进程通过fork()函数产生worker子进程 ,每个子进程执行一个for循环来实现Nginx服务器对事件的接收和处理 。
# 如果访问的ip地址为192.168.9.115,则返回403
if ($remote_addr = 192.168.9.115) {
return 403;
}
return形式:
# 301永久重定向,302临时重定向
return 301 https://example.com$request_uri;
# return 返回形式
return code;
return code URL;
return URL;
rewrite形式:
rewrite ^/$ http://bbs.gitlib.com permanent;
rewrite flag说明:
last:停止处理后续rewrite指令集,然后对当前重写的新URI在rewrite指令集上重新查找。
break:停止处理后续rewrite指令集,并不在重新查找,但是当前location内剩余非rewrite语句和location外的非rewrite语句可以执行。
redirect:如果replacement不是以http:// 或https://开始,返回302临时重定向。
permant:返回301永久重定向。
if ($http_user_agent ~ (125LA|WinHttpRequest|360Spider)) {
return 444;
}
if ($http_referer ~* "filter=author&orderby=dateline") {
return 444;
}
if ($host = 'bbs.gitlib.com') {
rewrite ^/$ http://bbs1.gitlib.com permanent;
}
比较符说明:
使用
=
、!=
比较的一个变量和字符串,true/false。
使用~
、~*
与正则表达式匹配的变量,如果这个正则表达式中包含右花括号}或者分号;则必须给整个正则表达式加引号。
使用-f
、!-f
检查一个文件是否存在。
使用-d
、!-d
检查一个目录是否存在。
使用-e
、!-e
检查一个文件、目录、符号链接是否存在。
使用-x
、!-x
检查一个文件是否可执行。