Nginx是一个Web服务器程序提供的开源解决方案,它既是一个Web Server同时又是一个著名的web proxy称为web的代理。可是做为代理来说,它更多的应用场景是在反向代理上面,因此咱们有时候将其称为web reverse proxy。html
在讲Nginx以前,上面已经强调了两个术语,一个是web server一个是web reverse proxy。因此简要回顾以前讲过的web服务。 web
若是不考虑咱们说过的lamp的话,只说一个web服务器,那么任什么时候候一个web服务器无非就是可以提供http应用层协议,可以基于所谓的请求响应报文完成Web事务的这么一个服务应用程序。像咱们此前讲到的Apache就是一个很是著名的表明。编程
对于Web服务来说,它的最核心的就是http协议。浏览器
默认状况下工做在tcp的80端口,其全称为HyperText Transfer Protocol(超文本传输协议)。他主要就是传输超文本的,超文本就是由HTML语言所开发的文本或者拥有HTML标记的文本,那么HTML又指的是什么呢?缓存
HTTP:HyperText Mark Language 超文本标记语言,这也是一种开发语言。服务器
各位必定要注意的是,html所可以传输的内容是文本内容。早期http/0.9也只能传输文本,可是从http/1.0后,他引入了MIME机制,从而可以实现对多媒体内容的支持的。多线程
MIME:Multipurpose Internet Mail Extension 多功能互联网邮件扩展。架构
MIME实际上是早期smtp引入进来的一种用于实现基于文本格式的smtp协议传输非文本信息的一种将非文本信息编码成文本,并且在接收方接收完成以后又可以还原为原有媒体格式的这么一种编码方案。在http/1.0后,它被引入了http,因此从而使得http协议也可以支持多媒体内容的传输了。那么MIME在实现其内容类型标记时,有两种符号,它有主类型(major)和次类型(minor)之分,好比像文本中的纯文本信息text/plain,再好比说像jpeg格式的图片下载image/jpeg等等相似于这种格式,因此使得咱们浏览器接收到相关的对应内容之后,知道使用何种应用程序来解析此类内容以及如何进行还原的。并发
这就是MIME,也正是MIME机制的引入才使得它可以传输非文本信息的。异步
你们又应该可以知道,互联网上的WEB服务器成千上万,数以万亿计。那么众多的服务器上的每个服务器都有可能有着许多资源,那么咱们如何去访问每个资源,如何去定位每个资源,这靠的是URL的标记。
URL的基本语法:
schema(协议)://server[:port]/path/to/source
经过URL来标记众多资源,所以互联网上的每个资源都应该至少有一个标识,有些资源标识的路径可能不止一个,是由于咱们服务器的名字可能不止一个。可是每个资源都一个可以被某一个URL所惟一的进行标识。
可是当咱们的用户代理(User Agent)跟咱们的服务器端进行交互时,通常而言用户代理是一个浏览器,它跟咱们的服务器之间须要经过相应的报文格式来完成其对应的资源交互的。
因此咱们说,这一次请求响应过程叫一个http事务,咱们有请求报文叫作request,响应报文叫作response组成。对一个事务而言,他无非就是请求、响应、请求、响应,不断的来经过这种方式进行。请求通常而言是由用户代理发起的,响应则由服务器端进行响应,那么为了可以让两者的请求响应可以通讯,他们要借助于底层的TCP/IP通讯子网完成通讯,但http本身的工做流程则在应用层协议http协议的响应报文中得以维持和包含。
各位是否记得请求报文的报文格式(请求报文的http首部的格式)是什么?
request:
<method> <URL> <version>
<HEADERS>
<body>
响应报文(response)格式:
<version> <status> <reason phrase>
<HEADERS>
<body>
须要注意的是,咱们的应用层协议格式一般有两种:
文本格式通常来说要麻烦一点,但容易开发,它的交互过程和解析要较为麻烦。使用文本格式的协议像smtp协议,http协议等都是较为著名的。
二进制格式解析简单可是理解起来却没那么直观。使用二进制格式的协议像后续的不少其它协议像memacached等它们彼此之间互相通讯的协议有可能用的是二进制格式。
协议格式取决于那个应用层协议的设计者是如何去归类,如何去理解协议的。
在http事务中咱们提到了这几个基本概念,URL已经解释过了,那METHOD是什么?HEADERS有哪些?STATUS常见的有哪些呢?咱们再作一次梳理和回顾。
method:(几个最为常见也是最基本的method,这是常识!!!)
status,响应的状态码:
每一种状态码都应该有一个与之对应的reason phrase(缘由短语),用于描述他为何会发生这种相关信息。
HEADER:
特定的两个条件式请求:If-Modified-Since、If-None-Match;
未来在讲到反向代理的缓存功能时,基于反向代理实现Web服务请求的缓存功能时,这项是相当重要的。在讲到对应的WEB系统构建的时候,各位要了解,缓存在现今的互联网时代是相当重要的一个组件,有一句话叫作Cache is king(缓存为王的时代);如今互联网在实现加速不少组件彼此之间的结合度或者其速率是不匹配的,甚至是相去甚远的,那在这种场景当中,咱们如何去实现让整个系统的响应性能更好,通常而言都是在速度不匹配的组件之间添加缓存来实现。而If-Modified-Since、If-None-Match在Web Cache方面用处仍是比较大的。
咱们又说过,对于任何一个Web服务器的Web页面而言,用户可以直接请求的一般都是某一个页面而不会是某一个图片。在Web上每个资源都有本身的独有的URL,不管是图片、声音、甚至影像这些资源都有。可是任什么时候候咱们都不多直接打开一个单个的图片或者一个影像资源而是经过某个Web页面去载入后在某一页面的某个位置来进行显示,因此咱们说过不少次,一个页面当中一般由多个资源组成,不多像咱们本身写的测试页同样。因此大多数的WEB页面都由多个资源组成。
因此浏览器加载一个页面的以后页面中极可能有众多资源组成,那这些资源有多是来自于同一个服务器也有可能来自于不一样服务器,但不管来自于哪些服务器,这个浏览器在通过分析后必须把引用到的每个资源都加载到本机,说白了就是把每个都要从新单独请求一次,然后才可以予以完整展现的,因此,此时浏览器为了尽量快速的去加载这些内容,它其实引入了两种机制,一种机制是浏览器本身有缓存称为private cache,是浏览器本身的私有缓存,也就意味着说若是此前曾经访问过这些页面,里边有些内容是静态生成的话,即使是动态生成它也容许缓存的话,这个时候会把这些内容缓存在浏览器所在的这个主机的浏览器本身的私有缓存空间当中,从而使得第二次再请求一样的内容时,它就能够发出条件式请求或者是若是发现资源未过时就直接使用本地的内容了。由于像Google或百度这样的网站,它们有可能会把某一个资源的缓存时长调整为1年,即1年以内都是有效的。
这是第一点,咱们经过缓存来加速打开资源的过程,不会像真正服务器发请求而是用到本地缓存。第二种是因为这里的资源引用可能过多,所以浏览器则有可能会多线程并行发请求,好比:咱们浏览器有的是双线程的,有的是四线程,甚至有的是8线程的。若是咱们如今的主机大多数都是多核心的话,那么启动多个线程同时去加载资源也是能够的,只要带宽足够可用,也是一种的的确确可以提高其加载速度的一种方案。因此这个浏览器分析之后发现有众多资源,则有可能同时,第一次发起请求时它可能只请求一个资源,但第二次再次请求里边所引用到的资源时则有可能根据浏览器本身工做的线程模式同时发起两个请求,那这个时候最多请求到两个资源,这两个请求再发起两个请求后面两个资源,依次类推,指到全部资源都加载完后整个页面才能打开。
做为服务器来说,它有可能同时接收到n个请求进来,可是n个请求并非同时对应n个用户,由于任何一个用户的主机它有可能打开一个浏览器进程,而这个浏览器进程可能会并行发起多个线程同时进行请求。因此每个链接未必对应一个用户,由于它的双线程每个线程都会发起一个单独的请求,这点须要注意。
对于服务器端来说,他有可能承载的链接数量跟请求的用户个数自己并无一一对应关系。并且对于一个很是繁忙的站点来说,同时发起请求的用户数量不少,虽然第一次请求时,只是发一个请求但随后可能会有大量请求随之都涌过来了,这时候服务器可能会承受很大的并发压力,因此后来一直讲,对于有些站点来说,它们承载的用户数量多是很是大的,这就要求一个服务器所可以承载的请求量是很是大的。
事实上如今的互联网站点为了应付这样的请求,它们有各类各样的加载优化方式。不过无论怎么优化,单台服务器所可以面临的并发用户请求总量总然是有限的,所以在后期会提到用户量很是大时,并发能够达到几万个级别的时候,甚至几十万级别时,如何扩展这个架构,让更多的用户接进来提供服务等等。
那在统计PV是统计的是什么?是否是每个资源请求都是一个PV?显然不是,对于这整个主页的浏览,它可能加载n个资源可是这只能算一次的页面浏览量,所以咱们的站点上有多少个页面入口,咱们在统计PV的时候只能统计多少个PV。说白了,在同一个时间内来自于同一个用户的刷新不能被统计为两个PV,由于他仍然是同一个。
pv:page view
其统计方式应该是这整个页面中虽然由众多页面组成每个资源都要单独请求经过日志分期请求的pv时,只能把这个入口的请求当作是PV,然后续的实际上是对这个入口所引用的每个资源再次请求以达到彻底展现的目的。而咱们的整个网站有多少个能够做为入口单独请求的页面咱们本身应该是心中有数的,所以咱们在实现日志分析来实现PV统计的时候,它也必然是有一套方案在里头,可是如今有各类各样的站长统计工具可以帮助咱们去统计的,不过对于大型站点来说必然是要作自行分析的。用日志分析来分析、判断用户行为甚至是作有的放矢的广告推广这都是一个基本的能力,在不少像电商站点或者是社交站点,它们都在作用户行为分析、作数据挖掘、甚至于说用户行为量很大的话,可能作一年的用户行为分析可能还要构建大数据分析平台。若是要作实时分析的话,还要作流式数据实时分析平台。这其中包含了许许多多复杂的组件,这也是一个很是复杂的生态。
除了pv这样的术语以外,偶尔还可能用到uv这样的概念。
uv:user view
咱们在统计的时候并非真正按照用户名来进行统计的,由于有些站点原本访问的是匿名的,它不可能可以根据用户名进行统计,那怎么去统计uv呢?那就须要经过独立IP来统计了。如日pv与日uv就是一天内咱们的页面浏览量有多少个,一天内有多少个独立的IP地址对咱们的网站发起了请求和访问。那也有月pv、月uv等等几个术语。
刚才咱们提到过,咱们为了可以帮助客户端打开页面时尽量可以快速的打开页面,可以有较好的用户体验,一般有两种常见的方案:引入缓存、并行请求。如今的完成的成熟的浏览器大多数都是支持多线程的,但这种多线程须要提醒的是任何一个浏览器对于单个域名来说,它的多线程是有上限的,就像刚才所讲,浏览器能够支持两线程、能够支持四线程,这种两线程和四线程是相对于单个域名而言的。也就是说咱们打开一个浏览器后,如今浏览器可以支持标签式浏览,咱们访问a网站它会打开两个线程去加载a网站的资源,咱们又同时打开一个标签页面访问b网站,那么对于b网站来说,他也会同时打开两个线程去加载b网站上的内容。那因而有些网站为了优化用户体验,有可能在同一个网站上它们会使用不一样的域名,好比说打开主站时使用的是www.a.com,而主站内部对于这些图片的引用则有可能在另一个域名下的,好比图片就有可能有一个www.image.com,若是是视频的话就在www.video.com这样的域名下,这样就使得若是在同一个网站的页面上咱们本身做为网站拥有方注册了n个域名,分别将图片放在了一个域名下,把视频放在了域名下,把文本放在了一个域名下。这样客户端浏览器在加载时对于单个域名均可以启动两个线程,那所以一个页面中若是有80个资源的话而咱们又使用了四个域名这就意味着它同时能够启动8线程同时加载,这也是网站打开速度优化的一种方案或思路。
所以浏览器自身的限制是针对于单个域名作限制的,它最终能打开几个线程。而对于多个域名来说,每个域名均可以同时打开多个线程同时访问的。因此这就是为何不少站点上它们不一样的资源却发现彻底是属于不一样域名的缘由。并非说不属于不一样域名它就盗用了别人的,而是说它有多是同一个网站上为了实现用户加载时加速策略而有意为之的。
这是咱们在提到多个WEB页面资源加载时所讲到的概念,咱们在这里提到了页面的访问入口以及资源引用的概念以及浏览器在引用时的多线程、浏览器应用缓存来及进行加速等相关概念。
Web服务器认证:
WEB服务器在使用时还能够作认证的,认证有两种方式:
基于用户认证又分为两种:
而用户访问一个须要认证的资源时服务器端可能会返回一个特殊的状态码以实现认证质询,要求客户端自行必需要打开浏览器弹出一个对话框输入帐号密码之后再次向服务器端确认后才能得到资源。
在Web服务器中还有所谓的资源映射的概念,什么叫资源映射呢?好比用户经过浏览器输入URL所访问的每个资源有可能基于DocmentRoot指定了本地文件系统下某个位置的路径。这就是一种映射,还有像Alias即路径别名,这也是资源映射的方案。
httpd的MPM:
再回顾一下httpd,它有本身的MPM(多道处理模块),MPM在Linux主机上有三种类型:
prefork的工做方式:有一个主进程,主进程生成多个子进程,然后每一个子进程处理一个请求;主进程是以管理员的身份启动的,因此它可以监听在80端口上。此前说过,端口小于1024的称为特权端口只有管理员才有权限使用的。这就是为何它可以监听在特权端口上,然后又可以以普通用户运行的缘由。
有一个主进程,主进程生成多个子进程,然后每个子进程再生成多个线程,每一个线程响应一个请求;
有一个主进程,生成多个子进程,能够理解为每个子进程响应多个请求,也能够理解为生成多个线程。在Linux系统上,子进程与线程并无严格意义上的区分。而event模型当中最典型的特性就是被称为的事件驱动机制。
那什么叫事件驱动机制呢?接下来就说一下I/O模型;
I/O类型:
I/O的类型从不一样的角度来划分,它有两种不一样的方式:
那什么是同步什么是异步?首先同步其实更关注的是消息通知机制,说白了就是如何通知调用者的,能够这么理解,IO无非就是一方可以提供服务一方须要调用别人的服务,因此IO请求就是调用方向被调用方运行一个库调用或函数调用或系统调用。假如说是一个系统调用,因此调用方向被调用方发起系统调用请求,被调用方本地要把这个内容给它运行完成,因此在本地要作处理,处理结束了,把处理的结果响应给调用方。问题是调用方何时知道本身的请求结束了呢?本身的请求对方响应了呢?因此就有同步和异步两种模式,所谓同步指的是调用发出以后不会当即返回,但一旦返回,则返回的便是最终结果。那异步指的就是调用发出以后,被调用方当即返回消息,但返回的并不是最终结果;被调用者最后经过状态、通知机制等来通知调用者,或经过回调函数来处理结果。
调用者一旦发出调用之后被调用者不会当即给予响应,有可能对方已经收到调用请求了,那因而去处理,处理到最后,结果才返回过去。这种称为同步调用,以下图:
异步调用就是当调用者发出请求后,被调用者当即就告诉调用者请求已收到,须要等待一段时间。这就是当即返回结果但不是最终结果,当对方该处理这个请求时,处理完成后才再次通知调用者。以下图:
阻塞和非阻塞其实在同步和异步上比较难以区分开来,由于它们在所实现的意义描述上用汉语进行描述可能并非特别能体会到它们之间的区别。阻塞和非阻塞关注的是调用者等待被调用者返回调用结果时(便是这个中间过程)的状态;
阻塞指的是调用结果返回以前,调用者会被挂起(所谓挂起就是有可能转为不可中断睡眠状态);调用者只有在获得返回结果以后才能继续。
注意:异步和同步关注的是消息是如何通知的,关注的是消息通知机制,说白了就是被调用者如何把调用已经完成的结果通知给调用者。而阻塞则关注的是调用者的状态。同步和异步关注的是被调用者如何把调用结果拿到之后通知给调用者;一个关注的是调用者本身在发出调用之后本身是如何等待结果的。
而对于阻塞而言,调用结果返回以前,它怎么等待?在本身所指望请求的事没结束以前,再也不干其它事,因此这就一直处于等待状态。
非阻塞指的是调用者在结果返回以前,不会被挂起,即调用不会被阻塞调用者。
举例:在吃面时,等待面好的过程中有两种不一样的选择,一种是就在面馆等待,其它什么事也不干。另外一种是,在等待面好的过程中,能够再去作其它事,当以为面快要好的时候再去面馆。
因此这就是所谓的I/O模型,经过两种不一样的角度划分它有同步/异步、阻塞/非阻塞这几种类型,而这几种类型当中,同步/异步和阻塞/非阻塞看上去在有些地方很相像,但事实上它们一个关注的是调用者如何等待结果,一个关注的是被调用者如何通知调用者结果已完成的。因此它们压根就不是一回事。
站在这个角度划分的话,I/O其实能够分为五种模型。目前来说,经常使用的I/O模型有五种。
经常使用的五种I/O模型:
select(),poll()
引入了通知机制,有两种通知方案:
指的是通知一次你没来处理,我再通知一次,没处理再通知一次直处处理为止。屡次通知虽然看上去更可靠了,更有保证了,可是一遍一遍通知资源就被浪费了;
若是对方没有过来接收,能够将这个通知事件经过回调函数让调用者自行获取或者将通知信息放在某处;
为了可以解释IO,咱们经过磁盘IO来进行解释。
注意:下述概念特别关键,对于后续但凡提到的IO时都应该有这么一个直观印象。
咱们就以read为例,例如:从磁盘上作一次read操做;
一次read操做大致上由两步组成:
此前说过,用户空间的进程是没有权限直接访问硬件的。当某一个应用程序或者一个进程发起IO调用时,这个IO大致上分为两步。它须要向内核请求说要读取某数据,由于它无法直接访问硬盘,所以接下来要由内核完成,这就是一次IO调用,可是这个调用大致由两步组成。当它发起请求以后,内核收到请求以后,内核本身没有数据的。数据在磁盘上,所以内核要怎么处理呢?
第一步,内核要把数据从磁盘加载至内存中。内核能直接访问用户空间的进程,可是通常不建议让它直接访问,因此内核加载这个数据至内核本身的内存空间中,注意叫内核内存。
可是咱们说过,加载至内核内存,即使是从磁盘加载完了,这个进程也是不能访问到的,咱们不可能让进程直接访问内核内存。
第二步,将这个数据从内核内存复制一份到进程内存中。
因此它由两步组成,即一次IO调用发起请求后,它要等待两个阶段,第一阶段,数据要从磁盘到内核内存,第二步,数据要从内核内存复制一次到进程内存。由于咱们的进程有多个因此这个进程内存必定是进程本身所特定的内存空间。两个进程之间通常而言它不能去共享这些数据的。
因此再次说明,一个进程发起IO调用后,一次IO将有两个阶段组成,此处指的是磁盘IO。而这个过程中真正被称为叫IO的那一步,其实就是第二步,由于底下那个过程只是咱们内核本身处理数据的过程。真正被称为IO的步骤是数据从内核内存到进程内存的过程,或者才是真正执行IO过程的阶段。
prefork和worker用到的都是复用型IO,因此它们的并发能力颇有限,select()最多只能1024个。而event则用到的是事件驱动IO,事件驱动IO从本质上来说,它是一个进程直接响应n个请求的。事件驱动型IO在实现IO处理时依然有可能会被阻塞,它只是第一段没有被阻塞,可是第二段有可能被阻塞的,因此性能会比较低。
因此在事件驱动式IO的基础上再一次进行改进,就出现了异步式IO。虽然事件驱动式IO和异步式IO对于并发能力支持较好,可是其编程复杂度也是较高的。
这就是为何基于后面两种IO模型来提供服务的Web服务器程序其性能较好的缘由。Nginx在最初设计时用到的就是事件驱动型IO,并且基于边缘触发来实现。更重要的是,Nginx还支持异步IO,并且他还能基于内存映射(MMAP)机制来完成数据的发放。因此Nginx是尽量用到了近些年最新的服务器端编程技术来支持较好的并发。
五种IO的比较: