服務端經典的C10k問題(譯)

服務端經典的C10K問題

最近看了一下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

內容

  • C10K問題
    • 內容]
    • 相關網站
    • 預讀書籍
    • I/O 框架
    • I/O 策略
      • 1. 一個線程服務多個客戶端,使用非阻塞 IO 和水平觸發的就緒通知
      • 2. 一個線程服務多個客戶端,使用非阻塞 IO 和就緒改變通知
      • 3. 一個線程服務多個客戶端,使用異步 I/O
      • 4. 一個線程服務一個客戶端
      • Linux線程
        • NGPT: Linux的下一代 Posix 線程
        • NPTL: Linux原生的 Posix 線程庫
        • FreeBSD 線程支持
        • NetBSD 線程支持
        • Solaris 線程支持
        • JDK 1.3.x及更早版本中的Java線程支持
        • 注意:1:1 線程與 M:N線程
      • 5. 將服務端代碼構建到內核中
    • 將 TCP 協議棧帶入用戶空間
    • 評論
    • 打開文件句柄的限制
    • 線程限制
    • Java問題]
    • 其餘建議
    • 其餘限制
    • 內核問題
    • 測試服務性能
    • 例子
    • 有趣的基於 select() 的服務器
    • 有趣的基於 /dev/poll 的服務器
    • 有趣的基於 epoll 的服務器
    • 有趣的基於 kqueue() 的服務器
    • 有趣的基於實時信號的服務器
    • 有趣的基於線程的服務器
    • 有趣的內核服務器
    • 其餘有趣的連接

相關網站

參閱下 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

I/O 框架

如下提供了幾個預打包的庫,它們抽象了下面介紹的一些技術,使代碼與操做系統隔離,並使其更具可移植性.github

  • ACE,一個輕量級的 C++ I/O 框架,包含一些用面對對象的思想實現的 I/O 策略和許多其餘有用的事情.特別的,他的 Reactor 以面對對象的方式執行非阻塞 I/O,Proactor 是一種面對對象處理異步 I/O 的的方式.
  • ASIO 是一個 C++ I/O 框架,它正在成爲Boost的一部分.這就像是爲 STL 時代更新的ACE.
  • libevent 是 Niels Provos 寫的一個輕量級的 C I/O 框架.它支持 kqueue 和 select,即將支持 poll 和 epoll.我想它應該只採用了水平觸發,這具備兩面性.Niels給了一個圖來講明時間和鏈接數目在處理一個事件上的功能,圖中能夠看出kqueue 和 sys_epoll 是明顯的贏家.
  • 我本身在輕量級框架的嘗試(惋惜的是沒有保持更新)
    • Poller 是一個輕量級的 C++ I/O 框架,它使用任何一種準備就緒API(poll, select, /dev/poll, kqueue, sigio)實現水平觸發準備就緒API. 以其餘多種 API 爲基礎測試,Poll的性能好的多.文檔鏈到下面的Poller 子類,該連接文檔的下面一部分說明了如何使用這些準備就緒API.
    • rn 是一個輕量級的C I/O 框架,這是我在Poller以後的第二次嘗試. 他使用lgpl(所以它更容易在商業應用程序中使用) 和 C(所以更容易在非 C++ 的產品中使用).現在它被應用在一些商業產品中.
  • Matt Welsh 在2000年4月寫了一篇關於如何在構建可擴展性服務時去平衡工做線程和事件驅動使用的論文,該論文描述了他的 Sandstorm I/O 框架.
  • Cory Nelson 的Scale!庫 - 一個Windows下的異步套接字, 文件, 和管道 I/O 庫.

I/O 策略

網絡軟件的設計者有多種選擇.這有一些:

  • 是否以及如何在單個線程發出多個 I/O 調用
    • 不處理;使用阻塞和同步調用,儘量的使用多個線程和進程實現併發.
    • 使用非阻塞調用(如,在一個socket write()上設置 O_NONBLOCK) 去啓動 I/O,就緒通知(如,poll() 或則 /dev/poll)知道何時通道是 OK 的而後開啓下一個 I/O.一般這隻能用於網絡 I/O,而不能用於磁盤 I/O.
    • 使用異步調用(如,aio_write())去啓動 I/O,完成通知(如,信號或完成端口)去通知 I/O 完成.這同時適用於網絡和磁盤 I/O.
  • 如何控制每一個客戶的服務
    • 一個進程服務一個客戶(經典的 Unix 方法,從1980年左右就開始使用)
    • 一個系統級別線程服務多個客戶;每一個客戶經過如下控制:
      • 一個用戶級別線程(如. GNU 狀態線程, 帶綠色線程的經典 java)
      • 狀態機(有點深奧,但在某些圈子裏很受歡迎; 個人最愛)
      • continuation (有點深奧,但在某些圈子裏很受歡迎; 個人最愛)
    • 一個系統級線程服務單個客戶(如,經典的帶有原生線程的Java)
  • 一個系統級線程服務每一個活躍的客戶(如. Tomcat與apache的前端;NT完成端口; 線程池)
  • 是否使用標準系統服務,或者構建服務到內核中(如,在一些自定義驅動,內核模塊,或者 VxD)

下邊的5中組合彷佛很是流行:

  1. 一個線程服務多個客戶端.使用非阻塞 I/O 和水平觸發就緒通知.
  2. 一個線程服務多個客戶端.使用非阻塞 I/O 和就緒更改通知.
  3. 一個線程服務多個客戶端. 使用異步 I/O.
  4. 一個線程服務多個客戶端.使用阻塞 I/O
  5. 將服務端代碼構建到內核

1. 一個線程服務多個客戶端,使用非阻塞 IO 和水平觸發就緒通知

... 在全部的網絡句柄上都設置爲非阻塞模式,使用 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上的任何文件都要好.

在非阻塞套接字的集合中,關於單一線程如何告知哪一個套接字是準備就緒的,列出了幾種方法:

  • 傳統的 select()
    不幸的, select() 限制了 FD_SETSIZE 的處理.這個限制被編譯到標準庫和用戶程序中.(一些 C 庫版本讓你在編譯應用程序的時候提示這個限制.)
    參閱Poller_select (cc,h)是一個如何使用 select() 與其餘就緒通知模式交互的示例.
  • 傳統的 poll()
    對於 poll() 能處理的文件描述符數量的沒有硬編碼限制,可是當有上千鏈接時會變得慢,由於大多數文件描述符在某個時刻都是是空閒的,徹底掃描成千上萬個文件描述符會花費時間.
    一些操做系統(像,Solaris 8)使用像 poll hinting 的技術加速了 poll() 等,Niels Provos 在1999年爲Linux實現和並利用基準測試程序測試.
    參閱Poller_poll (cc,h, benchmarks)是一個如何使用 poll() 與其餘就緒通知模式交互的示例.
  • /dev/poll
    這是推薦在Solaris 代替poll的方法
    /dev/poll 的背後思想就是利用 poll() 在大部分的調用時使用相同的參數.使用/dev/poll,獲取一個 /dev/poll 的文件描述符,而後把你關心的文件描述符寫入到/dev/poll的描述符;而後,你就能夠從該句柄中讀取當前就緒文件描述符集.
    它悄悄的出如今 Solaris 7 中(看 patchid 106541),可是它第一次公開現身是在Solaris 8中;據 Sun 透露,在750客戶端的狀況下,這隻有 poll() 的10%的開銷.
    在Linux上嘗試了 /dev/poll 的各類實現,但它們都沒有像 epoll 同樣高效,而且從未真正完成.不推薦在Linux上使用 /dev/poll.
    參閱Poller_devpoll (cc, h基礎測試)是一個如何使用 /dev/poll 與其餘就緒通知模式交互的示例.(注意 - 該示例適用於Linux /dev/poll,可能沒法在 Solaris 上正常運行.)
  • kqueue()
    是在FreeBSD系統上推薦使用的代替poll的方法(很快,NetBSD).
    看下邊 kqueue() 能夠指定邊緣觸發或水平觸發.

2. 一個線程服務多個客戶端, 使用非阻塞 IO 和就緒改變通知

就緒改變通知(或邊緣就緒通知)意味着你向內核提供文件描述符,而後,當該描述符從 not ready 轉換爲 ready 時,內核會以某種方式通知你.而後它假定你已知文件描述符已準備好,同時不會再對該描述符發送相似的就緒通知,直到你在描述符上進行一些操做使得該描述符再也不就緒(例如,直到你收到 EWOULDBLOCK 錯誤爲止)發送,接收或接受呼叫,或小於請求的字節數的發送或接收傳輸).

當你使用就緒改變通知時,你必須準備處理好虛假事件,由於最多見的實現是隻要接收到任何數據包都發出就緒信號,而無論文件描述符是否準備就緒.

這與"水平觸發"就緒通知相反.它對編程錯誤的寬容度要低一些,由於若是你只錯過一個事件,那麼事件的鏈接就會永遠停滯不前.能夠儘管如此,我發現邊緣觸發的就緒通知能讓使用OpenSSL編程非阻塞客戶端變得更容易,所以仍是值得嘗試.

[Banga, Mogul, Drusha '99]在1999年描述了這種類型的模式.

有幾種API使應用程序檢索"文件描述符準備就緒"通知:

  • kqueue() 這是在FreeBSD(很快,NetBSD)系統上推薦使用邊緣觸發的方法.
    FreeBSD 4.3和之後的版本,以及截至2002年10月的NetBSD-current支持 poll() 的通用替代方法kqueue()/ kevent();它同時支持邊緣觸發和水平觸發.(另見Jonathan Lemon的網頁和他的BSDCon 2000 關於kqueue() 的論文.)
    與 /dev/poll 同樣,你能夠分配一個監聽對象,不過不是打開文件/dev/poll,而是調用kqueue() 來得到.須要改變你正在監聽的事件或者要獲取當前事件的列表,能夠在kqueue()返回的描述符上調用kevent().它不只能夠監聽套接字就緒,還能夠監聽純文件就緒,信號,甚至是 I/O 完成.
    注意: 截至2000年10月,FreeBSD 上的線程庫與 kqueue() 沒法很好地交互; 所以,當kqueue() 阻塞時,整個進程都會阻塞,而不只僅是調用kqueue()的線程.
    參閱Poller_kqueue (cc, h,基礎測試)是一個如何使用 kqueue() 與其餘就緒通知模式交互的示例
    使用 kqueue() 的示例和庫:
  • epoll
    這是Linux 2.6 的內核中推薦使用的邊沿觸發poll.
    2001年7月11日,Davide Libenzi 提出了實時信號的替代方案;他將他的補丁稱之爲/dev/epoll www.xmailserver.org/linux-patches/nio-improve.html. 這就像實時的信號就緒通知同樣,同時它能夠合併冗餘事件,而且具備更高效的批量事件檢索的方法.
    當接口從 /dev 中的特殊文件更改成系統調用 sys_epoll 後,Epoll就合併到2.5.46 版本的內核開發樹中. 2.4 內核能夠也使用舊版 epoll 的補丁.
    在2002年萬聖節先後,linux 內核郵件列表就統一epoll,aio和其餘事件源的問題進行了長時間的爭論.它也會會發生,但Davide首先仍是集中精力打造 epoll.
  • Polyakov的 kevent(Linux 2.6+) 新聞報道: 2006年2月9日和2006年7月9日,Evgeniy Polyakov發佈了補丁,彷佛統一了epoll和aio;他的目標是支持網絡AIO.看到:
  • Drepper的新網絡接口(Linux 2.6+提案)
    在OLS 2006上,Ulrich Drepper提出了一種新的高速異步網絡API.看到:
  • 實時信號
    Linux2.4 內核中推薦使用的邊沿觸發poll.
    linux 2.4 內核能夠經過特定的實時信號分配套接字就緒事件.示例以下:
/* 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);複製代碼
  • 當 read() 或 write() 等普通 I/O 函數完成時,發送該信號.要使用該段的話,在循環裏面,當poll()處理完全部的描述符後,進入 sigwaitinfo()sigwaitinfo() 循環.
    若是 sigwaitinfo 或 sigtimedwait 返回你的實時信號,siginfo.si_fd 和 siginfo.si_band 提供的信息幾乎與 pollfd.fd 和 pollfd.revents 在調用 poll() 以後的信息相同,若是你處理該 I/O,那麼就繼續調用sigwaitinfo()
    若是 sigwaitinfo 返回傳統的 SIGIO,那麼信號隊列溢出,你必須經過臨時將信號處理程序更改成SIG_DFL來刷新信號隊列,而後回到外層poll()循環.
    參閱Poller_sigio (cc, h)是一個如何使用 rtsignals 與其餘就緒通知模式交互的示例.
    參閱Zach Brown的phhttpd,例如直接使用此功能的代碼.(仍是不要; phhttpd有點難以弄清楚......)
    [Provos,Lever和Tweedie 2000]描述了 phhttpd 的最新基準,使用的不一樣的sigtimedwait(),sigtimedwait4(),它容許你經過一次調用檢索多個信號.有趣的是 sigtimedwait4() 對他們的主要好處彷佛是容許應用程序來衡量系統過載(所以它能夠行爲恰當).(請注意,poll()也提供了一樣的系統負載測量.)

每一個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 與其餘就緒通知模式交互的示例.

3. 一個線程服務多個客戶端,使用異步 I/O.

這在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.

4. 一個線程服務多個客戶端

...讓 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線程是標準Linux線程庫的名稱.從 glibc2.0 開始,它就集成到 glibc 中,主要是符合 Posix 標準,但性能和信號支持度上都不盡如人意.

NGPT: Linux 的下一代的 Posix 線程

NGPT是 IBM 啓動的爲 Linux 帶來更好的 Posix 線程兼容性的項目.他目前的穩定版本是2.2,工做的很是好...可是 NGPT 團隊宣佈他們將 NGPT 代碼庫置於support-only模式,由於他們以爲這是"長期支持社區的最佳方式". NGPT團隊將會繼續改進 Linux 的線程支持,可是如今主要集中在NPTL.(感謝NGPT團隊的出色工做以及他們以優雅的方式轉向NPTL.)

NPTL: Linux 原生的 Posix 線程庫

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上的默認線程庫.

NetBSD線程支持

根據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中的線程支持發展...從 Solaris 2 到 Solaris 8,默認線程庫使用 M:N 模型,但 Solaris 9 默認爲 1:1 模型線程支持.看Sun的多線程編程指導Sun關於 Java 和 Solaris 線程的筆記

Java線程從JDK 1.3.x及之後開始支持

衆所周知,直到 JDK1.3.x 的 Java 不支持處理除每一個客戶端一個線程以外的任何網絡鏈接方法.Volanomark是一個很好的微基準測試,它能夠在不一樣數量鏈接中測量每秒消息的吞吐量.截至2003年5月,來自不一樣供應商的 JDK 1.3實際上可以處理一萬個同時鏈接 - 儘管性能顯着降低.請參閱表4,瞭解哪些 JVM 能夠處理10000個鏈接,以及隨着鏈接數量的增長性能會受到影響.

注意:1:1 線程與 M:N 線程

在實現線程庫時有一個選擇: 你能夠將全部線程支持放在內核中(這稱爲 1:1 線程模型),或者您能夠將其中的至關一部分移動到用戶空間(這稱爲 M:N 線程模型).有一點,M:N被認爲是更高的性能,但它太複雜了,很難作到正確,大多數人都在遠離它.

5. 將服務器代碼構建到內核中

聽說 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同樣快,但不使用任何內核修改.

Bring the TCP stack into userspace

例如,參見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.comMSDN上的這些網頁,他說這是NT獨有的;簡而言之,win32的"重疊 I/O "結果過低而不方便,"完成端口"是一個提供完成事件隊列的包裝器,加上調試魔術試圖保持運行的數量,若是從該端口獲取完成事件的其餘線程正在休眠(可能阻塞I/O)則容許更多線程獲取完成事件,從而使線程保持不變。

另請參閱OS/400對I/O完成端口的支持

1999年9月對linux-kernel進行了一次有趣的討論"> 15,000個同時鏈接"(和線程的第二週).強調:

  • Ed Hall發佈關於他的經歷的一些筆記; 他在運行Solaris的UP P2/333上實現了>1000次鏈接/秒.他的代碼使用了一小塊線程(每一個CPU1或2個),每一個線程使用"基於事件的模型」管理大量客戶端.
  • Mike Jagdis 發佈了對 poll/select 性能開銷的分析,並說"當前的select/poll 實現能夠獲得顯着改善,特別是在阻塞狀況下,但因爲 select/poll 沒有,所以開銷仍會隨着描述符的數量而增長,而且不能,記住哪些描述符頗有趣的.這很容易用新的API修復.歡迎提出建議......"
  • Mike發佈關於他改進select()和poll()的工做.
  • Mike 發佈了一些可能的API來替換poll()/select(): "你能夠編寫'pollfd like'結構的'device like'API,'device'監聽事件並在你讀它時提供表明它們的'pollfd like'結構?..."
  • Rogier Wolff 建議使用"數字傢伙建議的API",www.cs.rice.edu/~gaurav/pap…
  • Joerg Pommnitz 指出沿着這些線路的任何新API應該不只可以等待文件描述符事件,還可以等待信號和SYSV-IPC.咱們的同步原語至少應該可以作到Win32的WaitForMultipleObjects.
  • Stephen Tweedie斷言,_SETSIG,排隊的實時信號和 sigwaitinfo() 的組合是 www.cs.rice.edu/~gaurav/pap… 中提出的API的超集.他還提到,若是你對性能感興趣,你能夠隨時阻止信號;而不是使用異步傳遞信號,進程使用sigwaitinfo()從隊列中獲取下一個信號.
  • Jayson Nordwick 比較完成端口和F_SETSIG 同步事件模型,得出的結論是它們很是類似.
  • Alan Cox 指出SCT的 SIGIO 補丁的舊版本包含在2.3.18ac中.
  • Jordan Mendelson 發佈一些示例代碼,展現瞭如何使用F_SETSIG.
  • Stephen C. Tweedie 繼續比較完成端口和F_SETSIG,並注意到:"使用信號出隊機制,若是庫使用相同的機制,您的應用程序將獲取發往各類庫組件的信號",但庫能夠設置本身的信號處理程序,因此這不該該影響程序(不少).
  • Doug Royer指出,當他在 Sun 日曆服務器上工做時,他在 Solaris 2.6 上得到了 100,000 個鏈接.其餘人則估計Linux須要多少RAM,以及會遇到什麼瓶頸。

有趣的閱讀!

打開文件句柄的限制

  • 任何Unix: 都由ulimit或setrlimit設置限制
  • Solaris: 看 Solaris FAQ,問題3.46 (或左右; 他們按期從新編號)
  • FreeBSD:
    編輯 /boot/loader.conf, 增長行
    set kern.maxfiles=XXXX
    其中XXXX是文件描述符所需的系統限制,並從新啓動.感謝一位匿名讀者,他寫道,他說他在FreeBSD 4.3上得到了超過10000個鏈接,並說
    "FWIW: 你實際上沒法經過sysctl輕鬆調整FreeBSD中的最大鏈接數....你必須在/boot/loader.conf文件中這樣作.
    這樣作的緣由是 zalloci() 調用初始化套接字和 tcpcb 結構區域在系統啓動時很早就發生了,這樣區域既能夠是類型穩定的又能夠交換。
    您還須要將 mbuf 的數量設置得更高,由於您在(在未修改的內核上)爲 tcptempl 結構每一個鏈接消耗一個mbuf,用於實現 keepalive."

    其餘的讀者說到:
    "從FreeBSD 4.4開始,再也不分配 tcptempl 結構; 你再也不須要擔憂每一個鏈接都會被消耗一個mbuf

    也能夠看看:


  • OpenBSD: 讀者說
    "在OpenBSD,須要額外的調整來增長每一個進程可用的打開文件句柄的數量: /etc/login.conf 的openfiles-cur參數須要被增長. 您可使用sysctl -w 或 sysctl.conf 更改 kern.max 文件,但它不起做用.這很重要,由於對於非特權進程,login.conf限制爲很是低的64,對於特權進程爲128

  • Linux: 參閱Bodo Bauer的 /proc 文檔. 在2.4內核
    echo 32768 > /proc/sys/fs/file-max
    增大系統打開文件的限制.和
    ulimit -n 32768
    ulimit -n 32768
    增大當前進程的限制
    On 2.2.x kernels,
    在 2.2.x 內核,
    echo 32768 > /proc/sys/fs/file-max echo 65536 > /proc/sys/fs/inode-max
    增大系統打開文件的限制.和
    ulimit -n 32768
    ulimit -n 32768
    增大當前進程的限制
    我驗證了 Red Hat 6.0 上的進程(2.2.5 左右加補丁)能夠經過這種方式打開至少31000 個文件描述符.另外一位研究員已經證明,2.2.12 上的進程能夠經過這種方式打開至少90000 個文件描述符(具備適當的限制).上限彷佛是可用的內存。
    Stephen C. Tweedie 發表 關於如何使用 initscript 和 pam_limit 在引導時全局或按用戶設置 ulimit 限制.
    在 2.2 更老的內核,可是,即便進行了上述更改,每一個進程的打開文件數仍限制爲1024
    另見Oskar1998年的帖子,其中討論了 2.0.36 內核中文件描述符的每一個進程和系統範圍限制。

線程限制

在任何體系結構上,您可能須要減小爲每一個線程分配的堆棧空間量,以免耗盡虛擬內存.若是使用pthreads,可使用pthread_attr_init() 在運行時設置它。

Java 問題

經過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的建議:

  • Matt Welsh 的Jaguar 系統提出預序列化對象,新的 Java 字節碼和內存管理更改容許使用 Java 進行異步I/O.
  • C-C. Chang and T. von Eicken寫的將Java鏈接到虛擬接口體系結構提出內存管理更改容許 Java 使用異步 I/O.
  • JSR-51是提出 java.nio 包的Sun工程項目. Matt Welsh參加了(誰說Sun不聽?).

其餘建議

  • 零拷貝
    一般狀況下,數據會從一處到其餘處屢次複製.任何將這些副本消除到裸體物理最小值的方案稱爲"零拷貝".
    • Thomas Ogrisegg 在 Linux 2.4.17-2.4.20 下爲 mmaped 文件發送零拷貝發送補丁.聲稱它比 sendfile() 更快.
    • IO-Lite 是一組 I/O 原語的提議,它擺脫了對許多副本的需求.
    • 在1999年, Alan Cox指出零拷貝有時是不值的會遇到麻煩.(但他確實喜歡sendfile())
  • Ingo於2000年7月在 2.4 內核中爲 TUX 1.0實現了一種零拷貝TCP,他說他很快就會將其提供給用戶空間.
    • Drew Gallatin and Robert 已經爲FreeBSD增長了一些零拷貝特性;想法彷佛是若是你在一個套接字上調用 write() 或者 read(),指針是頁對齊的,而且傳輸的數據量至少是一個頁面, 同時你不會立刻重用緩衝區,內存管理技巧將會用於避免拷貝. 可是請參閱linux-kernel上關於此消息的後續內容,以瞭解人們對這些內存管理技巧速度的疑慮.
      根據Noriyuki Soda的說明:
      自NetBSD-1.6發佈以來,經過指定 "SOSEND_LOAN" 內核選項,支持發送端零拷貝.此選項如今是 NetBSD-current 的默認選項(你能夠經過在 NetBSD_current 上的內核選項中指定"SOSEND_NO_LOAN"來禁用此功能).使用此功能時,若是將超過4096字節的數據指定爲要發送的數據,則會自動啓用零複製.

    • sendfile() 系統調用能夠實現零拷貝網絡.
      Linux和FreeBSD中的sendfile()函數容許您告訴內核發送部分或所有文件. 使操做系統儘量高效地完成。 它能夠在使用非阻塞 I/O 的線程或服務器的服務器中一樣使用.(在 Linux中,目前他的記錄還不多;使用_syscall4 去調用它.Andi Kleen 正在寫覆蓋該內容的 man 頁面.另請參閱Jeff Tranter在Linux Gazette issue 91中探索 sendfile 系統調用.) 有傳言稱 ftp.cdrom.com 受益於 sendfile().
      sendfile() 的零拷貝實現正在爲2.4內核提供.看LWN Jan 25 2001.
      一個開發者在 Freebsd 下使用 sendfile() 的報告顯示使用 POLLWRBAND 而不是 POLLOUT 會產生很大的不一樣.
      Solaris 8 (截至2001年7月更新) 有一個新的系統調用'sendfilev'.手冊頁的副本在這裏. Solaris 8 7/01 發版說明 也提到了它.我懷疑這在以阻塞模式發送到套接字時最有用;使用非阻塞套接字會有點痛苦。
  • 使用writev避免使用小幀(或者 TCP_CORK)
    一個新的在 Linux 下的套接字選項, TCP_CORK,告訴內核去避免發送部分幀,這有點幫助,例如當有不少小的 write() 調用時,因爲某種緣由你不能捆綁在一塊兒.取消設置選項會刷新緩衝區.最好使用writev(),但......
    LWN Jan 25 2001,關於TCP-CORK和可能的替代MSG_MORE的關於linux-kernel的一些很是有趣的討論的摘要.
  • 在過載時表現得智能.
    [Provos, Lever, and Tweedie 2000]提到在服務器過載時丟棄傳入鏈接能夠改善性能曲線的形狀,並下降總體錯誤率.他們使用平滑版本的" I/O 就緒客戶端數"做爲過載的衡量標準.此技術應該很容易應用於使用 select, poll 或任何系統調用編寫的服務器,這些調用返回每次調用的就緒事件技術(例如 /dev/poll 或 sigtimedwait4()).
  • 某些程序能夠從使用非Posix線程中受益.
    並不是全部線程都是相同的.例如,Linux 中的 clone() 函數(及其在其餘操做系統中的朋友)容許您建立具備其本身的當前工做目錄的線程,這在實現ftp服務器時很是有用.有關使用本機線程而不是 pthreads 的示例,請參閱 Hoser FTPd。
  • 緩存本身的數據有時多是一個勝利.
    Vivek Sadananda Pai(vivek@cs.rice.edu)在 new-httpd"回覆: 修復混合服務器問題",5月9日,聲明:
    "我在 FreeBSD 和 Solaris/x86 上比較了基於 select 的服務器和多進程服務器的原始性能.在微基準測試中,軟件架構的性能差別很小.基於 select 的服務器的巨大性能優點源於進行應用程序級緩存.雖然多進程服務器能夠以更高的成本實現,但實際工做負載(與微基準測試相比)更難得到相同的好處.我將把這些測量結果做爲論文的一部分展現,這些論文將出如今下一屆Usenix會議上.若是你有後記,那麼論文能夠在www.cs.rice.edu/~vivek/flas…"

其餘限制

  • 舊系統庫可能使用16位變量來保存文件句柄,這會致使32767句柄之上的麻煩.glibc2.1應該沒問題。
  • 許多系統使用16位變量來保存進程或線程ID.將Volano可伸縮性基準測試 移植到C會頗有意思,看看各類操做系統的線程數上限是多少.
  • 某些操做系統預先分配了過多的線程本地內存;若是每一個線程得到1MB,而且總VM空間爲2GB,則會建立2000個線程的上限.
  • 查看www.acme.com/software/th… 底部的性能對比圖.請注意各類服務器如何在 128個 以上的鏈接上出現問題,甚至在 Solaris 2.6上 知道緣由的人,讓我知道.

注意: 若是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%

測量服務器性能

特別是兩個測試簡單,有趣,並且很難:

  1. 每秒原始鏈接數(每秒能夠提供多少512字節文件?)
  2. 具備許多慢速客戶端的大型文件的總傳輸速率(在性能進入底池以前,有多少 28.8k 調制解調器客戶端能夠同時從服務器下載?)

Jef Poskanzer發佈了比較許多Web服務器的基準測試.看他的結果www.acme.com/software/th…

我也有關於將thttpd與Apache比較的一些舊筆記可能對初學者感興趣.

Chuck Lever不斷提醒咱們關於Banga和Druschel關於Web服務器基準測試的論文.值得一讀。

IBM有一篇名爲Java服務器基準測試的優秀論文.[Baylor 等,2000年].值得一讀。

例子

Nginx 是一個web服務器,它使用目標操做系統上可用的任何高效網絡事件機制.它變得很是流行;這甚至有關於它的兩本書

有趣的基於 select() 的服務器

有趣的基於 /dev/poll 服務器

  • N.Provos,C.Lever,"Scalable Network I/O in Linux"2000年5月.[ FREENIX track,Proc.USENIX 2000,San Diego,California(2000年6月).]描述了被修改成支持 /dev/poll的 thttpd 版.將性能與phhttpd進行比較。

有趣的基於 epoll 服務器

  • ribs2
  • cmogstored - 對大多數網絡使用epoll/kqueue,對磁盤和accept4使用線程

有趣的基於 kqueue() 服務器

有趣的基於實時信號服務器.

  • Chromium 的 X15.使用2.4內核 SIGIO 功能以及 sendfile() 和 TCP_CORK,據報道甚至比TUX實現更高的速度. 在社區許可下的源碼是可用的.看 Fabio Riccardi 原始公告
  • Zach Brown 的 phhttpd - "一個更快的服務服務器, 它用於展現 sigio/siginfo 事件模型.若是你嘗試在生產環境中使用它,請將此代碼視爲高度實驗性的,同時您本身也格外注意" ,使用 2.3.21或以後的 siginfo 特性, 包含了須要的更新內核補丁.據傳甚至比khttpd更快.見他1999年5月31日的一些說明

有趣的基於線程的服務器

有趣的基於內核的服務器

其餘有趣的連接

相關文章
相關標籤/搜索