轉——淺談如何提升服務器併發處理能力

目錄linux

 

(一)什麼是服務器併發處理能力nginx

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

1.吞吐率算法

2.壓力測試sql

(三)怎麼提升服務器的併發處理能力shell

1,提升CPU併發計算能力
(1)多進程&多線程
(2)減小進程切換,使用線程,考慮進程綁定CPU
(3)減小使用沒必要要的鎖,考慮無鎖編程
(4)考慮進程優先級
(5)關注系統負載
(6)關注CPU使用率,除了用戶空間和內核空間的CPU使用率之外,還要關注I/O wait數據庫

2,減小系統調用apache

3,考慮減小內存分配和釋放編程

(1)改善數據結構和算法複製度後端

(2)使用內存池

(3)考慮使用共享內存

4,考慮使用持久鏈接

5,改進I/O模型

(1)DMA技術

(2)異步I/O
(3)改進多路I/O就緒通知策略,epoll
(4)Sendfile
(5)內存映射
(6)直接I/O

6,改進服務器併發策略

1)一個進程處理一個鏈接,非阻塞I/O,使用長鏈接

 

(2)一個進程處理多個鏈接,異步I/O, 使用長鏈接

 

7,改進硬件環境

 

 

 

(一)什麼是服務器併發處理能力?

 

 

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

 

服務器的本質工做就是,爭取以最快的速度將內核緩衝區中的用戶請求數據一個不剩地都拿出來,而後儘快處理,再將響應數據放到一塊又可以與發送數據的緩衝區中,接着處理下一撥請求。

 

 

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

 

一,吞吐率

 

量化指標:吞吐率,單位時間裏服務器處理的最大請求數,單位req/s

 

再深刻一些,HTTP請求一般是對不一樣資源的請求,也就是請求不一樣的URL,有的是請求圖片,有的是獲取動態內容,有的是靜態頁面,顯然這些請求所花費的時間各不相同,而這些請求再不一樣時間的組成比例又是不肯定的,因此實際狀況下的吞吐率是很是複雜的。

 

 

正由於這些請求的性質不一樣,因此服務器併發能力強弱關鍵在於如何正對不一樣的請求性質來設計最優併發策略。如一臺服務器處理諸多不一樣性質的請求,在必定程度上使得服務器的性能沒法充分發揮。而併發策略的設計就是在服務器同時處理較多請求時,合理協調和充分利用CPU計算和I/O操做,使其在較大併發用戶數的狀況下提供較高吞吐率。

 

 

另外,實際上多少用戶同時發來請求並非服務器所能決定的,一旦實際併發用戶數過多,則勢必影響站點質量。因此得出最大併發用戶數的意義,在於瞭解服務器的承載能力,而且結合用戶規模考慮適當的擴展方案。

 

在考慮用戶模型時,用戶訪問 web站點時一般使用瀏覽器,瀏覽器對於同一域名下URL的併發下載是多線程的,不過有最大限制的,因此前面說到的最大併發數,具體到真實的用戶,可能不是一對一的關係。

 

而從服務器角度,實際併發用戶數的能夠理解爲服務器當前維護的表明不一樣用戶的文件描述符總數,也就是併發鏈接數。服務器通常會限制同時服務的最多用戶數,好比apache的MaxClents參數。

 

這裏再深刻一下,對於服務器來講,服務器但願支持高吞吐率,對於用戶來講,用戶只但願等待最少的時間,顯然,雙方不能知足,因此雙方利益的平衡點,就是咱們但願的最大併發用戶數。

 

 

 

二,壓力測試

 

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

 

壓力測試前提考慮的條件:

1) 併發用戶數

2) 總請求數

3) 請求資源描述

 

併發用戶數是指某一時刻同時向服務器發送請求的用戶總數。

 

 

壓力測試中關係的時間有細分如下2種,

(1) 用戶平均請求等待時間 

(這裏暫不把數據在網絡的傳輸時間,還有用戶PC本地的計算時間計算入內)

(2) 服務器平均請求處理時間

 

 

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

 

  

 

(三)怎麼提升服務器的併發處理能力呢?

 

一,提升CPU併發計算能力

 

 

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

 

 

(1)多進程&多線程

 

多執行流的通常實現就是進程。多進程的好處不只在於CPU時間的輪流使用,還在於對CPU計算和I/O操做進行很好的重疊利用,這裏的I/O主要指磁盤I/O和網絡I/O. 實際上,大多數進程的時間主要消耗在I/O操做上,現代計算機的DMA技術可讓CPU不參與I/O操做的全過程,好比進程經過系統調用,使得CPU向網卡或者磁盤等I/O設備發出指令,而後進程被掛起,釋放出CPU資源,等待I/O設備完成工做後經過中斷來通知進程從新就緒。對於單任務而言,CPU大部分時間空閒,這時候多進程的做用尤其重要。

 

並且進程的優越性還在其相互獨立帶來的穩定性和健壯性方面。

 

進程的缺點: 每一個進程都有本身的獨立空間和生命週期。當子進程被父進程建立後,便將父進程地址空間的全部數據複製到本身的地址空間中,徹底繼承父進程的上下文信息。進程的建立使用fork()系統調用,仍是有必定的開銷的,這個開銷若太頻繁,其可能成爲影響性能的主要因素。

 

 

 

 

 

 

那是否越多進程數越好呢? 請看下面討論:

(2)減小進程切換

 

進程擁有獨立的內存空間,每一個進程都只能共享CPU寄存器。一個進程被掛起的本質是將它在CPU寄存器中的數據拿出來暫存在內存態堆棧着那個,而一個進程恢復工做的本質就是把它的數據從新裝入CPU寄存器,這段裝入和移出的數據稱爲「硬件上下文」,除此以外,進程上下文還包含進程容許所需的一切狀態信息。

 

當硬件上下文頻繁裝入和移出時,所消耗的時間是很是可觀的。可用Nmon工具監視服務器每秒的上下文切換次數。

 

爲了儘可能減小上下文切換次數,最簡單的作法就是減小進程數,儘可能使用線程並配合其它I/O模型來設計併發策略。

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

 

(3)減小使用沒必要要的鎖

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

 

經過鎖競爭的本質,咱們要意識到儘可能減小併發請求對於共享資源的競爭。好比在容許狀況下關閉服務器訪問日誌,這能夠大大減小在鎖等待時的延遲時間。要最大程度減小無辜的等待時間。

 

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

 

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

 

(4)除了上述所說,要優化服務器的併發處理能力,還要考慮進程優先級(可由進程決定),進程調度器會動態調整運行隊列中進程的優先級,經過top觀察進程的PR值

 

 

(5)還要關注系統負載,可在任什麼時候刻查看/proc/loadavg, top中的load average也可看出

 

 

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

 

 

二,考慮系統調用

 

進程若運行在用戶態,這時可以使用CPU和內存來完成一些任務,而當進程須要對硬件外設進行操做的時候(如讀取磁盤文件,發送網絡數據等),就必須切換到內核態,這時它擁有更多的權力來操縱整個計算機。

 

而系統調用涉及進程從用戶態到內核態的切換,致使必定的內存交換,這也是必定程度上的上下文切換,因此係統調用的開銷一般認爲比較昂貴的。

 

因此要減小沒必要要的系統調用,也是服務器性能優化的一個方面。例如在apache中,修改httpd.conf文件,能夠減小對文件路徑中各級目錄下檢測是否存在.htacess文件這個open()系統調用; 還能夠修改httpd.conf文件來減小多餘的gettimeofday()系統調用。

 

 但有時若使用一些簡單的系統調用能代替大量的邏輯運算,這樣反而使用系統調用更能優化性能

 

 

 

 

三,考慮減小內存分配和釋放

 

服務器的工做過程當中,須要大量的內存,使得內存的分配和釋放工做尤其重要。

 

能夠經過改善數據結構和算法複製度來適當減小中間臨時變量的內存分配及數據複製時間,而服務器自己也使用了各自的策略來提升效率。

 

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

 

 

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

 

 

順便說下 Linux進程的地址空間分段

一、棧(存放着局部變量和函數參數等數據),向下生長   (可讀可寫可執行)

二、堆(給動態分配內存是使用),向上生長        (可讀可寫可執行)

三、數據段(保存全局數據和靜態數據)              (可讀可寫不可執行)

四、代碼段(保存代碼)                       (可讀可執行不可寫)

 

 

 

(1)  代碼段(.text)。這裏存放的是CPU要執行的指令。代碼段是可共享的,相同的代碼在內存中只會有一個拷貝,同時這個段是隻讀的,防止程序因爲錯誤而修改自身的指令。

(2)  初始化數據段(.data)。這裏存放的是程序中須要明確賦初始值的變量,例如位於全部函數以外的全局變量:int val=100。須要強調的是,以上兩段都是位於程序的可執行文件中,內核在調用exec函數啓動該程序時從源程序文件中讀入。

(3)    未初始化數據段(.bss)。位於這一段中的數據,內核在執行該程序前,將其初始化爲0或者null。例如出如今任何函數以外的全局變量:int sum;

(4)    堆(Heap)。這個段用於在程序中進行動態內存申請,例如常常用到的malloc,new系列函數就是從這個段中申請內存。

(5)    棧(Stack)。函數中的局部變量以及在函數調用過程當中產生的臨時變量都保存在此段中

 

 

還能夠考慮使用共享內存

共享內存指在多處理器的計算機系統中,能夠被不一樣中央處理器(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和磁盤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操做達到很好的重疊。

 

 

 

 

 

順便說說同步阻塞I/O的缺點,其雖然能夠和多進程有效利用CPU資源,但代價是佔用了大量的內存開銷。而同步非阻塞I/O須要進程執行屢次輪訓查看數據是否就緒,花費了大量的CPU時間。

 

(3)改進多路I/O就緒通知策略,epoll服務器同時處理大量的文件描述符是必不可少的,若採用同步非阻塞I/O模型,若同時接收TCP鏈接的數據,就必須輪流對每一個socket調用接收數據的方法,無論這些socket有沒有可接收的數據,都要詢問一次,假如大部分socket並無數據能夠接收,那麼進程便會浪費不少CPU時間用於檢查這些socket.有沒有能夠接收的數據, 多路I/O就緒通知的出現,提供了對大量文件描述符就緒檢查的高性能方案,它容許進程經過一種方法同時監視全部文件描述符,並能夠快速得到全部就緒的文件描述符,而後只針對這些文件描述符進行數據訪問。

 

 

 

下面詳細介紹被公認爲linux 2.6下性能最好的多路I/O就緒通知方法epoll., 幾乎具有select, poll, /dev/poll等模型的所有優勢

 

 

 

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

 

 

 

epoll主要有2大改進:

 

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

 

 

 

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

 

 

 

 

 

(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調度上儘可能花最少的時間。

 

 

 

 

 

 

 

(1)一個進程處理一個鏈接,非阻塞I/O,使用長鏈接

 

 

 

Apache使用這個模型,其進程的開銷限制了它的併發鏈接數,但從穩定性和兼容性的角度,則其相對安全,任何一個子進程的崩潰不會影響Apache自己,Apache父進程能夠建立新的子進程;另外一方面,Apache通過長期的考驗和廣發的使用,功能模塊很是豐富。因此對於一些併發數要求不高(如150之內),還對其它功能有依賴,那麼可考慮Apache這個模型。

 

 

 

 

 

(2)一個進程處理多個鏈接,異步I/O, 使用長鏈接

 

 

 

一個進程處理多個鏈接,潛在條件就是多路I/O就緒通知的應用。

 

 

 

服務器一般維護者大量的空閒鏈接,有些可能因爲使用長鏈接而在等待超時,有些多是網絡傳輸的延時等等,這時epoll只會關注活躍鏈接,而不在死鏈接上浪費時間,可是select和poll會掃描全部文件描述符,這個是個很是昂貴的開銷。一個典型的應用就是圖片服務器,它們但願爲用戶提供網頁中大量圖片的快速下載,採用長鏈接,可是這些大量鏈接在等待超時關閉前,處於空閒狀態,這種狀況下,epoll依然能很好工做。

 

 

POSIX的標準庫(aio.h)中定義了AIO的一系列接口,它幾乎屏蔽了一切網絡通訊的細節,對使用者而言很是簡單。AIO沒有提供非阻塞的open()方法,進程仍使用open()系統調用來打開文件,而後填充一些I/O請求的數據結構(struct aiocb),接下來調用aid_read()或aid_write()來發起異步I/O操做,一旦請求進入操做隊列後,函數便返回,進程能夠在此調用aid_error()來檢查正在運行的I/O操做的狀態

 

 

 

 

aiocb中相關的域

 

 

 

AIO接口API 

 

 

 

 

關於進程的數量,這個不是越多越好的。大量的進程能夠維持更多的活躍鏈接數,但每一個鏈接的下載速度要遠遠小於前者(因上下文切換的CPU時間減小,有更多的時間用於發起sendfile()系統調用),則怎麼決定worker的進程數取決於應用,例如是但願爲更多的用戶同時提供慢速下載服務,仍是但願爲有限的用戶提供快速的下載服務。

 

 

 

 

 

對於動態內容,如PHP腳本,worker進程一般只是負責轉發請求給獨立的fastcgi進程,或者做爲反向代理服務器將請求轉發給後端服務器,worker進程不太依賴太多的本地資源,能夠適當提升併發鏈接數,但太多的worker進程又會帶來更多的上下文切換開銷和內存開銷,從而總體上全部鏈接的相應時間變長。

 

 

 

而讀取磁盤文件能夠考慮使用異步I/O,在某些場景比性能sendfile()更出色。

 

 

七,改進硬件環境

 

還有一點要說起的是硬件環境,服務器的硬件配置對站點代理的性能提高確定是有的,但這裏不做詳細討論。

相關文章
相關標籤/搜索