最近看了一下Unix網絡編程相關的內容,而後發現了一篇很是經典的文章, 內容可能不是很新,不過真的很經典,C10K問題,簡單翻譯了一下(markdown轉換過來格式可能存在少許問題)對掌握linux io和 linux 線程會有更深的理解.(文內存在大量的連接.) 原文連接 The C10K problemphp
如今 web 服務器須要同時處理上萬請求,難道不是嗎?畢竟現在的網絡將會有很大的發展空間. 計算機也一樣強大.你能夠花1200美圓買一臺 1000MHz,2G 內存和1000Mbits/sec的網卡的機器.讓咱們來看看-- 20000 客戶端,每一個客戶端 50KHz, 1000Kb 和每秒 50Kb,那沒有什麼比這兩萬個客戶端每一個每秒從磁盤中取出4千字節並將它們每秒發送到網絡上去更消耗資源了.(順便說一下,每一個客戶端0.0.8美圓,一些操做系統收費的單個客戶端 美圓的許可費看起來有點貴)因此硬件再也不是一種瓶頸.css
在1999年,最繁忙的 ftp 網站之一, cdrom.com, 實際上經過一個千兆以太網網卡同時處理 10000個客戶端.如今相同的速度也被ISP 提供,他們但願它變得愈來愈受大型企業客戶的歡迎.html
輕量級的客戶端計算模型彷佛又開始變得流行起來了 - 服務器在互聯網上運行,爲數千個客戶提供服務.前端
基於以上的一些考慮,這有一些關於如何配置操做系統或者編寫支持數千客戶端的代碼問題提出了一些注意點. 討論的中心的主要是圍繞着類Unix操做系統,由於這是我我的感興趣的領域,可是Windows也會涉及一點.java
參閱下 Nick Black 的傑出的 快速的Unix服務器網頁,瞭解大約2009的狀況.node
在2003年10月,Felix von Leitner 整理了一個優秀的關於網絡可擴展性的網站和演示,完成了多種不一樣的網絡系統調用和操做系統的性能比較.其中一個發現是 linux 2.6 內核擊敗了 2.4 內核,固然這裏有不少很好的圖片會讓操做系統開發人員在平時提供點想法.linux
若是你沒有閱讀過the late W. Richard Stevens的Unix網絡編程: 網絡Apis:套接字和Xti(第1卷)的拷貝,請儘快獲取一份,它描述了不少的於 I/O 策略和編寫高性能服務器的陷阱.它甚至談到了 'thundering herd'問題.當你在閱讀它時,請閱讀 Jeff Darcy寫的關於高性能服務器設計.nginx
(另一本書構建可擴展的網站可能會對使用而不是編寫一個web服務器的人會有幫助)git
如下提供了幾個預打包的庫,它們抽象了下面介紹的一些技術,使代碼與操做系統隔離,並使其更具可移植性.github
網絡軟件的設計者有多種選擇.這有一些:
下邊的5中組合彷佛很是流行:
... 在全部的網絡句柄上都設置爲非阻塞模式,使用 select() 或則 poll() 去告知哪一個網絡句柄處理有數據等待.此模型是最傳統的.這種模式下,內核告訴你是否一個文件描述符就緒,自從上次內核告訴你它以來,你是否對該文件描述符作了任何事情.('水平觸發'這個名詞來自計算機硬件設計;它與'邊緣觸發'相反).Jonathon Lemon在他的關於BSDCON 2000 paper kqueue()的論文中介紹了這些術語
注意: 牢記來自內核的就緒通知只是一個提示,這一點尤其重要;當你嘗試去讀取文件描述符的時候,它可能沒有就緒.這就是爲何須要在使用就緒通知時使用非阻塞模式的緣由.
一個重要的瓶頸是 read()或 sendfile() 從磁盤塊讀取時,若是該頁當前並不在內存中.在設置非阻塞模式的磁盤文件處理是沒有影響的.內存映射磁盤文件也是如此.首先一個服務須要磁盤 I/O時,他的處理塊,全部客戶端必須等待,所以原生非線程性能將會被浪費了.
這也就是異步 I/O 的目的,固然僅限於沒有 AIO 的系統上,用多線程和多進程進行磁盤 I/O 也可能解決這個瓶頸.一種方法是使用內存映射文件,若是 mincore() 表示須要 I/O,讓一個工做線程去進行 I/O 操做,並繼續處理網絡流量.Jef Poskanzer 提到 Pai, Druschel, and Zwaenepoel的1999 Flash web服務器使用這個技巧;他們在Usenix '99發表了關於它的演講.看起來 mincore() 在BSD-derived Unixes 上是可用的,如像FreeBSD和Solaris,但它不是單Unix規範的一部分.從kernel2.3.51 開始,它也開始是linux的一部分,感謝Chuck Lever.
可是在2003年十一月的 freebsd-hackers list, Vivek Pei 等人報道了使用他們的 Flash web服務器有一個很好的結果.而後在攻擊其瓶頸,其中發現一個瓶頸是 mincore(猜想以後這不是一個好辦法),另一個就是 sendfile 阻塞磁盤訪問;他們一種修改的 sendfile(),當他的訪問磁盤頁還沒有處於核心狀態時返回相似 EWOULDBLOCK 的內容,提高了性能.(不知道怎麼告訴用戶頁如今是常駐的...在我看來真正須要的是aio_sendfile().)他們優化的最終結果是在 1GHZ/1GB 的FreeBSD盒子上 SpecWeb99 得分約爲800,這比spec.org上的任何文件都要好.
在非阻塞套接字的集合中,關於單一線程如何告知哪一個套接字是準備就緒的,列出了幾種方法:
就緒改變通知(或邊緣就緒通知)意味着你向內核提供文件描述符,而後,當該描述符從 not ready 轉換爲 ready 時,內核會以某種方式通知你.而後它假定你已知文件描述符已準備好,同時不會再對該描述符發送相似的就緒通知,直到你在描述符上進行一些操做使得該描述符再也不就緒(例如,直到你收到 EWOULDBLOCK 錯誤爲止)發送,接收或接受呼叫,或小於請求的字節數的發送或接收傳輸).
當你使用就緒改變通知時,你必須準備處理好虛假事件,由於最多見的實現是隻要接收到任何數據包都發出就緒信號,而無論文件描述符是否準備就緒.
這與"水平觸發"就緒通知相反.它對編程錯誤的寬容度要低一些,由於若是你只錯過一個事件,那麼事件的鏈接就會永遠停滯不前.能夠儘管如此,我發現邊緣觸發的就緒通知能讓使用OpenSSL編程非阻塞客戶端變得更容易,所以仍是值得嘗試.
[Banga, Mogul, Drusha '99]在1999年描述了這種類型的模式.
有幾種API使應用程序檢索"文件描述符準備就緒"通知:
/* Mask off SIGIO and the signal you want to use. */
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigaddset(&sigset, SIGIO);
sigprocmask(SIG_BLOCK, &m_sigset, NULL);
/* For each file descriptor, invoke F_SETOWN, F_SETSIG, and set O_ASYNC. */
fcntl(fd, F_SETOWN, (int) getpid());
fcntl(fd, F_SETSIG, signum);
flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK|O_ASYNC;
fcntl(fd, F_SETFL, flags);複製代碼
每一個fd一個信號
Signal-per-fd是由Chandra和Mosberger提出的對實時信號的一種改進,它經過合併冗餘事件來減小或消除實時信號隊列溢出.但它並無超越 epoll.他們的論文 (www.hpl.hp.com/techreports…)將此方案的性能與select() 和 /dev/poll 進行了比較.
Vitaly Luban於2001年5月18日宣佈了一項實施該計劃的補丁;他的補丁產生於www.luban.org/GPL/gpl.htm….(注意:截至2001年9月,這個補丁在高負載下可能存在穩定性問題.dkftpbench在大約4500個用戶可能會觸發oops.)
參閱Poller_sigfd (cc,h)是一個如何使用 signal-per-fd 與其餘就緒通知模式交互的示例.
這在Unix至今都沒有流行起來,多是由於較少的操做系統支持了異步 I/O,也多是由於(像非阻塞 I/O)它要求從新思考應用程序.在標準 Unix 下,異步 I/O 被aio_ 接口提供(從該連接向下滾動到"異步輸入和輸出"),它將信號和值與每一個 I/O操做相關聯.信號及其值排隊並有效地傳遞給用戶進程.這是來自 POSIX 1003.1b 實時擴展,也是單Unix規範第二版本.
AIO一般與邊緣觸發完成通知一塊兒使用,即當操做完成時,信號排隊.(它也能夠經過調用aio_suspend()與水平觸發的完成通知一塊兒使用,雖然我懷疑不多有人這樣作.)
glibc 2.1和後續版本提供了一個普通的實現,僅僅是爲了兼容標準,而不是爲了得到性能上的提升.
截止linux內核 2.5.32,Ben LaHaise的 Linux AIO 實現已合併到主 Linux 內核中.它不使用內核線程,同時還具備很是高效的底層api,可是(從2.6.0-test2開始)還不支持套接字.(2.4內核還有一個 AIO 補丁,但 2.5/2.6 實現有些不一樣.)更多信息:
Suparna還建議看看DAFS API 對 AIO 的方法.
Red Hat AS和 Suse SLES 都在2.4內核上提供了高性能的實現.它與2.6內核實現有關,但並不徹底相同.
2006年2月,網絡AIO有一個新的嘗試;看上面關於Evgeniy Polyakov基於kevent的AIO的說明
在1999年,SGI爲 Linux 實現了高速 AIO,從版本1.1開始,聽說能夠很好地兼容磁盤 I/O 和套接字.它彷佛使用內核線程.對於那些不能等待 Ben 的 AIO 支持套接字的人來講,會仍然頗有用.
O'Reilly的書POSIX.4: 真實世界的編程聽說涵蓋了對aio的一個很好的介紹.
Solaris早期非標準的aio實現的教程在線Sunsite.這可能值得一看,但請記住,你須要在精神上將"aioread"轉換爲"aio_read"等.
請注意,AIO不提供在不阻塞磁盤 I/O 的狀況下打開文件的方法; 若是你關心打開磁盤文件致使休眠,Linus建議你只需在另外一個線程中執行 open()而不是是進行 aio_open() 系統調用.
在Windows下,異步 I/O 與術語"重疊 I/O "和 IOCP 或"I/O完成端口"相關聯.微軟的 IOCP 結合了現有技術的技術,如異步 I/O(如aio_write)和排隊完成通知(如將 aio_sigevent 字段與 aio_write 一塊兒使用時),以及阻止某些請求嘗試保持運行線程數量相關的新想法具備單個 IOCP 常量.欲得到更多信息,請參閱 sysinternals.com 上的 Mark Russinovich 撰寫的I/O 完成端口的內部,Jeffrey Richter的書 "爲Microsoft Windows 2000編寫服務端程序"(Amazon, MSPress), U.S. patent #06223207, 或者MSDN.
...讓 read() 和 write() 阻塞.每一個客戶端使用整個棧偵會有很大的缺點,就是消耗內存.不少操做系統也難以操做處理上百個線程.若是每一個線程得到2MB堆棧(不是很是見的默認值),則在 32 位機器上的 (2^30/2 ^21)= 512 個線程上會耗盡虛擬內存,具備 1GB 用戶可訪問的VM(好比,Linux 一般在 x86 上容許)你能夠經過提供更小的棧解決這個問題,可是線程一旦建立,大多數線程庫都不容許增長線程棧,因此這樣作就意味着你必須使你的程序最小程度地使用內存.你也能夠經過轉移到64位處理器來解決這個問題.
在Linux, FreeBSD, Solaris上的線程支持是正在完善,即便對於主流用戶來講,64位處理器也即將到來.也許在不就的未來,那些喜愛每一個客戶端使用一個線程的人也有能力服務10000個客戶端了.然而,在目前這個時候,若是你真的想要支持那麼多客戶,你可能最好仍是使用其餘一些方法.
對於絕不掩飾的親線程觀點的人,請參閱爲何事件是一個壞主意(對於高併發服務器)由von Behren,Condit和Brewer,UCB,在HotOS IX上發佈.有反線營地的任何人能指出一篇反駁這篇論文的論文嗎?:-)
Linux線程是標準Linux線程庫的名稱.從 glibc2.0 開始,它就集成到 glibc 中,主要是符合 Posix 標準,但性能和信號支持度上都不盡如人意.
NGPT是 IBM 啓動的爲 Linux 帶來更好的 Posix 線程兼容性的項目.他目前的穩定版本是2.2,工做的很是好...可是 NGPT 團隊宣佈他們將 NGPT 代碼庫置於support-only模式,由於他們以爲這是"長期支持社區的最佳方式". NGPT團隊將會繼續改進 Linux 的線程支持,可是如今主要集中在NPTL.(感謝NGPT團隊的出色工做以及他們以優雅的方式轉向NPTL.)
NPTL是由Ulrich Drepper(glibc的維護人員)和Ulrich Drepper發起的,目的是爲Linux帶來的world-class Posix線程庫支持.
截至2003年10月5日,NPTL 如今做爲附加目錄合併到 glibc cvs 樹中(就像linux線程),因此它幾乎確定會與 glibc 的下一個版本一塊兒發佈.
Red Hat 9是最先的包含NPTL的發行版本(這對某些用戶來講有點不方便,但有人不得不打破僵局...)
NPTL 連接:
這是我嘗試寫的描述NPTL歷史的文章(也能夠看看Jerry Cooperstein的文章):
在2002年3月, NGPT團隊的Bill Abt, glibc的維護者與Ulrich Drepper和其餘人會面探討LinuxThreads的發展.會議產生的一個想法是提升互斥性能;Rusty Russell 等人後來實現了快速用戶空間鎖(futexes)),它如今被用在 NGPT 和 NPTL 中.大多數與會者認爲NGPT應該被合併到glibc.
但Ulrich Drepper並不喜歡 NGPT,認爲他能夠作得更好.(對於那些試圖爲 glibc 作出補丁的人來講,這可能不會讓人大吃一驚:-)在接下來的幾個月裏,Ulrich Drepper,Ingo Molnar致力於 glibc 和內核的變化,這些變化構成了 Native Posix線程庫(NPTL).NPTL使用了NGPT設計的全部內核改進,並利用一些新功能:
> NPTL使用NGPT引入的三個內核特性:getpid()返回 PID,CLONE_THREAD 和 futexes;NPTL還使用(並依賴)更普遍的新內核功能,做爲該項目的一部分開發.
> 引入 2.5.8 內核的 NGPT 中的一些項目獲得了修改,清理和擴展,例如線程組處理(CLONE_THREAD).[影響 NGPT 兼容性的 CLONE_THREAD 更改與 NGPT 人員同步,以確保NGPT不會以任何不可接受的方式破壞.]
> NPTL開發和使用的內核功能在設計白皮書中有描述,people.redhat.com/drepper/npt… ...
> 簡短列表:TLS支持,各類克隆擴展(CLONE_SETTLS,CLONE_SETTID,CLONE_CLEARTID),POSIX線程信號處理,sys_exit()擴展(在VM發佈時發佈TID futex)sys_exit_group()系統調用,sys_execve()加強功能 並支持分離的線程.
> 還有擴展 PID 空間的工做 - 例如,procfs因爲 64K PID 的設計,爲max_pid 和 pid 分配可伸縮性的工做而崩潰.此外,還進行了許多僅針對性能的改進.
> 本質上,新功能徹底是使用1:1線程方法 - 內核如今能夠幫助改進線程的全部內容,而且咱們爲每一個基本線程原語精確地執行最低限度必需的上下文切換和內核調用.
FreeBSD線程支持
FreeBSD同時支持 linux 線程和用戶空間線程庫.此外,在 FreeBSD 5.0 中引入了一個名爲 KSE 的 M:N 實現.概述,參閱www.unobvious.com/bsd/freebsd….
2003年3月25日,Jeff Roberson在 freebsd-arch 上發佈了帖子:
...感謝Julian,David Xu,Mini,Dan Eischen,和其它的每一位參加了KSE和libpthread開發的成員所提供的基礎,Mini和我已經開發出了一個 1:1 模型的線程實現.此代碼與 KSE 並行工做,不會以任何方式更改它.它實際上有助於經過測試共享位來使M:N線程更接近...
並於2006年7月,Robert Watson提出的 1:1 線程應該成爲FreeBsd 7.x中的默認實現:
我知道過去曾經討論過這個問題,但我認爲隨着7.x向前推動,是時候從新考慮一下這個問題.在許多常見應用程序和特定場景的基準測試中,libthr 表現出比 libpthread 更好的性能... libthr也在咱們的大量平臺上實現的,而且已經在幾個平臺上實現了 libpthread.咱們對 MySQL 和其餘大量線程的使用者建議是"切換到libthr",這也是暗示性的! ...因此草書建議是:使libthr成爲7.x上的默認線程庫.
根據Noriyuki Soda的說明:
內核支持 M:N 基於 Scheduler Activations 模型線程庫將於2003年1月18日合併到NetBSD-current中.
更多細節,看由NethanD系統公司的 Nathan J. Williams在2002年的FREENIX上的演示An Implementation of Scheduler Activations on the NetBSD Operating System.
Solaris中的線程支持發展...從 Solaris 2 到 Solaris 8,默認線程庫使用 M:N 模型,但 Solaris 9 默認爲 1:1 模型線程支持.看Sun的多線程編程指導和Sun關於 Java 和 Solaris 線程的筆記
衆所周知,直到 JDK1.3.x 的 Java 不支持處理除每一個客戶端一個線程以外的任何網絡鏈接方法.Volanomark是一個很好的微基準測試,它能夠在不一樣數量鏈接中測量每秒消息的吞吐量.截至2003年5月,來自不一樣供應商的 JDK 1.3實際上可以處理一萬個同時鏈接 - 儘管性能顯着降低.請參閱表4,瞭解哪些 JVM 能夠處理10000個鏈接,以及隨着鏈接數量的增長性能會受到影響.
在實現線程庫時有一個選擇: 你能夠將全部線程支持放在內核中(這稱爲 1:1 線程模型),或者您能夠將其中的至關一部分移動到用戶空間(這稱爲 M:N 線程模型).有一點,M:N被認爲是更高的性能,但它太複雜了,很難作到正確,大多數人都在遠離它.
聽說 Novell 和微軟已經在不一樣的時間作過這個,至少有一個 NFS 實現是這樣作的,khttpd爲Linux和靜態網頁作了這個,"TUX"(線程linux web服務器)是Ingo Molnar爲Linux的一個快速且靈活的內核空間HTTP服務器. Ingo的2000年9月1日公告表示能夠從ftp://ftp.redhat.com/pub/redhat/tux 下載 TUX 的alpha版本,並解釋如何加入郵件列表以獲取更多信息.
linux-kernel列表一直在討論這種方法的優勢和缺點,並且彷佛不是將 Web 服務器移動到內核中,內核應該添加最小的鉤子來提升Web服務器的性能.這樣,其餘類型的服務器能夠受益.參見例如Zach Brown的評論關於 userland 與內核 http 服務器的關係.彷佛2.4 linux 內核爲用戶程序提供了足夠的功能,由於X15服務器的運行速度與Tux同樣快,但不使用任何內核修改.
例如,參見netmap數據包 I/O 框架和Sandstorm基於這個概念驗證Web服務器.
Richard Gooch已經寫了一篇關於討論 I/O 選項的論文.
在2001年,Tim Brecht和MMichal Ostrowski測試了多種策略爲簡化基於 select 的服務器.他們的數據值得一看.
在2003年,Tim Brecht發佈了userver的源代碼,由Abhishek Chandra, David Mosberger, David Pariag 和 Michal Ostrowski 編寫的幾臺服務器組成的小型Web服務器.它能使用select(), poll(),或者sigio.
早在1999年3月,Dean Gaudet的文章:
我不斷被問到"爲何大家不使用像Zeus這樣的基於select/event的模型?它顯然是最快的."...
他的理由歸結爲"這真的很難,收益還不清楚",然而,在幾個月內,很明顯人們願意繼續努力.
Mark Russinovich 寫了一篇社論和文章討論在 linux內核2.2 中的 I/O 策略問題.值得一看,甚至他彷佛在某些方面也被誤導了.特別是,他彷佛認爲Linux 2.2 的異步 I/O (參見上面的F_SETSIG)在數據就緒時不會通知用戶進程,只有當新鏈接到達時.這彷佛是一個奇怪的誤解.也能夠看看更早的草案,Ingo Molnar於1999年4月30日的反駁,Russinovich對1999年5月2日的評論, 一個來自Alan Cox的反駁,和各類linux-kernel的帖子,我懷疑他試圖說 Linux 不支持異步磁盤I/O,這曾經是真的,可是如今 SGI 已經實現了KAIO,它再也不那麼真實了.
有關"完成端口"的信息,請參閱sysinternals.com和MSDN上的這些網頁,他說這是NT獨有的;簡而言之,win32的"重疊 I/O "結果過低而不方便,"完成端口"是一個提供完成事件隊列的包裝器,加上調試魔術試圖保持運行的數量,若是從該端口獲取完成事件的其餘線程正在休眠(可能阻塞I/O)則容許更多線程獲取完成事件,從而使線程保持不變。
1999年9月對linux-kernel進行了一次有趣的討論"> 15,000個同時鏈接"(和線程的第二週).強調:
有趣的閱讀!
set kern.maxfiles=XXXX
echo 32768 > /proc/sys/fs/file-max
ulimit -n 32768
ulimit -n 32768
echo 32768 > /proc/sys/fs/file-max echo 65536 > /proc/sys/fs/inode-max
ulimit -n 32768
ulimit -n 32768
在任何體系結構上,您可能須要減小爲每一個線程分配的堆棧空間量,以免耗盡虛擬內存.若是使用pthreads,可使用pthread_attr_init() 在運行時設置它。
經過JDK 1.3, Java的標準網絡庫大多提供了一個客戶端一個線程模型.這是一種非阻塞讀的方式,可是沒有辦法去作非阻塞寫.
在2001年5月. JDK 1.4 引進了包 java.nio 去提供徹底支持非阻塞 I/O (和其餘好的東西).看發行說明警告.嘗試一下,給Sun反饋!
HP 的 java 也包含了一個線程輪訓API.
在2000, Matt Welsh爲java實現了非阻塞套接字.他的性能基準測試顯示他們優於在處理大量(大於10000)鏈接的服務器中的阻塞套接字.他的類庫被稱做java-nbio;他是Sandstorm項目的一部分.基準測試顯示10000鏈接的性能是可用的.
參閱 Dean Gaude關於 Java , 網絡 I/O, 和線程主題的文章,和 Matt Welsh 寫的關於事件對比工做線程的論文
在 NIO 以前,有幾個改進Java的網絡API的建議:
注意: 若是TCP堆棧有一個bug,致使 SYN 或 FIN 時間更短(200ms)延遲,如 Linux 2.2.0-2.2.6 所示,而且操做系統或 http 守護程序對鏈接數有硬限制,你會期待這種行爲.可能還有其餘緣由.
對於Linux,看起來內核瓶頸正在不斷修復.看Linux Weekly News,Kernel Traffic, the Linux-Kernel mailing list,和my Mindcraft Redux page.
1999年3月,微軟贊助了一項比較 NT 和 Linux 的基準測試,用於服務大量的 http 和 smb客戶端,linux的結果不如人意.另見關於Mindcraft 1999年4月基準測試的文章瞭解更多信息
另請參見Linux可擴展性項目.他們正在作有趣的工做.包括Niels Provos的暗示民意調查補丁,關於雷鳴般的羣體問題的一些工做.
另請參與Mike Jagdis致力於改進 select() 和 poll();這是Mike關於它的帖子.
Mohit Aron(aron@cs.rice.edu)寫道,TCP中基於速率的時鐘能夠將"緩慢"鏈接上的HTTP響應時間提升80%
特別是兩個測試簡單,有趣,並且很難:
Jef Poskanzer發佈了比較許多Web服務器的基準測試.看他的結果www.acme.com/software/th…
我也有關於將thttpd與Apache比較的一些舊筆記可能對初學者感興趣.
Chuck Lever不斷提醒咱們關於Banga和Druschel關於Web服務器基準測試的論文.值得一讀。
IBM有一篇名爲Java服務器基準測試的優秀論文.[Baylor 等,2000年].值得一讀。
Nginx 是一個web服務器,它使用目標操做系統上可用的任何高效網絡事件機制.它變得很是流行;這甚至有關於它的兩本書