什么是 Nginx
Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。Nginx 是由俄罗斯人 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。Igor Sysoev 在建立的项目时,使用基于 BSD 许可。
官网:http://nginx.net
Simplify your journey to microservices with the new NGINX Application Platform.
Nginx 以事件驱动的方式编写,所以有非常好的性能,同时也是一个非常高效的反向代理、负载平衡。其拥有匹配 Lighttpd 的性能,同时还没有 Lighttpd 的内存泄漏问题,而且 Lighttpd 的 mod_proxy 也有一些问题并且很久没有更新。
现在,Igor 将源代码以类 BSD 许可证的形式发布。Nginx 因为它的稳定性、丰富的模块库、灵活的配置和低系统资源的消耗而闻名.业界一致认为它是 Apache2.2+mod_proxy_balancer 的轻量级代替者,不仅是因为响应静态页面的速度非常快,而且它的模块数量达到 Apache 的近 2/3。对 proxy 和 rewrite 模块的支持很彻底,还支持 mod_fcgi、ssl、vhosts ,适合用来做 mongrel clusters 的前端 HTTP 响应。
Nginx在大型互联网站点中使用度很高,一台nginx服务器的处理性能基于服务器CPU,进程越高其nginx的质量越高。
Nginx 在启动后,在 unix 系统中会以 daemon 的方式在后台运行,后台进程包含一个 master 进程和多个 worker 进程。我们也可以手动地关掉后台模式,让 Nginx 在前台运行,并且通过配置让 Nginx 取消 master 进程,从而可以使 Nginx 以单进程方式运行。很显然,生产环境下我们肯定不会这么做,所以关闭后台模式,一般是用来调试用的,在后面的章节里面,我们会详细地讲解如何调试 Nginx。
Nginx 最初是作为一个 Web 服务器创建的,用于解决 C10k 的问题。作为一个 Web 服务器,它可以以惊人的速度为您的数据服务。但 Nginx 不仅仅是一个 Web 服务器,你还可以将其用作反向代理,与较慢的上游服务器(如:Unicorn 或 Puma)轻松集成。你可以适当地分配流量(负载均衡器)、流媒体、动态调整图像大小、缓存内容等等。
基本的 nginx 体系结构由 master 进程和其 worker 进程组成。master 读取配置文件,并维护 worker 进程,而 worker 则会对请求进行实际处理。
Nginx的内部架构如图所示,当Nginx服务开启的时候,会启动一个master进程,及其多个worker(worker的多少与你搭建的服务器主机的性能有关,更多进程支持的强大CPU当然可以开启更多的worker),master 进程主要用来管理 worker 进程,包含:master接收来自外界的信号,向各 worker 进程发送信号,监控 worker 进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的 worker 进程。而基本的网络事件,则是放在 worker 进程中来处理了。多个 worker 进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个 worker 进程中处理,一个 worker 进程,不可能处理其它进程的请求。worker 进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与 Nginx 的进程模型以及事件处理模型是分不开的。
master 来管理 worker 进程,所以我们只需要与 master 进程通信就行了。master 进程会接收来自外界发来的信号,再根据信号做不同的事情。
例如:控制Nginx的一般命令有
-kill -HUP pid(这个命令现在较老,用处不大):告诉 Nginx,从容地重启 Nginx,我们一般用这个信号来重启 Nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master 进程在接收到 HUP 信号后是怎么做的呢?首先 master 进程在接到信号后,会先重新加载配置文件,然后再启动新的 worker 进程,并向所有老的 worker 进程发送信号,告诉他们可以光荣退休了。新的 worker 在启动后,就开始接收新的请求,而老的 worker 在收到来自 master 的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。
./nginx -s reload:重启 Nginx,新的 Nginx 进程在解析到 reload 参数后,就知道我们的目的是控制 Nginx 来重新加载配置文件了,它会向 master 进程发送信号,然后接下来的动作,就和我们直接向 master 进程发送信号一样了。
./nginx -s stop:停止 Nginx 的运行
我们知道了当我们在操作 Nginx 的时候,Nginx 内部做了些什么事情,那么,worker 进程又是如何处理请求的呢?我们前面有提到,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 进程。当然,worker 进程的异常退出,肯定是程序有 bug 了,异常退出,会导致当前 worker 上的所有请求失败,不过不会影响到所有请求,所以降低了风险。
为什么 Nginx 可以采用异步非阻塞的方式来处理呢,或者异步非阻塞到底是怎么回事呢?我们先回到原点,看看一个请求的完整过程。首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu 就会让出去给别人用了,对单线程的 worker 来说,显然不合适,当网络事件越多时,大家都在等待呢,cpu 空闲下来没人用,cpu利用率自然上不去了,更别谈高并发了。好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增加无谓的上下文切换。所以,在 Nginx 里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,马上返回 EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。所以,才会有了异步非阻塞的事件处理机制,具体到系统调用就是像 select/poll/epoll/kqueue 这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制正好解决了我们上面的两个问题,拿 epoll 为例(在后面的例子中,我们多以 epoll 为例子,以代表这一类函数),当事件没准备好时,放到 epoll 里面,事件准备好了,我们就去读写,当读写返回 EAGAIN 时,我们将它再次加入到 epoll 里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在 epoll 里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。 我之前有对连接数进行过测试,在 24G 内存的机器上,处理的并发请求数达到过 200 万。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。
我们之前说过,推荐设置 worker 的个数为 cpu 的核数,在这里就很容易理解了,更多的 worker 数,只会导致进程来竞争 cpu 资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了 cpu 亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来 cache 的失效。像这种小的优化在 Nginx 中非常常见,同时也说明了 Nginx 作者的苦心孤诣。比如,Nginx 在做 4 个字节的字符串比较时,会将 4 个字符转换成一个 int 型,再作比较,以减少 cpu 的指令数等等。
现在,知道了 Nginx 为什么会选择这样的进程模型与事件模型了。对于一个基本的 Web 服务器来说,事件通常有三种类型,网络事件、信号、定时器。从上面的讲解中知道,网络事件通过异步非阻塞可以很好的解决掉。如何处理信号与定时器?
首先,信号的处理。对 Nginx 来说,有一些特定的信号,代表着特定的意义。信号会中断掉程序当前的运行,在改变状态后,继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重入。关于信号的处理,大家可以学习一些专业书籍,这里不多说。对于 Nginx 来说,如果nginx正在等待事件(epoll_wait 时),如果程序收到信号,在信号处理函数处理完后,epoll_wait 会返回错误,然后程序可再次进入 epoll_wait 调用。
另外,再来看看定时器。由于 epoll_wait 等函数在调用的时候是可以设置一个超时时间的,所以 Nginx 借助这个超时时间来实现定时器。nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入 epoll_wait前,先从该红黑树里面拿到所有定时器事件的最小时间,在计算出 epoll_wait 的超时时间后进入 epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait 会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件。由此可以看出,当我们写 Nginx 代码时,在处理网络事件的回调函数时,通常做的第一个事情就是判断超时,然后再去处理网络事件。
nginx应用场景:
1、反向代理
反向代理应该是Nginx做的最多的一件事了,什么是反向代理呢,以下是百度百科的说法:反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。简单来说就是真实的服务器不能直接被外部网络访问,所以需要一台代理服务器,而代理服务器能被外部网络访问的同时又跟真实服务器在同一个网络环境,当然也可能是同一台服务器,端口不同而已。
在物理层面上就是一般浏览器访问网站是对接到相应的真实服务器上,可能会存在多台服务器做集群来分布式处理,这时候我们若加上Nginx作为反向代理服务器(就是搭建一台主机给Nginx用),本来浏览器发送给真实服务器的请求就会先去到Nginx服务器上,再由Nginx分发请求给不同的真实服务器,这样做,一是为了减少服务端压力(统一处理),二是为了安全(浏览器访问到的服务器只是Nginx的地址而不是真实服务器)。
反向代理的定义:反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
反向代理的基本功能就是 隐藏原始服务器地址。其解决的是安全的问题。
但目前的主流反向代理服务器还同时兼职做负载均衡,静态文件缓存和动态请求分发,压缩文件,预防DDoS攻击等功能。这些功能都在一定程度上可以提升服务器负载。
2、http服务器。Nginx是一个http服务可以独立提供http服务及其动静分离。
Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,同时现在也很流行动静分离,就可以通过Nginx来实现,首先看看Nginx做静态资源服务器。
-静态资源储存
server {
listen 80;
server_name localhost;
client_max_body_size 1024M;
location / {
root e:wwwroot;
index index.html;
}
}
这样如果访问http://localhost 就会默认访问到E盘wwwroot目录下面的index.html,如果一个网站只是静态页面的话,那么就可以通过这种方式来实现部署。
就是指定url与本地静态资源的关系,当用户发起访问,直接就可以通过地址对应到Nginx服务器下的静态资源,我们可以将网站的静态资源全都放在Nginx服务器下的储存中,可以有效减免服务器压力。
-动静分离(动态页面的动静分离)
一般来说,现在的网站大多都具备交互性,就是动态网站,可以与后台交互,静态网站多数都是一些博客和资料,静态页面是不能随时改动的,静态是一次性写好放在服务器上进行浏览的,如果想改动,必须在页面上修改,然后再上传服务器覆盖原来的页面,这样才能更新信息,比较麻烦,使用者不能随时修改。 动态页面是可以随时改变内容的,有前后台之分,管理员可以在后台随时更新网站的内容,前台页面的内容也会随之更新,比较简单易学。
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路:
upstream test{
server localhost:8080;
server localhost:8081;
}
server {
listen 80;
server_name localhost;
location / {
root e:wwwroot;
index index.html;
}
// 所有静态请求都由nginx处理,存放目录为html,静态资源和Nginx是同一台服务器,当然也可以不同
location ~ .(gif|jpg|jpeg|png|bmp|swf|css|js)$ {
root e:wwwroot; //指的是e盘符下的wwwroot目录
}
// 所有动态请求都转发给tomcat处理,jsp动态网页和.do请求
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后面其实是一个正则表达式,所以非常灵活
3.负载均衡
负载均衡也是Nginx常用的一个功能,负载均衡其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。简单而言就是当有2台或以上服务器时,根据规则随机的将请求分发到指定的服务器上处理,负载均衡配置一般都需要同时配置反向代理,通过反向代理跳转到负载均衡。而Nginx目前支持自带3种负载均衡策略,还有2种常用的第三方策略。
(1)RR(默认)即Round-Robin,轮询调度
默认的负载均衡策略,当我们项目中的服务器有多台的时候,通过Nginx内部机制将这些请求进行随机分发而不是统统将所有的客户端请求都交给一台服务器去处理,这样也充当着一个请求管理者的作用,
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
简单配置:
upstream test {
server localhost:8080;
server localhost:8081;
}
server {
listen 81;
server_name localhost;
client_max_body_size 1024M;
location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
负载均衡的核心代码为:
upstream test {
server localhost:8080;
server localhost:8081;
}
端口8080和8081分别代表两台不同的服务器,但是我们访问http://localhost 的时候,也不会有问题,会默认跳转到http://localhost:8080 具体是因为Nginx会自动判断服务器的状态,如果服务器处于不能访问(服务器挂了),就不会跳转到这台服务器,所以也避免了一台服务器挂了影响使用的情况,由于Nginx默认是RR策略,所以我们不需要其他更多的设置。
(2)权重,基于RR
可以设置轮询哪个服务器的几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
例如项目中使用的服务器集群中存在性能差异,为了避免性能分配不均和利用率,我们可以设置RR权重。
例如:
upstream test {
server localhost:8080 weight=9;
server localhost:8081 weight=1;
}
那么10次一般只会有1次会访问到8081,而有9次会访问到8080
(3)ip_hash(见名知意,就是根据浏览器IP的Hash值)
上面的2种方式都有一个问题,那就是下一个请求来的时候请求可能分发到另外一个服务器,当我们的程序不是无状态的时候(采用了session保存数据),这时候就有一个很大的很问题了,比如把登录信息保存到了session中,那么跳转到另外一台服务器的时候就需要重新登录了,所以很多时候我们需要一个客户只访问一个服务器,那么就需要用iphash了,iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
如果利用Redis保存全局Session还会用到这个功能吗?
upstream test {
ip_hash;
server localhost:8080;
server localhost:8081;
}
(4)fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。(可以根据减少服务器不必要时间的浪费)
upstream backend {
fair;
server localhost:8080;
server localhost:8081;
}
(5)url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法。
upstream backend {
hash $request_uri;
hash_method crc32;
server localhost:8080;
server localhost:8081;
}
以上5种负载均衡各自适用不同情况下使用,所以可以根据实际情况选择使用哪种策略模式,不过fair和url_hash需要安装第三方模块才能使用。
4.正向代理
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。当你需要把你的服务器作为代理服务器的时候,可以用Nginx来实现正向代理,但是目前Nginx有一个问题,那么就是不支持HTTPS,虽然我百度到过配置HTTPS的正向代理,但是到最后发现还是代理不了,当然可能是我配置的不对,所以也希望有知道正确方法的同志们留言说明一下。
resolver 114.114.114.114 8.8.8.8;
server {
resolver_timeout 5s;
listen 81;
access_log e:wwwrootproxy.access.log;
error_log e:wwwrootproxy.error.log;
location / {
proxy_pass http://$host$request_uri;
}
}
resolver是配置正向代理的DNS服务器,listen 是正向代理的端口,配置好了就可以在ie上面或者其他代理插件上面使用服务器ip+端口号进行代理了。
Nginx支持热启动
Nginx是支持热启动的,也就是说当我们修改配置文件后,不用关闭Nginx,就可以实现让配置生效,当然我并不知道多少人知道这个,反正我一开始并不知道,导致经常杀死了Nginx线程再来启动。。。Nginx从新读取配置的命令是
即nginx -s reload
windows下面就是:nginx.exe -s reload