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的比較: