服務器併發處理能力

序:

    這裏指的服務器是指提供HTTP服務的服務器,人們通常衡量一臺web服務器能力的大小爲其在單位時間內能處理的請求數的多少。

3.1 吞吐率

    Web服務器的吞吐率是指其單位時間內所能處理的請求數。更關心的是服務器併發處理能力的上限即最大吞吐率。

    Web服務器在實際工作中,其處理的Http請求包括對很多不同資源的請求即請求的url不一樣。正因爲這種請求性質的不同,Web服務器併發能力的強弱關鍵在於如何針對不同的請求性質設計不同的併發策略。有時候一臺Web服務器要同時處理許多不同性質的請求,在一程度上使得Web服務器性能無法發揮。

    併發用戶數爲某一時刻同時向服務器發送請求的用戶數。注意,100個用戶同時向服務器各發10個請求與1個用戶同時向服務器發1000個請求對服務器造成的壓力是不一樣的,顯然是前者造成的壓力更大,原因是此時服務器網卡接收緩衝區中的請求同時有100個等待處理。

    最大併發數是有一定利益前提的,是用戶和服務器各自期望利益的一個衡量點。一般是服務器保持了比較高的吞吐率同時用戶對等待時間比較滿意時的併發數即可定爲最大併發數。

    在併發用戶數較大的情況下,服務器採用什麼樣的併發策略是影響最大併發數的關鍵。

    用戶訪問web站點通常是使用瀏覽器,而瀏覽器在下載一個網頁及網頁中的組件是採用多線程下載的。但其對同一域名下的URL併發下載數是有限制的,具體限制因瀏覽器及其版本和http版本不同。

    服務器支持的最大併發數具體到真實用戶並不是一對一的關係。一個真實的用戶可能給服務器帶來兩個或更多的併發用戶數的壓力。

    從web服務器的角度看,實際併發用戶數可理解爲服務器維護不同用戶的文件描述符總數即併發連接數。不是同時有多少用戶,服務器就爲其建立多少連接,服務器一般會限制同時服務的最多用戶數。

    web服務器工作的本質是以最快的速度將內核緩衝區中的用戶請求數據拿回來並儘量儘快處理完這些請求,並將響應數據放到發送數據的緩衝區中,再去處理下一撥請求,如此反覆。

    用戶平均請求等待時間用於衡量服務器在一定併發用戶數下對單個用戶的服務質量。而服務器平均請求處理時間用於衡量服務器的整體服務質量,它是吞吐率的倒數。如果併發策略得當,每個請求的平均處理時間可以減少。

    併發策略的設計就是在服務器同時處理較多請求的時候合理協調並充分利用CPU和IO計算 ,使其在較大併發用戶數下保持較高的吞吐率。但並不存在一個對所有請求性質都較高的併發策略。

3.2 CPU併發計算

    服務器之所以可以同時處理多個請求,在於操作系統通過多執行流體系設計多個任務可以輪流使用系統資源,包括CPU、內存、IO等。

    多執行流的一般實現便是進程,多進程的好處可以對CPU時間的輪流使用,對CPU計算和IO操作重疊利用。這裏的IO主要是指磁盤IO和網絡IO,對CPU而言,它們慢的可憐。大多數進程的時間主要耗在IO上。

    進程的調度由內核執行,進程的目的是擔當分配資源的實體。每個進程都有自己的內存地址空間和生命週期。子進程被父進程創建後便把父進程地址空間的所有數據複製到自己的內存地址空間。完全繼承父進程的上下文信息,它們之間可以互相通信,但不互相依賴,無權干涉。

    進程的創建使用fork()系統調用,服務器頻繁地創建進程會引起不小的性能開銷。Linux 2.6對fork()進行了優化,減少了一些多餘的內存複製。

    進程的優越性體現在其穩定性和健壯性,其中一個進程崩潰不會影響到另一個進程。但採用大量進程的web服務器(如:Apache prefork模型)在處理大量併發請求時其內存開銷將成爲性能的瓶頸。

    輕量級進程由系統調用clone()來創建,由內核管理,獨立存在,允許這些進程共享數據,輕量級進程減少了內存開銷,爲多進程應用提供了數據共享,但其上下文切換開銷還是避免不了。

    一般多線程的管理在用戶態完成,線程切換的開銷比輕量級進程切換開銷要小,但它在多CPU服務器中表現較差。

    進程調度器維護着一個可運行隊列以及一個包括所有休眠和殭屍進程的列表。進程調度器的工作就是決定下一個運行的進程。如果隊列中有多個可運行的進程,此時進程調度器可根據進程的優先級及其它策略進行選擇。

    CPU時間片的長度要具體權衡,時間片太短,那麼CPU在進程切換上的時間浪費就比較大,如果時間片太長,那麼多任務實時性和交互性就無法做到保證。

    系統負載越高代表CPU越忙,也就越無法很好地滿足所有進程的需要。系統負載的計算是根據單位時間內運行隊列中就緒等待的進程數平均值。當運行隊列中的就緒進程不需要等待就可以立即得到CPU說明系統負載比較低,系統響應速度也就快。

    查看系統負載可以通過cat /proc/loadavg、top、w等命令工具查看。

    進程的切換就是進程調度器掛起正在運行的進程,恢復之前掛起的某個進程。

    每個進程只能共享CPU寄存器,一個進程被掛起的本質就是將其在CPU寄存器中的數據取出來暫存到內核堆棧中,恢復一個進程的本質就是將其數據重新載入到CPU寄存器中,其實這種硬件上下文切換的開銷也是挺大的。

    要服務器支持較大的併發數,就要減少上下文切換的次數,最簡單地做法是減少進程數目,儘量使用線程配合其它IO模型來設計併發策略。

    除了關注CPU使用率外,還要關注IOWait,它是指CPU空閒並等待IO操作完成的時間比例。IOWait不能真實地代表IO操作的性能或工作量,它是衡量CPU性能的。即使IOWait爲100%也不代表IO出現性能瓶頸,IOWait爲0時IO也可能很忙。此時,最好是測試磁盤IO和查看網絡IO的流量。

3.3 系統調用

    進程有用戶態和內核態兩種運行模式。進程可以在這兩種模式中切換,存在一定的開銷。進程通常運行在用戶態,當進程需要對硬件操作的時候就要切換到內核態。這兩種模式的分離是爲了底層操作的安全性和簡化開發模型。所有進程都必須通過內核提供的系統調用來操作硬件。進程從用戶態到內核態存在一定的內存空間切換,這種開銷是比較昂貴的,應儘量減少不必要的系統調用。

3.4 內存分配

    Web服務器在工作的過程中需要大量的內存,這使得內存的分配和釋放很重要。服務器處理成千上萬的http請求,其內存堆棧的分配和複製次數變得更加頻繁。

    Apache在運行時內存使用量非常驚人,它一開始就申請大量內存作內存池,爲防止以後頻繁的內存再分配帶來的性能開銷,內存池的使用使用Apache管理更安全,但內存池的使用也沒有彌補其性能,其內存池的釋放是在Apache關閉的時候。

    Lighttpd使用單進程模型,其內存使用量比較小,同樣是使用單進程的Nginx其內存使用量更小,Nginx使用多線程處理請求,這些多線程可以共享內存資源,它使用分階段按需分配內存、及時釋放策略。

3.5 持久連接

    持久連接是指一次TCP連接中持續處理多個請求而不斷開連接。建立TCP連接操作的開銷可不小,在允許的情況下,連接次數越小越有利於性能提升。

    長連接對於密集型的圖片或網頁等小數據量的請求有明顯的加速作用。

    Http長連接的實施需要瀏覽器和服務器的配合,缺一不可。

    瀏覽器要支持http長連接可以在http請求頭中加入:Connection: Keep-Alive,目前主流web服務器都默認使用長連接,除非顯式關閉。

    對於長連接的使用要注意長連接的有效時間多長,即什麼時候關閉長連接,瀏覽器和服務器都有默認的有效時間,也都可以設置有效時間,都可以主動關閉,若兩者設置的時間長度不一致,以短的爲準。例如:

    請求:Connection:Keep-Alive

     響應:Connection:Keep-Alive

                Keep-Alive:timeout=5,max=100

    持久連接的目的就是減少連接次數,重用已有的連接通道,減少連接開銷。

3.6 IO模型

    IO有內存IO、網絡IO和磁盤IO等。

    可以使用RAID磁盤陣列來加速對磁盤IO的訪問,使用獨立網絡帶寬和高帶寬網絡適配器可以搞網絡IO速度,但IO操作都要由內核系統調用完成,系統調用需要CPU調用,無疑存在CPU快和IO慢的不協調。

    我們所關注的IO操作主要是網絡數據的發送、接收和磁盤文件的訪問。不同IO模型的本質在於CPU參與的方式。

    DMA:直接內存訪問。即不需要通過CPU即可以進行內存到磁盤的數據交換。這樣就可降低對CPU的佔有率,節省系統資源。

    IO等待是不可避免的,既然有等待,就會有阻塞。這裏的阻塞是指當前發起請求的進程IO被阻塞,並不是CPU被阻塞,CPU是沒有阻塞的,它只有拼命地計算。

    同步阻塞IO是指當前進程調用某些IO操作的系統調用或庫函數時,進程便暫停下來,等待IO操作完成後再繼續進行,這種模型可以和多進程結合起來有效利用CPU資源,但其代價就是多進程的大內存開銷。這種模型的等待時間包括等待數據的就緒和等待數據的複製。

    同步非阻塞IO是指調用不會等待數據的就緒,當沒數據可讀或可寫時立即告訴進程,讓其函數及時返回。通過反覆輪詢來嘗試數據是否就緒,防止進程被阻塞,最大的一個好處就是可以在一個進程內同時處理多個IO操作。但是反覆輪詢會大量佔用CPU時間,使得進程處於忙碌等待狀態。非阻塞IO只對網絡IO有效,對磁盤IO無效。

    多路IO就緒通知允許進程通過一種方法同時監視所有文件描述符,並可以快速獲得所有就緒的文件描述符,然後只針對這些文件描述符進行數據訪問。當然,要注意,這種模型在數據訪問時仍然要採用阻塞或非阻塞方式進行。

    select:通過一個select()系統調用來監視並返回就緒的文件描述符,從而對這些文件描述符進行後續的讀寫。幾乎所有的平臺都支持這種方式,可以跨平臺,但它的缺點是單個進程可監視的文件描述符數量有最大限制,Linux上一般爲1024,它對所有socket進行一次性掃描也存在開銷。

    poll:與select沒有本質區別,只是poll沒有最大文件描述符數量限制。它的缺點也是將大理文件描述符的數組在用戶態和內核態來回複製,而不管文件描述符是否就緒,開銷會成線性增長。

    epoll:Linux2.6纔出現,具有其它方式的一切優點,是Linux2.6下性能最好的多路IO就緒通知方法。它基於事件的就緒通知方式。

    kqueue:性能和epoll差不多,它是FreeBSD下的,但它的API在許多平臺下不支持。

    內存映射是指將內存中某塊地址空間和我們指定的磁盤文件相關聯,從而把對這塊內存的訪問轉換爲對磁盤文件的訪問。內存映射可以提高磁盤IO性能,像訪問內存一樣地訪問磁盤文件。有兩種內存映射,共享型和私有型。共享型是指對任何內存的寫操作都同步到磁盤文件,而所有映射同一個文件的進程都共享任意一個進程對映射內存的修改。私有型是指映射的文件只能是隻讀文件,不可以將內存的寫同步到文件,多個進程不共享修改。顯然,共享型的內存映射效率偏低。

    直接IO就是指繞過內核緩衝區,打開的文件可直接訪問,避免CPU和內存的多餘時間開銷。

    sendfile系統調用可將磁盤文件的特定部分直接送到客戶端的Socket的描述符,加快靜態文件的請求速度,減少CPU和內存的開銷。

    阻塞和非阻塞是指當進程訪問的數據尚未就緒,進程是否等待即是立即返回還是繼續等待。同步是指主動請求並等待IO操作完成,當數據就緒後讀寫時必須阻塞。異步是指主動請求數據後可以繼續處理其它任務,隨便等待IO操作完成的通知,即讀寫時進程不阻塞。

3.7 服務器併發策略

    設計併發策略的目的就是就是讓IO操作和CPU計算儘量重疊進行。一方面要讓CPU在IO等待不要空閒,另一方面要讓CPU在IO調度上儘量花最少的時間。

    (1)一個進程處理一個連接,非阻塞IO

        這樣會存在多個併發請求同時到達時,服務器必然要準備多個進程來處理請求。這種策略典型的例子就是Apache的fork和prefork模式。對於併發數不高的站點同時依賴Apache其它功能時的應用選擇Apache還是可以的。

    (2)一個線程處理一個連接,非阻塞IO

        這種方式允許在一個進程中通過多個線程來處理多個連接,一個線程處理一個連接。Apache的worker模式就是這種典型例子,使其可支持更多的併發連接。不過這種模式的總體性能還不如prefork,所以一般不選用worker模式。

    (3)一個進程處理多個連接,非阻塞IO

        適用的前提條件就是多路IO就緒通知的應用。這種情況下,將處理多個連接的進程叫做worker進程或服務進程。worker的數量可以配置,如Nginx中的worker_processes 4

    (4)一個線程處理多個連接,異步IO

        即使有高性能的多路IO就緒通知,但磁盤IO的等待還是無法避免的。更加高效的方法是對磁盤文件使用異步IO,目前很少有Web服務器真正意義上支持這種異步IO。

發佈了29 篇原創文章 · 獲贊 3 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章