高效能伺服器設計
原文:http://blog.chinaunix.net/u/5251/showart_236329.html
先後查看了haproxy,l7sw和lighttpd的相關源碼,無一例外,他們一致認為多工是效能最好的伺服器架構。 事實也確實應該如此,進程的出現一方面就是為了儲存任務的執行內容從而簡化應用程式設計,如果程式的邏輯結構不是很複雜,那麼用整個進程式控制制塊來儲存執 行上下文未免有些大材小用,加上進程調度和其他的一些額外開銷,程式設計上的高效很可能會被執行時的低效所抵消。代價也是有的:程式設計工作將更加具有挑 戰性。
體繫結構選定之後,我們就要考慮更加細節的部分,比如說用什麼作業系統,用作業系統提供的那些API。在這方面,前輩們已經做過很多,我們只需要簡單的“拿來”即可,如果再去枉費唇舌,簡直就是浪費時間,圖財害命。High-Performance Server Architecture從根本上分析了導致伺服器低效的罪魁禍首:資料拷貝、(使用者和核心)環境切換、記憶體申請(管理)和鎖競爭;The C10K Problem列舉並分析了UNIX、Linux甚至是部分Windows為提高伺服器效能而設計的一些系統調用介面,這篇文檔的難能可貴之處還在於它一致保持更新;Benchmarking BSD and Linux更是通過實測資料用圖表的形式把BSD和Linux的相關係統調用的效能直觀地陳列在我們眼前,結果還是令人激動的:Linux 2.6的相關係統調用的時間複雜度竟然是O(1)。
簡單的總結如下:
1. 作業系統採用Linux 2.6.x核心,不僅因為它的高效能,更因為它大開源(這並不是說其他的UNIX或者是BSD衍生物不開源)給程式設計帶來的便利,我們甚至可以把服務做到核心空間。
2. 多工採用epoll的“電平觸發”(Level Triggered)模式,必要時可以採用“邊緣觸發”(Edge Triggered),但要注意防止資料停滯。
3. 為避免資料拷貝可以採用sendfile系統調用發送小檔案,或者是檔案的小部分,注意避免sendfile因磁碟IO而導致的阻塞。
4. 如果服務作業設計大量磁碟IO操作,應選用Linux核心提供的非同步IO機制,其對應的使用者空間庫為libaio,注意:這裡提到非同步IO庫並非目前glibc中附帶的非同步IO實現。
5. 如果同時有多個資料需要傳輸,採用writev/readv來減少系統調用所帶來的環境切換開銷,如果資料要寫到網路通訊端檔案描述符,這也能在一定程度上防止網路上出現比較小幀,為此,還可以有選擇地開啟TCP_CORK選項。
6. 實現自己的記憶體管理,比如說快取資料,複用常用資料結構等。
7. 用多線程替代多進程,線程庫當然選擇nptl。
8. 避免進程/線程間非必要的同步,保持互斥區的短小。
上面這些瑣碎的細節在ESR看來可能都是過早最佳化,他可 能又會建議我們等待硬體的升級。哈哈,提醒還是不無道理的,演算法的設計部分,我們更要下大力氣,因地制宜地降低演算法的時間複雜度。為什麼不提空間複雜度 呢?記憶體的價格還是相對低廉吧,不過還是不要忘記現在的電腦瓶頸多在記憶體的訪問。
有一點需要提醒一下,目前SMP系統和多核心CPU比較 常見,如果還是僅採用單進程(線程)的多工模型,那麼同一時間將只有一個CPU為這個進程(線程)服務,並不能充分發揮CPU的計算能力,所以需要至 少CPU(CPU核心)數目個進程(線程)來分擔系統負擔。有一個變通的解決方案:不用修改源碼,在伺服器上運行兩個服務程式的執行個體,當然這個時候服務端 口應該是不同的,然後在其前端放置負載平衡器將流量和串連平均分配到兩個服務連接埠,可以簡單的通過DNAT來實現負載平衡。其實,這個時候我們已經把多 CPU或者是多核系統看成了多個系統組成的叢集。
為了提高伺服器的效能,單純的依靠提高單個伺服器的處理 能力似乎不能奏效,況且配置越高的伺服器花銷也就越高,為此人們經常採用伺服器叢集的方式,通過把計算儘可能地分配到相對比較廉價的機器上單獨完成,籍此 來提升伺服器的整體效能,事實證明,這種體繫結構不僅是切實可行的,而且還能提高伺服器的可用性,容錯能力也較強。在網路伺服器方面,Linux核心中的 由國人章文嵩先生設計的IP層負載平衡解決方案LVS比較有名,還有就是工作於應用程式層的haproxy和剛剛起步的l7sw。