1.這里的吞吐率特指Web服務器單位時間內處理的請求。
2.壓力測試的前提:1>並發用戶數 2>總請求數 3>請求資源描述
3.用戶平均請求等待時間主要用戶衡量服務器在一定並發用戶數的情況下,對於單個用戶的服務器質量;而服務器平均請求處理時間與前者相比,則用於衡量服務器的整體服務質量,它其實就是吞吐率的倒數。
4.對http header中標記為Connection: Keep-Alive的請求,開啟web服務器的長連接支持。減少系統調用accept的次數,即減少建立連接的開銷。
5.進程,內核級線程和用戶級線程在不同情況下的優劣。IO模型,mmap(內村映射),直接IO,例如sendfile syscall以及異步IO等。多路IO復用(select, poll,epoll and kqueue etc)
6.服務器並發策略
1> 一個進程處理一個連接,非阻塞IO。穩定性強,但context switch的開銷隨http request遞增而快速增長。
2> 一個內核級線程處理一個連接,非阻塞IO,多進程多線程混合方式。Context switch的問題依然存在。理論上可以支持更多的並發連接。
3>一個進程處理多個連接,非阻塞IO。(epoll, kqueue)lighttpd, nginx。支持並發性能強勁。
吞吐率
這里的吞吐率特指Web服務器單位時間內處理的請求。
吞吐率:一個衡量web服務性能的指標,表征每秒處理請求的次數。該指標受到3方面的因素影響:並發用戶數、總請求數、請求資源的類型。有時在請求總數一定的情況下,並發用戶越多,吞吐率反而越高;另外,請求一個幾kb的文件和請求一個幾m的文件,最終完成處理的時間顯然是不一樣的。因此,吞吐率是一個比較綜合的指標,並不是指並發能力。
cpu並發計算
多進程、多線程的選擇和調度:進程切換和線程切換都需要一定的系統開銷,通常使用多線程模型的web服務器軟件比使用多進程,具備更優的性能。
一個進程被掛起的本質就是將它在CPU寄存器中的數據拿出來暫存在內核態堆棧中,而一個進程恢復工作的本質就是將它的數據重新裝入CPU寄存器,這段裝入和移出的數據稱為硬件上下文
希望服務器支持較大的並發數,就要盡量減少上下文切換次數,最簡單的做法是減少進程數,盡量使用線程並配合其它I/O模型來設計並發策略。
系統調用
系統調用:一些需要從用戶模式切換到內核模式的函數調用可以稱為系統調用,比如:打開文件。系統調用會有一定程度上的開銷,減少系統調用是可以加快處理速度的程序設計細節。
進程通常運行在用戶態,可以使用CPU和內存來完成一些任務,而當進程需要對硬件外設進行操作的時候(讀取磁盤文件,發送網絡數據),就必須切換到內核態
由於系統調用設計進程從用戶態到內核態的切換,導致一定的內存空間交換,這也是一定程度上的上下文切換,所以系統調用的開銷通常認為是比較昂貴
內存分配
nginx多線程來處理請求,多個線程之間可以共享內存資源,分階段的內存分配策略,按需分配,及時釋放,使內存總體使用量保持在很小的數量范圍,10000個非活躍HTTP持久連接只需要2.5MB內存
持久連接
TCP鏈接保持:可以通過保持TCP鏈接來減少服務端和客戶端之間的創建和關閉TCP鏈接的操作。HTTP中的Connection:Keep-Alive就有這樣的功能
I/O模型
IO模型:由於CPU的速度遠遠比IO快,IO延遲往往成為性能瓶頸,因此,IO模型十分重要。
同步阻塞I/O:對於進程來說,一些系統調用為了同步IO,會不同程度上阻塞進程,比如accept、send、read等。
同步非阻塞I/O:對於進程來說,一些系統調用可以在調用完之后立即返回,告知進程IO是否就緒,避免阻塞進程。
多路I/O就緒通知:對於同步非阻塞I/O的方式,進程仍然需要輪詢文件描述符(句柄)來得知哪些IO就緒了,而多路I/O就緒通知將這個過程改成回調通知。
特別是Web服務器,同時處理大量的文件描述符是必不可少的,但是使用同步非阻塞I/O顯然不是最佳的選擇,在這種模型下,我們知道如果服務器想要同時接收多個TCP連接的數據,就必須輪流對每個socket調用接收數據的方法,比如recv()。不管這些socket有沒有可以接收的數據,都要詢問一遍,假如大部分socket並沒有數據可以接收,那么進程便會浪費很多CPU時間用於檢查這些socket,這顯然不是我們所希望看到的。
多路I/O就緒通知的出現,提供了對大量文件描述符就緒檢查的高性能方案,它允許進程通過一種方法來同時監視所有文件描述符,並可以快速獲得所有就緒的文件描述符,然后只針對這些文件描述符進行數據訪問。
epoll可以同時支持水平觸發和邊緣觸發,理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜,因為任何意外的丟失事件就會造成請求處理錯誤。默認情況下epoll采用水平觸發,如果要使用邊緣觸發,則需要在事件注冊時增加EPOLLET選項。而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時拷貝的開銷。
Linux的內核提供一種訪問磁盤文件的特殊方式,它可以將內存中某塊地址空間和我們要指定的磁盤文件相關聯,從而把我們對這塊內存的訪問轉換為對磁盤文件的訪問,這種技術稱為內存映射(MemoryMapping)。在大多數情況下,使用內存映射可以提高磁盤I/O的性能,它無需使用read()或write()等系統調用來訪問文件,而是通過mmap()系統調用來建立內存和磁盤文件的關聯,然后像訪問內存一樣自由的訪問文件。
內存映射:將文件與內存的某塊地址空間相映射,這樣可以想寫內存一樣寫文件。當然這種方式本質上跟寫文件沒有什么區別。
直接I/O:在用戶進程地址空間和磁盤中間通常都會有操作系統管轄的內核緩沖區,當寫入文件時,一般是寫入這個緩沖區,然后由一些延遲策略來寫入磁盤。這樣做可以提高寫效率。但是對於諸如數據庫這樣的應用來說,往往希望自己管理讀寫緩存,避免內核緩沖區的無畏內存浪費。Linux的open函數支持O_DIRECT參數來進行直接IO。
sendfile:如果web服務器想發送一個文件,將會經歷如下過程:打開文件,從磁盤中讀取文件內容(這通常涉及到內核緩沖區數據復制到用戶進程),然后進程通過socket發送文件內容(這通常設計到用戶進程數據復制到網卡內核緩沖區),可以看到重復的數據復制是可以避免的。sendfile可以支持直接從文件內核緩沖區復制到網卡內核緩沖區。
服務器並發策略
PHP腳本,worker進程通常只是負責轉發請求給獨立的fastcgi進程或者作為反向代理服務器將請求轉發給后端服務器,這時候worker進程並不依賴太多的本地資源,所以為了提高並發連接數,可以適當提高worker進程數。但在一般情況下,動態內容本身的吞吐率是相當有限,由於存在腳本解釋器的開銷,通常2000req/s的吞吐率就已經相當高了,所以worker進程的壓力並不是很大。
但如果作為基於反向代理的負載均衡器時,多台后端服務器擴展了動態內容計算能力,woker進程數會逐漸成為整體性能的瓶頸。當然,太多的worker進程又會帶來更多的上下文切換開銷和內存開銷,從而整體上使所有連接的響應時間變長
上述情況的適用范圍不能一刀切,而且這里都是指單機並發,需根據實際情況(實際並發數)來選擇。通常,在並發用戶數較大的情況下,Web服務器使用什么樣的並發策略,是影響最大並發數的關鍵。