來源:www.cnblogs.com/zengjin93

如何提高服務器併發處理能力

如何提高服務器併發處理能力


說明

以下內容爲入門級介紹,意在對老技術作較全的總結而不是較深的研究。主要參考《構建高性能Web站點》一書。

什麼是服務器併發處理能力

一臺服務器在單位時間裏能處理的請求越多,服務器的能力越高,也就是服務器併發處理能力越

有什麼方法衡量服務器併發處理能力

1. 吞吐率

吞吐率,單位時間裏服務器處理的最大請求數,單位req/s。

從服務器角度,實際併發用戶數的可以理解爲服務器當前維護的代表不同用戶的文件描述符總數,也就是併發連接數。服務器一般會限制同時服務的最多用戶數,比如apache的MaxClents參數。

這裏再深入一下,對於服務器來說,服務器希望支持高吞吐率,對於用戶來說,用戶只希望等待最少的時間,顯然,雙方不能滿足,所以雙方利益的平衡點,就是我們希望的最大併發用戶數。

2. 壓力測試

有一個原理一定要先搞清楚,假如100個用戶同時向服務器分別進行10個請求,與1個用戶向服務器連續進行1000次請求,對服務器的壓力是一樣嗎?實際上是不一樣的,因對每一個用戶,連續發送請求實際上是指發送一個請求並接收到響應數據後再發送下一個請求。這樣對於1個用戶向服務器連續進行1000次請求, 任何時刻服務器的網卡接收緩衝區中只有1個請求,而對於100個用戶同時向服務器分別進行10個請求,服務器的網卡接收緩衝區最多有100個等待處理的請求,顯然這時的服務器壓力更大。

壓力測試前提考慮的條件

  • 併發用戶數: 指在某一時刻同時向服務器發送請求的用戶總數(HttpWatch)
  • 總請求數
  • 請求資源描述
  • 請求等待時間(用戶等待時間)
  • 用戶平均請求的等待時間
  • 服務器平均請求處理的時間
  • 硬件環境

壓力測試中關心的時間又細分以下2種:

  • 用戶平均請求等待時間(這裏暫不把數據在網絡的傳輸時間,還有用戶PC本地的計算時間計算入內)
  • 服務器平均請求處理時間

用戶平均請求等待時間主要用於衡量服務器在一定併發用戶數下,單個用戶的服務質量;而服務器平均請求處理時間就是吞吐率的倒數,一般來說,用戶平均請求等待時間 = 服務器平均請求處理時間 * 併發用戶數

怎麼提高服務器的併發處理能力

1. 提高CPU併發計算能力

服務器之所以可以同時處理多個請求,在於操作系統通過多執行流體系設計使得多個任務可以輪流使用系統資源,這些資源包括CPU,內存以及I/O. 這裏的I/O主要指磁盤I/O, 和網絡I/O。

多進程 & 多線程

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

而實際上,大多數進程的時間主要消耗在I/O操作上。現代計算機的DMA技術可以讓CPU不參與I/O操作的全過程,比如進程通過系統調用,使得CPU向網卡或者磁盤等I/O設備發出指令,然後進程被掛起,釋放出CPU資源,等待I/O設備完成工作後通過中斷來通知進程重新就緒。對於單任務而言,CPU大部分時間空閒,這時候多進程的作用尤爲重要。

多進程不僅能夠提高CPU的併發度。其優越性還體現在獨立的內存地址空間和生命週期所帶來的穩定性和健壯性,其中一個進程崩潰不會影響到另一個進程。

但是進程也有如下缺點:

  • fork()系統調用開銷很大: prefork
  • 進程間調度和上下文切換成本: 減少進程數量
  • 龐大的內存重複:共享內存
  • IPC編程相對比較麻煩

減少進程切換

當硬件上下文頻繁裝入和移出時,所消耗的時間是非常可觀的。可用Nmon工具監視服務器每秒的上下文切換次數。爲了儘量減少上下文切換次數,最簡單的做法就是減少進程數,儘量使用線程並配合其它I/O模型來設計併發策略。

還可以考慮使用進程綁定CPU技術,增加CPU緩存的命中率。若進程不斷在各CPU上切換,這樣舊的CPU緩存就會失效。

減少使用不必要的鎖

服務器處理大量併發請求時,多個請求處理任務時存在一些資源搶佔競爭,這時一般採用“鎖”機制來控制資源的佔用,當一個任務佔用資源時,我們鎖住資源,這時其它任務都在等待鎖的釋放,這個現象稱爲鎖競爭。

通過鎖競爭的本質,我們要意識到儘量減少併發請求對於共享資源的競爭。比如在允許情況下關閉服務器訪問日誌,這可以大大減少在鎖等待時的延遲時間。要最大程度減少無辜的等待時間。

這裏說下無鎖編程,就是由內核完成這個鎖機制,主要是使用原子操作替代鎖來實現對共享資源的訪問保護 ,使用原子操作時,在進行實際的寫操作時,使用了lock指令,這樣就可以阻止其他任務寫這塊內存,避免出現數據競爭現象。原子操作速度比鎖快,一般要快一倍以上。

例如fwrite(), fopen(),其是使用append方式寫文件,其原理就是使用了無鎖編程,無鎖編程的複雜度高,但是效率快,而且發生死鎖概率低。

考慮進程優先級

進程調度器會動態調整運行隊列中進程的優先級,通過top觀察進程的PR值

考慮系統負載

可在任何時刻查看/proc/loadavg, top中的load average也可看出

考慮CPU使用率

除了用戶空間和內核空間的CPU使用率以外,還要關注I/O wait,它是指CPU空閒並且等待I/O操作完成的時間比例(top中查看wa的值)。

2. 考慮減少內存分配和釋放

服務器的工作過程中,需要大量的內存,使得內存的分配和釋放工作尤爲重要。可以通過改善數據結構和算法複製度來適當減少中間臨時變量的內存分配及數據複製時間,而服務器本身也使用了各自的策略來提高效率。

例如Apache,在運行開始時一次申請大片的內存作爲內存池,若隨後需要時就在內存池中直接獲取,不需要再次分配,避免了頻繁的內存分配和釋放引起的內存整理時間。

再如Nginx使用多線程來處理請求,使得多個線程之間可以共享內存資源,從而令它的內存總體使用量大大減少,另外,nginx分階段的內存分配策略,按需分配,及時釋放,使得內存使用量保持在很小的數量範圍。

另外,還可以考慮共享內存。共享內存指在多處理器的計算機系統中,可以被不同中央處理器(CPU)訪問的大容量內存,也可以由不同進程共享,是非常快的進程通信方式。

但是使用共享內存也有不好的地方,就是對於多機器時數據不好統一。

shell命令ipcs可用來顯示系統下共享內存的狀態,函數shmget可以創建或打開一塊共享內存區,函數shmat將一個存在的共享內存段連接到本進程空間, 函數shmctl可以對共享內存段進行多種操作,函數shmdt函數分離該共享內存。

3. 考慮使用持久連接

持久連接也爲長連接,它本身是TCP通信的一種普通方式,即在一次TCP連接中持續發送多分數據而不斷開連接,與它相反的方式稱爲短連接,也就是建立連接後發送一份數據就斷開,然後再次建立連接發送下一份數據, 周而復始。是否採用持久連接,完全取決於應用特點。

從性能角度看,建立TCP連接的操作本身是一項不小的開銷,在允許的情況下,連接次數越少,越有利於性能的提升; 尤其對於密集型的圖片或網頁等小數據請求處理有明顯的加速所用。

HTTP長連接需要瀏覽器和web服務器的共同協作,目前瀏覽器普遍支持長連接,表現在其發出的HTTP請求數據頭中包含關於長連接的聲明,如下: Connection: Keep-Alive,主流的web服務器都支持長連接,比如apache中,可以用KeepAlive off關閉長連接。

對於長連接的有效使用,還有關鍵一點在於長連接超時時間的設置,即長連接在什麼時候關閉嗎? Apache的默認設置爲5s, 若這個時間設置過長,則可能導致資源無效佔有,維持大量空閒進程,影響服務器性能。

4. 改進I/O 模型

I/O操作根據設備的不同分爲很多類型,比如內存I/O, 網絡I/O, 磁盤I/O. 對於網絡I/O和磁盤I/O, 它們的速度要慢很多,儘管使用RAID磁盤陣列可通過並行磁盤磁盤來加快磁盤I/O速度,購買大連獨享網絡帶寬以及使用高帶寬網絡適配器可以提高網絡i/O的速度。

但這些I/O操作需要內核系統調用來完成,這些需要CPU來調度,這使得CPU不得不浪費寶貴的時間來等待慢速I/O操作。我們希望讓CPU足夠少的時間在i/O操作的調度上,如何讓高速的CPU和慢速的I/O設備更好地協調工作,是現代計算機一直探討的話題。各種I/O模型的本質區別在於CPU的參與方式。

1. DMA技術

I/O設備和內存之間的數據傳輸方式由DMA控制器完成。在DMA模式下,CPU只需向DMA下達命令,讓DMA控制器來處理數據的傳送,這樣可以大大節省系統資源。

2. 異步I/O

異步I/O指主動請求數據後便可以繼續處理其它任務,隨後等待I/O操作的通知,這樣進程在數據讀寫時不發生阻塞。

異步I/O是非阻塞的,當函數返回時,真正的I/O傳輸已經完成,這讓CPU處理和I/O操作達到很好的重疊。

3. I/O多路複用

epoll服務器同時處理大量的文件描述符是必不可少的,若採用同步非阻塞I/O模型,若同時接收TCP連接的數據,就必須輪流對每個socket調用接收數據的方法,不管這些socket有沒有可接收的數據,都要詢問一次。

假如大部分socket並沒有數據可以接收,那麼進程便會浪費很多CPU時間用於檢查這些socket有沒有可以接收的數據。多路I/O就緒通知的出現,提供了對大量文件描述符就緒檢查的高性能方案,它允許進程通過一種方法同時監視所有文件描述符,並可以快速獲得所有就緒的文件描述符,然後只針對這些文件描述符進行數據訪問。

epoll可以同時支持水平觸發和邊緣觸發,理論上邊緣觸發性能更高,但是代碼實現複雜,因爲任何意外的丟失事件都會造成請求處理錯誤。

epoll主要有2大改進:

epoll只告知就緒的文件描述符,而且當調用epoll_wait()獲得文件描述符時,返回並不是實際的描述符,而是一個代表就緒描述符數量的值,然後只需去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裏使用了內存映射(mmap)技術,這樣徹底省掉了這些文件描述符在系統調用時複製的開銷。

epoll採用基於事件的就緒通知方式。其事先通過epoll_ctrl()註冊每一個文件描述符,一旦某個文件描述符就緒時,內核會採用類似callback的回調機制,當進程調用epoll_wait()時得到通知

關於IO模型,可以參考前面寫的相關文章Java NIO.2; 關於epoll,可以參考前面寫的文章select、poll和epoll簡介。

4. Sendfile

大多數時候,我們都向服務器請求靜態文件,比如圖片,樣式表等,在處理這些請求時,磁盤文件的數據先經過內核緩衝區,然後到用戶內存空間,不需經過任何處理,其又被送到網卡對應的內核緩衝區,接着再被送入網卡進行發送。

Linux提供sendfile()系統調用,可以講磁盤文件的特定部分直接傳送到代表客戶端的socket描述符,加快了靜態文件的請求速度,同時減少CPU和內存的開銷。

適用場景: 對於請求較小的靜態文件,sendfile發揮的作用不那麼明顯,因發送數據的環節在整個過程中所佔時間的比例相比於大文件請求時小很多。

5. 內存映射

Linux內核提供一種訪問磁盤文件的特殊方式,它可以將內存中某塊地址空間和我們指定的磁盤文件相關聯,從而對這塊內存的訪問轉換爲對磁盤文件的訪問。這種技術稱爲內存映射。

多數情況下,內存映射可以提高磁盤I/O的性能,無須使用read()或write()等系統調用來訪問文件,而是通過mmap()系統調用來建立內存和磁盤文件的關聯,然後像訪問內存一樣自由訪問文件。

缺點:在處理較大文件時,內存映射會導致較大的內存開銷,得不償失。

6. 直接I/O

在linux 2.6中,內存映射和直接訪問文件沒有本質差異,因爲數據需要經過2次複製,即在磁盤與內核緩衝區之間以及在內核緩衝區與用戶態內存空間。

引入內核緩衝區的目的在於提高磁盤文件的訪問性能,然而對於一些複雜的應用,比如數據庫服務器,它們爲了進一步提高性能,希望繞過內核緩衝區,由自己在用戶態空間實現並管理I/O緩衝區,比如數據庫可根據更加合理的策略來提高查詢緩存命中率。另一方面,繞過內核緩衝區也可以減少系統內存的開銷,因內核緩衝區本身就在使用系統內存。

Linux在open()系統調用中增加參數選項O_DIRECT,即可繞過內核緩衝區直接訪問文件,實現直接I/O。

在Mysql中,對於Innodb存儲引擎,自身進行數據和索引的緩存管理,可在my.cnf配置中分配raw分區跳過內核緩衝區,實現直接I/O。

改進服務器併發策略

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

一個進程處理一個連接,非阻塞I/O

這樣會存在多個併發請求同時到達時,服務器必然要準備多個進程來處理請求。其進程的開銷限制了它的併發連接數。但從穩定性和兼容性的角度,則其相對安全,任何一個子進程的崩潰不會影響服務器本身,父進程可以創建新的子進程;這種策略典型的例子就是Apache的fork和prefork模式。對於併發數不高(如150以內)的站點同時依賴Apache其它功能時的應用選擇Apache還是可以的。

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

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

一個進程處理多個連接,異步I/O

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

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

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

6. 改進硬件環境

還有一點要提及的是硬件環境,服務器的硬件配置對應用程序的性能提升往往是最直接,也是最簡單的方式,這就是所謂的scale up。這裏不做論述。

相關文章