說明html
如下內容爲入門級介紹,意在對老技術做較全的總結而不是較深的研究。主要參考《構建高性能Web站點》一書。java
一臺服務器在單位時間裏能處理的請求越多,服務器的能力越高,也就是服務器併發處理能力越強linux
吞吐率,單位時間裏服務器處理的最大請求數,單位req/snginx
從服務器角度,實際併發用戶數的能夠理解爲服務器當前維護的表明不一樣用戶的文件描述符總數,也就是併發鏈接數。服務器通常會限制同時服務的最多用戶數,好比apache的MaxClents參數。web
這裏再深刻一下,對於服務器來講,服務器但願支持高吞吐率,對於用戶來講,用戶只但願等待最少的時間,顯然,雙方不能知足,因此雙方利益的平衡點,就是咱們但願的最大併發用戶數。算法
有一個原理必定要先搞清楚,假如100個用戶同時向服務器分別進行10個請求,與1個用戶向服務器連續進行1000次請求,對服務器的壓力是同樣嗎?其實是不同的,因對每個用戶,連續發送請求其實是指發送一個請求並接收到響應數據後再發送下一個請求。這樣對於1個用戶向服務器連續進行1000次請求, 任什麼時候刻服務器的網卡接收緩衝區中只有1個請求,而對於100個用戶同時向服務器分別進行10個請求,服務器的網卡接收緩衝區最多有100個等待處理的請求,顯然這時的服務器壓力更大。sql
壓力測試前提考慮的條件shell
壓力測試中關心的時間又細分如下2種:數據庫
用戶平均請求等待時間主要用於衡量服務器在必定併發用戶數下,單個用戶的服務質量;而服務器平均請求處理時間就是吞吐率的倒數,通常來講,用戶平均請求等待時間 = 服務器平均請求處理時間 * 併發用戶數apache
服務器之因此能夠同時處理多個請求,在於操做系統經過多執行流體系設計使得多個任務能夠輪流使用系統資源,這些資源包括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的併發度。其優越性還體如今獨立的內存地址空間和生命週期所帶來的穩定性和健壯性,其中一個進程崩潰不會影響到另外一個進程。
可是進程也有以下缺點:
當硬件上下文頻繁裝入和移出時,所消耗的時間是很是可觀的。可用Nmon工具監視服務器每秒的上下文切換次數。
爲了儘可能減小上下文切換次數,最簡單的作法就是減小進程數,儘可能使用線程並配合其它I/O模型來設計併發策略。
還能夠考慮使用進程綁定CPU技術,增長CPU緩存的命中率。若進程不斷在各CPU上切換,這樣舊的CPU緩存就會失效。
服務器處理大量併發請求時,多個請求處理任務時存在一些資源搶佔競爭,這時通常採用「鎖」機制來控制資源的佔用,當一個任務佔用資源時,咱們鎖住資源,這時其它任務都在等待鎖的釋放,這個現象稱爲鎖競爭。
經過鎖競爭的本質,咱們要意識到儘可能減小併發請求對於共享資源的競爭。好比在容許狀況下關閉服務器訪問日誌,這能夠大大減小在鎖等待時的延遲時間。要最大程度減小無辜的等待時間。
這裏說下無鎖編程,就是由內核完成這個鎖機制,主要是使用原子操做替代鎖來實現對共享資源的訪問保護 ,使用原子操做時,在進行實際的寫操做時,使用了lock指令,這樣就能夠阻止其餘任務寫這塊內存,避免出現數據競爭現象。原子操做速度比鎖快,通常要快一倍以上。
例如fwrite(), fopen(),其是使用append方式寫文件,其原理就是使用了無鎖編程,無鎖編程的複雜度高,可是效率快,並且發生死鎖機率低。
進程調度器會動態調整運行隊列中進程的優先級,經過top觀察進程的PR值
可在任什麼時候刻查看/proc/loadavg, top中的load average也可看出
除了用戶空間和內核空間的CPU使用率之外,還要關注I/O wait,它是指CPU空閒而且等待I/O操做完成的時間比例(top中查看wa的值)。
服務器的工做過程當中,須要大量的內存,使得內存的分配和釋放工做尤其重要。
能夠經過改善數據結構和算法複製度來適當減小中間臨時變量的內存分配及數據複製時間,而服務器自己也使用了各自的策略來提升效率。
例如Apache,在運行開始時一次申請大片的內存做爲內存池,若隨後須要時就在內存池中直接獲取,不須要再次分配,避免了頻繁的內存分配和釋放引發的內存整理時間。
再如Nginx使用多線程來處理請求,使得多個線程之間能夠共享內存資源,從而令它的內存整體使用量大大減小,另外,nginx分階段的內存分配策略,按需分配,及時釋放,使得內存使用量保持在很小的數量範圍。
另外,還能夠考慮共享內存。
共享內存指在多處理器的計算機系統中,能夠被不一樣中央處理器(CPU)訪問的大容量內存,也能夠由不一樣進程共享,是很是快的進程通訊方式。
可是使用共享內存也有很差的地方,就是對於多機器時數據很差統一。
shell命令ipcs可用來顯示系統下共享內存的狀態,函數shmget能夠建立或打開一塊共享內存區,函數shmat將一個存在的共享內存段鏈接到本進程空間, 函數shmctl能夠對共享內存段進行多種操做,函數shmdt函數分離該共享內存。
持久鏈接也爲長鏈接,它自己是TCP通訊的一種普通方式,即在一次TCP鏈接中持續發送多分數據而不斷開鏈接,與它相反的方式稱爲短鏈接,也就是創建鏈接後發送一份數據就斷開,而後再次創建鏈接發送下一份數據, 周而復始。是否採用持久鏈接,徹底取決於應用特色。從性能角度看,創建TCP鏈接的操做自己是一項不小的開銷,在容許的狀況下,鏈接次數越少,越有利於性能的提高; 尤爲對於密集型的圖片或網頁等小數據請求處理有明顯的加速所用。
HTTP長鏈接須要瀏覽器和web服務器的共同協做,目前瀏覽器廣泛支持長鏈接,表如今其發出的HTTP請求數據頭中包含關於長鏈接的聲明,以下: Connection: Keep-Alive
主流的web服務器都支持長鏈接,好比apache中,能夠用KeepAlive off關閉長鏈接。
對於長鏈接的有效使用,還有關鍵一點在於長鏈接超時時間的設置,即長鏈接在何時關閉嗎? Apache的默認設置爲5s, 若這個時間設置過長,則可能致使資源無效佔有,維持大量空閒進程,影響服務器性能。
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的參與方式。
I/O設備和內存之間的數據傳輸方式由DMA控制器完成。在DMA模式下,CPU只需向DMA下達命令,讓DMA控制器來處理數據的傳送,這樣能夠大大節省系統資源。
異步I/O指主動請求數據後即可以繼續處理其它任務,隨後等待I/O操做的通知,這樣進程在數據讀寫時不發生阻塞。
異步I/O是非阻塞的,當函數返回時,真正的I/O傳輸已經完成,這讓CPU處理和I/O操做達到很好的重疊。
epoll服務器同時處理大量的文件描述符是必不可少的,若採用同步非阻塞I/O模型,若同時接收TCP鏈接的數據,就必須輪流對每一個socket調用接收數據的方法,無論這些socket有沒有可接收的數據,都要詢問一次。假如大部分socket並無數據能夠接收,那麼進程便會浪費不少CPU時間用於檢查這些socket有沒有能夠接收的數據。多路I/O就緒通知的出現,提供了對大量文件描述符就緒檢查的高性能方案,它容許進程經過一種方法同時監視全部文件描述符,並能夠快速得到全部就緒的文件描述符,而後只針對這些文件描述符進行數據訪問。
epoll能夠同時支持水平觸發和邊緣觸發,理論上邊緣觸發性能更高,可是代碼實現複雜,由於任何意外的丟失事件都會形成請求處理錯誤。
epoll主要有2大改進:
關於IO模型,能夠參考筆者前面寫的相關文章Java NIO.2; 關於epoll,能夠參考筆者前面寫的文章select、poll和epoll簡介。
大多數時候,咱們都向服務器請求靜態文件,好比圖片,樣式表等,在處理這些請求時,磁盤文件的數據先通過內核緩衝區,而後到用戶內存空間,不需通過任何處理,其又被送到網卡對應的內核緩衝區,接着再被送入網卡進行發送。
Linux提供sendfile()系統調用,能夠講磁盤文件的特定部分直接傳送到表明客戶端的socket描述符,加快了靜態文件的請求速度,同時減小CPU和內存的開銷。
適用場景: 對於請求較小的靜態文件,sendfile發揮的做用不那麼明顯,因發送數據的環節在整個過程當中所佔時間的比例相比於大文件請求時小不少。
Linux內核提供一種訪問磁盤文件的特殊方式,它能夠將內存中某塊地址空間和咱們指定的磁盤文件相關聯,從而對這塊內存的訪問轉換爲對磁盤文件的訪問。這種技術稱爲內存映射。
多數狀況下,內存映射能夠提升磁盤I/O的性能,無須使用read()或write()等系統調用來訪問文件,而是經過mmap()系統調用來創建內存和磁盤文件的關聯,而後像訪問內存同樣自由訪問文件。
缺點:在處理較大文件時,內存映射會致使較大的內存開銷,得不償失。
在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調度上儘可能花最少的時間。
這樣會存在多個併發請求同時到達時,服務器必然要準備多個進程來處理請求。其進程的開銷限制了它的併發鏈接數。但從穩定性和兼容性的角度,則其相對安全,任何一個子進程的崩潰不會影響服務器自己,父進程能夠建立新的子進程;這種策略典型的例子就是Apache的fork和prefork模式。對於併發數不高(如150之內)的站點同時依賴Apache其它功能時的應用選擇Apache仍是能夠的。
這種方式容許在一個進程中經過多個線程來處理多個鏈接,一個線程處理一個鏈接。Apache的worker模式就是這種典型例子,使其可支持更多的併發鏈接。不過這種模式的整體性能還不如prefork,因此通常不選用worker模式。
一個線程同時處理多個鏈接,潛在的前提條件就是使用IO多路複用就緒通知。
這種狀況下,將處理多個鏈接的進程叫作worker進程或服務進程。worker的數量能夠配置,如Nginx中的worker_processes 4。
即便有高性能的IO多路複用就緒通知,但磁盤IO的等待仍是沒法避免的。更加高效的方法是對磁盤文件使用異步IO,目前不多有Web服務器真正意義上支持這種異步IO。
還有一點要說起的是硬件環境,服務器的硬件配置對應用程序的性能提高每每是最直接,也是最簡單的方式,這就是所謂的scale up。這裏不作論述。