1. 程式人生 > >WEB服務訪問的過程

WEB服務訪問的過程

先說說執行緒、程序、以及併發連線數,在說Web伺服器

1.程序與執行緒

        程序是具有一定獨立功能的程式,關於某個資料集合上的一次執行活動,程序是系統進行資源分配和排程的一個獨立單位。從邏輯角度來看,多執行緒的意義在於一個應用程式(程序)中,有多個執行部分可以同時執行。但作業系統並沒有將多個執行緒看做多個獨立的應用來實現,而是作為程序來排程和管理以及資源分配。這就是程序和執行緒的重要區別,程序和執行緒的主要差別在於,程序有獨立的地址空間,一個程序崩潰後,在保護模式下不會對其它程序產生影響,而執行緒只是一個程序中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程序死掉,所以多程序的程式要比多執行緒的程式健壯,但在程序切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程序。下面我們來說一說併發連線數。

2.併發連線數

(1).什麼是最大併發連線數呢?

         最大併發連線數是伺服器同一時間能處理最大會話數量。

(2).何為會話?

       我們開啟一個網站就是一個客戶端瀏覽器與服務端的一個會話,而我們瀏覽網頁是基於http協議。

(3).HTTP協議如何工作?

       HTTP支援兩種建立連線的方式:非持久連線持久連線(HTTP1.1預設的連線方式為持久連線)。

(4).瀏覽器與Web伺服器之間將完成下列7個步驟

  • 建立TCP連線

  • Web瀏覽器向Web伺服器傳送請求命令

  • Web瀏覽器傳送請求頭資訊

  • Web伺服器應答

  • Web伺服器傳送應答頭資訊

  • Web伺服器向瀏覽器傳送資料

  • Web伺服器關閉TCP連線

       一般情況下,一旦Web伺服器向瀏覽器傳送了請求資料,它就要關閉TCP連線,但是瀏覽器一般其頭資訊加入了這行程式碼 Connection:keep-alive,TCP連線在傳送後將仍然保持開啟狀態,於是,瀏覽器可以繼續通過相同的連線傳送請求。保持連線目的,節省了為每 個請求建立新連線所需的時間,還節約了網路頻寬。

3.併發連線數的計算方法

使用者下載伺服器上的檔案,則為一個連線,使用者檔案下載完畢後這個連線就消失了。有時候使用者用迅雷的多執行緒方式下載的話,這一個使用者開啟了5個執行緒的話,就算是5個連線。

        使用者開啟你的頁面,就算停留在頁面沒有對伺服器發出任何請求,那麼在使用者開啟一面以後的15分鐘內也都要算一個線上。

        上面的情況使用者繼續開啟同一個網站的其他頁面,那麼線上人數按照使用者最後一次點選(發出請求)以後的15分鐘計算,在這個15分鐘內不管使用者怎麼點選(包括新視窗開啟)都還是一人線上。

         當用戶開啟頁面然後正常關閉瀏覽器,使用者的線上人數也會馬上清除。

二、Web伺服器提供服務的方式

       Web伺服器由於要同時為多個客戶提供服務,就必須使用某種方式來支援這種多工的服務方式。一般情況下可以有以下三種方式來選擇,多程序方式多執行緒方式非同步方式。其中,多程序方式中伺服器對一個客戶要使用一個程序來提供服務,由於在作業系統中,生成一個程序需要程序記憶體複製等額外的開銷,這樣在客戶較多時的效能就會降低。為了克服這種生成程序的額外開銷,可以使用多執行緒方式或非同步方式。在多執行緒方式中,使用程序中的多個執行緒提供服務, 由於執行緒的開銷較小,效能就會提高。事實上,不需要任何額外開銷的方式還是非同步方式,它使用非阻塞的方式與每個客戶通訊,伺服器使用一個程序進行輪詢就行了。

       雖然非同步方式最為高效,但它也有自己的缺點。因為非同步方式下,多個任務之間的排程是由伺服器程式自身來完成的,而且一旦一個地方出現問題則整個伺服器就會出現問題。因此,向這種伺服器增加功能,一方面要遵從該伺服器自身特定的任務排程方式,另一方面要確保程式碼中沒有錯誤存在,這就限制了伺服器的功能,使得非同步方式的Web伺服器的效率最高,但功能簡單,如Nginx伺服器

       由於多執行緒方式使用執行緒進行任務排程,這樣伺服器的開發由於遵從標準,從而變得簡單並有利於多人協作。然而多個執行緒位於同一個程序內,可以訪問同樣的記憶體空間,因此存線上程之間的影響,並且申請的記憶體必須確保申請和釋放。對於伺服器系統來講,由於它要數天、數月甚至數年連續不停的運轉,一點點錯誤就會逐漸積累而最終導致影響伺服器的正常運轉,因此很難編寫一個高穩定性的多執行緒伺服器程式。但是,不是不能做到時。Apache的worker模組就能很好的支援多執行緒的方式。

       多程序方式的優勢就在於穩定性,因為一個程序退出的時候,作業系統會回收其佔用的資源,從而使它不會留下任何垃圾。即便程式中出現錯誤,由於程序是相互隔離的,那麼這個錯誤不會積累起來,而是隨著這個程序的退出而得到清除。Apache的prefork模組就是支援多程序的模組。

三、多程序、多執行緒、非同步模式的對比

     Web伺服器總的來說提供服務的方式有三種,多程序方式,多執行緒的方式,非同步方式。其中效率最高的是非同步的方式,最穩定的是多程序方式,佔用資源較少的是多執行緒的方式。

1.多程序

       此種架構方式中,web伺服器生成多個程序並行處理多個使用者請求,程序可以按需或事先生成。有的web伺服器應用程式為每個使用者請求生成一個單獨的程序來進行響應,不過,一旦併發請求數量達到成千上萬時,多個同時執行的程序將會消耗大量的系統資源。(即每個程序只能響應一個請求或多個程序對應多個請求)

優點:

  • 最大的優勢就在於穩定性,一個程序出錯不會影響其它程序。如,伺服器同時連線100個請求對就的是100個程序,其中一個程序出錯,只會殺死一個程序,還有99個程序繼續響應使用者請求。每個程序響應一個請求

缺點:

  • 程序量大,程序切換次數過多,導致CPU資源使用效率低,每個程序的地址空間是獨立的,很多空間中重複的資料,所以記憶體使用效率低,程序切換由於核心完成,佔用CPU資源。

2.多執行緒

       在多執行緒方式中,每個執行緒來響應一下請求,由於執行緒之間共享程序的資料,所以執行緒的開銷較小,效能就會提高。

優點:

  • 執行緒間共享程序資料,每個執行緒響應一個請求,執行緒切換不可避免(切換量級比較輕量),同一程序的執行緒可以共享程序的諸多資源,對記憶體的需求較之程序有很大下降,讀可以共享,寫不可以共享

缺點:

  • 執行緒快速切換時會帶來執行緒抖動,多執行緒會導致伺服器不穩定

3.非同步方式

       一個程序或執行緒響應多個請求,不需要任何額外開銷的,效能最高,佔用資源最少。但也有問題一但程序或執行緒出錯就會導致整個伺服器的宕機。

四、Web 服務請求過程

20130830100310571

       在上面的講解中我們說明,Web伺服器的如何提供服務的,有多程序的方式、多執行緒的方式還有非同步方式我們先簡單這麼理解,後面我們慢慢說,現在我們不管Web伺服器是如何提供服務的,多程序也好、多執行緒好,非同步也罷。下面我們來說一下,一個客戶端的具體請求Web服務的具體過程,從上圖中我們可以看到有11步,下面我們來具體說一下,

  • 1.首先我們客戶端傳送一個請求到Web伺服器,請求首先是到網絡卡。2.網絡卡將請求交由核心空間的核心處理,其實就是拆包了,發現請求的是80埠。3.核心便將請求發給了在使用者空間的Web伺服器,Web伺服器接受到請求發現客戶端請求的index.html頁面。4.Web伺服器便進行系統呼叫將請求發給核心。5.核心發現在請求的是一頁面,便呼叫磁碟的驅動程式,連線磁碟。6.核心通過驅動呼叫磁碟取得的頁面檔案。7.核心將取得的頁面檔案儲存在自己的快取區域中便通知Web程序或執行緒來取相應的頁面檔案。8.Web伺服器通過系統呼叫將核心快取中的頁面檔案複製到程序快取區域中。9.Web伺服器取得頁面檔案來響應使用者,再次通過系統呼叫將頁面檔案發給核心。10.核心程序頁面檔案的封裝並通過網絡卡傳送出去。11.當報文到達網絡卡時通過網路響應給客戶端

簡單來說就是:使用者請求-->送達到使用者空間-->系統呼叫-->核心空間-->核心到磁碟上讀取網頁資源->返回到使用者空間->響應給使用者。上述簡單的說明了一下,客戶端向Web服務請求過程,在這個過程中,有兩個I/O過程,一個就是客戶端請求的網路I/O,另一個就是Web伺服器請求頁面的磁碟I/O。 下面我們就來說說Linux的I/O模型。

五、Linux I/O 模型

1.I/O模型分類

說明:我們都知道web伺服器的程序響應使用者請求,但無法直接操作I/O裝置,其必須通過系統呼叫,請求kernel來協助完成I/O動作,如下圖:20130830100310352

      對於資料輸入而言,即等待(wait)資料輸入至buffer需要時間,而從buffer複製(copy)資料至程序也需要時間  
根據等待模式不同,I/O動作可分為五種模式。

  • 1.阻塞I/O,2.非阻塞I/O,3.I/O複用(select和poll),4.訊號(事件)驅動I/O(SIGIO),5.非同步I/O(Posix.1的aio_系列函式)

2.I/O模型的相關術語

(1).阻塞和非阻塞:

       阻塞和非阻塞指的是執行一個操作是等操作結束再返回,還是馬上返回。比如你去車站接朋友,這是一個操作。可以有兩種執行方式。第一種,你這人特實誠,老早就到了車站一直等到車來了接到朋友為止。第二種,你到了車站,問值班的那趟車來了沒有,“還沒有”,你出去逛一圈,可能過會回來再問。第一種就是阻塞方式,第二種則是非阻塞的。我認為阻塞和非阻塞講得是做事方法,是針對做事的人而言的。

(2).同步和非同步:

       同步和非同步又是另外一個概念,它是事件本身的一個屬性。比如老闆讓你去搬一堆石頭,而且只讓你一個人幹,你只好自己上陣,最後的結果是搬完了,還是你砸到腳了,只有搬完了你才知道。這就是同步的事件。如果老闆還給你個小弟,你就可以讓小弟去搬,搬完了告你一聲。這就變成非同步的了。其實非同步還可以分為兩種:帶通知的和不帶通知的。前面說的那種屬於帶通知的。有些小弟幹活可能主動性不是很夠,不會主動通知你,你就需要時不時的去關注一下狀態。這種就是不帶通知的非同步。 對於同步的事件,你只能以阻塞的方式去做。而對於非同步的事件,阻塞和非阻塞都是可以的。非阻塞又有兩種方式:主動查詢和被動接收訊息。被動不意味著一定不好,在這裡它恰恰是效率更高的,因為在主動查詢裡絕大部分的查詢是在做無用功。對於帶通知的非同步事件,兩者皆可。而對於不帶通知的,則只能用主動查詢。

(3).I/O

       回到I/O,不管是I還是O,對外設(磁碟)的訪問都可以分成請求和執行兩個階段。請求就是看外設的狀態資訊(比如是否準備好了),執行才是真正的I/O操作。在Linux 2.6之前,只有“請求”是非同步事件,2.6之後才引入AIO把“執行”非同步化。別看Linux/Unix是用來做伺服器的,這點上比Windows落後了好多,IOC(Windows上的AIO)在Win2000上就有了。

(4).總結

      Linux上的前四種I/O模型的“執行”階段都是同步的,只有最後一種才做到了真正的全非同步。第一種阻塞式是最原始的方法,也是最累的辦法。當然累與不累要看針對誰。應用程式是和核心打交道的。對應用程式來說,這種方式是最累的,但對核心來說這種方式恰恰是最省事的。還拿接人這事為例,你就是應用程式,值班員就是核心,如果你去了一直等著,值班員就省事了。當然現在計算機的設計,包括作業系統,越來越為終端使用者考慮了,為了讓使用者滿意,核心慢慢的承擔起越來越多的工作,IO模型的演化也是如此。非阻塞I/O ,I/O複用,訊號驅動式I/O其實都是非阻塞的,當然是針對“請求”這個階段。非阻塞式是主動查詢外設狀態。I/O複用裡的select,poll也是主動查詢,不同的是select和poll可以同時查詢多個fd(檔案控制代碼)的狀態,另外select有fd個數的限制。epoll是基於回撥函式的。訊號驅動式I/O則是基於訊號訊息的。這兩個應該可以歸到“被動接收訊息”那一類中。最後就是偉大的AIO的出現,核心把什麼事都幹了,對上層應用實現了全非同步,效能最好,當然複雜度也最高。

六、Linux I/O 模型具體說明

         首先我們先來看一下,基本 Linux I/O 模型的簡單矩陣,從圖中我們可以看到的模型有,同步阻塞I/O(阻塞I/O)、同步非阻塞I/O(非阻塞I/O )、非同步阻塞I/O(I/O複用),非同步非阻塞I/O(有兩種,訊號驅動I/O和非同步I/O)。好了現在就來具體說一說吧。

20130830100311898

1.阻塞I/O

說明:應用程式呼叫一個IO函式,導致應用程式阻塞,等待資料準備好。 如果資料沒有準備好,一直等待資料準備好了,從核心拷貝到使用者空間,IO函式返回成功指示。這個不用多解釋吧,阻塞套接字。下圖是它呼叫過程的圖示:(注,一般網路I/O都是阻塞I/O,客戶端發出請求,Web伺服器程序響應,在程序沒有返回頁面之前,這個請求會處於一直等待狀態)

20130830100312494

2.非阻塞I/O

       我們把一個套介面設定為非阻塞就是告訴核心,當所請求的I/O操作無法完成時,不要將程序睡眠,而是返回一個錯誤。這樣我們的I/O操作函式將不斷的測試資料是否已經準備好,如果沒有準備好,繼續測試,直到資料準備好為止。在這個不斷測試的過程中,會大量的佔用CPU的時間,所有一般Web伺服器都不使用這種I/O模型。具體過程如下圖:

20130830100314195

3.I/O複用(select和poll)

        I/O複用模型會用到select或poll函式或epoll函式(Linux2.6以後的核心開始支援),這兩個函式也會使程序阻塞,但是和阻塞I/O所不同的的,這兩個函式可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函式進行檢測,直到有資料可讀或可寫時,才真正呼叫I/O操作函式。具體過程如下圖:

20130830100317719

4.訊號驅動I/O(SIGIO)

       首先,我們允許套介面進行訊號驅動I/O,並安裝一個訊號處理函式,程序繼續執行並不阻塞。當資料準備好時,程序會收到一個SIGIO訊號,可以在訊號處理函式中呼叫I/O操作函式處理資料。具體過程如下圖:

20130830100320472

綜上可以看出,越往後,阻塞越少,理論上效率也是最優。其五種I/O模型中,前三種屬於同步I/O,後兩者屬於非同步I/O。

同步I/O:

  • 1.阻塞I/O,2.非阻塞I/O,3.I/O複用(select和poll)     

非同步I/O:

  • 1.訊號驅動I/O(SIGIO) (半非同步),2.非同步I/O(Posix.1的aio_系列函式) (真正的非同步)

非同步 I/O 和 訊號驅動I/O的區別:

  • 訊號驅動 I/O 模式下,核心可以複製的時候通知給我們的應用程式傳送SIGIO 訊息。非同步 I/O 模式下,核心在所有的操作都已經被核心操作結束之後才會通知我們的應用程式。

七、Linux I/O模型的具體實現

1.主要實現方式有以下幾種:

  • select,poll,epoll,kqueue,/dev/poll,iocp

       其中iocp是Windows實現的,select、poll、epoll是Linux實現的,kqueue是FreeBSD實現的,/dev/poll是SUN的Solaris實現的。select、poll對應第3種(I/O複用)模型,iocp對應第5種(非同步I/O)模型,那麼epoll、kqueue、/dev/poll呢?其實也同select屬於同一種模型,只是更高階一些,可以看作有了第4種(訊號驅動I/O)模型的某些特性,如callback機制。

2.為什麼epoll、kqueue、/dev/poll比select高階?

       答案是,他們無輪詢。因為他們用callback取代了。想想看,當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成排程,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回撥函式,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll、kqueue、/dev/poll做的。這樣子說可能不好理解,那麼我說一個現實中的例子,假設你在大學讀書,住的宿舍樓有很多間房間,你的朋友要來找你。select版宿管大媽就會帶著你的朋友挨個房間去找,直到找到你為止。而epoll版宿管大媽會先記下每位同學的房間號,你的朋友來時,只需告訴你的朋友你住在哪個房間即可,不用親自帶著你的朋友滿大樓找人。如果來了10000個人,都要找自己住這棟樓的同學時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高併發伺服器中,輪詢I/O是最耗時間的操作之一,select、epoll、/dev/poll的效能誰的效能更高,同樣十分明瞭。

3.Windows or *nix (IOCP or kqueue、epoll、/dev/poll)?

       誠然,Windows的IOCP非常出色,目前很少有支援asynchronous I/O的系統,但是由於其系統本身的侷限性,大型伺服器還是在UNIX下。而且正如上面所述,kqueue、epoll、/dev/poll 與 IOCP相比,就是多了一層從核心copy資料到應用層的阻塞,從而不能算作asynchronous I/O類。但是,這層小小的阻塞無足輕重,kqueue、epoll、/dev/poll 已經做得很優秀了。

4.總結一些重點

       只有IOCP(windows實現)是asynchronous I/O,其他機制或多或少都會有一點阻塞。select(Linux實現)低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善epoll(Linux實現)、kqueue(FreeBSD實現)、/dev/poll(Solaris實現)是Reacor模式,IOCP是Proactor模式。Apache 2.2.9之前只支援select模型,2.2.9之後支援epoll模型,Nginx 支援epoll模型,Java nio包是select模型