Ruby Web服務器:這十五年

Ruby Web服務器發展時間軸
一、隨波逐流
長久以來,任何Web服務器都具備的兩項最重要的功能:一是根據RFC2616解析HTTP/1.1協議,二是接收、處理并響應客戶端的HTTP 請求。幸運的是Web技術的發展并不算太早,使得Ruby恰好能趕上這趟順風車,但在前期也基本上受限于整個業界的進展。像Apache HTTP Server、Lighttpd和Nginx這些通用型Web服務器+合適的Web服務器接口即可完成大部分工作,而當時開發者的重心則是放在接口實現 上。
cgi.rb
作為Web服務器接口的早期標準,CGI程序在調用過程中,通過環境變量(GET)或$stdin(POST)傳遞參數,然后將結果返回 至$stdout,從而完成Web服務器和應用程序之間的通信。cgi.rb是Ruby官方的CGI協議標準庫,發布于2000年的cgi.rb包含 HTTP參數獲取、Cookie/Session管理、以及生成HTML內容等基本功能。
Web服務器和CGI
當支持CGI應用的Web服務器接到HTTP請求時,需要先創建一個CGI應用進程,并傳入相應的參數,當該請求被返回時再銷毀該進程。因此 CGI原生是單一進程/請求的,特別是每次請求時產生的進程創建/銷毀操作消耗了大量系統資源,根本無法滿足較高負載的HTTP請求。此外,CGI進程模 型還限制了數據庫連接池、內存緩存等資源的復用。
對于標準CGI應用存在的單一進程問題,各大廠商分別提出了兼容CGI協議的解決方案,包括網景的NSAPI、微軟的ISAPI和后來的 Apache API(ASAPI)。上述服務器API的特點是既支持在服務器進程內運行CGI程序,也支持在獨立進程中運行CGI程序,但通常需要在服務器進程中嵌入 一個插件以支持該API。
Webrick
作為最古老的Ruby Web服務器而不僅僅是一個接口,誕生于2000年的Webrick從Ruby 1.9.3(2011年10月正式發布)起被正式納入標準庫,成為Ruby的默認Web服務器API。Webrick支持HTTP/HTTPS、代理服務 器、虛擬主機服務器,以及HTTP基礎認證等RFC2617及以外的其它認證算法。同時,一個Webrick服務器還能由多個Webrick服務器或服務 器小程序組合,提供類似虛擬主機或路由等功能:例如處理CGI腳本、ERb頁面、Ruby塊以及目錄服務等。
Webrick曾被用于Rails核心團隊的開發和測試中。但是,Webrick內置的HTTP Parser非常古老,文檔缺失,性能低下且不易維護,功能單一且默認只支持單進程模式(但支持多線程,不過在Rails中默認關閉了對Webrick的 多線程支持),根本無法滿足產品環境中的并發和日常維護需求。目前一般只用于Web應用的本地開發和基準測試。
fcgi.rb
fcgi.rb是FastCGI協議的Ruby封裝(latest版底層依賴libfcgi)。為了與當時的NSAPI競爭,FastCGI協議 最初由Open Market提出和開發、并應用于自家Web服務器,延續了前者采用獨立進程處理請求的做法:即維持一個FastCGI服務器。當Web服務器接收到 HTTP請求時,請求內容和環境信息被通過Socket(本地)或TCP連接(遠程)的方式傳遞至FastCGI服務器進行處理,再通過相反路徑返回響應 信息。分離進程的好處是Web服務器進程和FastCGI進程是永遠保持的,只有相互之間的連接會被斷開,避免了進程管理的開銷。
Web服務器和FastCGI/SCGI服務器
進一步,FastCGI還支持同時響應多個請求。為了盡量減少資源浪費,若干請求可以復用同一個與Web服務器之間的連接,且支持擴展至多個 FastCGI服務器進程。FastCGI降低了Web服務器和應用程序之間的耦合度,進而為解決安全、性能、管理等各方面問題提供新的思路,相比一些嵌 入式方案如mod_perl和mod_php更具靈活性。
由于FastCGI協議的開放性,主流Web服務器產品基本都實現了各自的FastCGI插件,從而導致FastCGI方案被廣泛使用。fcgi.rb最早開發于1998年,底層包含C和Ruby兩種實現方式,早期曾被廣泛應用于Rails應用的產品環境。
mod_ruby
mod_ruby是專門針對Apache HTTP Server的Ruby擴展插件,支持在Web服務器中直接運行Ruby CGI代碼。由于mod_ruby在多個Apache進程中只能共享同一個Ruby解釋器,意味著當同時運行多個Web應用(如Rails)時會發生沖 突,存在安全隱患。因此只在一些簡單部署環境下被采用,實際上并沒有普及。
LiteSpeed API/RubyRunner
LiteSpeed是由LiteSpeed Tech公司最初于2002年發布的商用Web服務器,特點是與被廣泛采用的Apache Web服務器的配置文件兼容,但因為采用了事件驅動架構而具有更好的性能。
LiteSpeed API(LSAPI)是LiteSpeed專有的服務器API,LSAPI具備深度優化的IPC協議以提升通信性能。類似其它Web服務 器,LiteSpeed支持運行CGI、FastCGI、以及后來的Mongrel。同時在LSAPI的基礎上開發了Ruby接口模塊,支持運行基于 Ruby的Web應用。此外,LiteSpeed還提供RubyRunner插件,允許采用第三方Ruby解釋器運行Ruby應用,但綜合性能不如直接基 于LSAPI Ruby。
由于LiteSpeed是收費產品,其普及率并不高,一般會考慮采用LiteSpeed作為Web服務器的業務場景包括虛擬主機/VPS提供商、 以及相關業務的cPanel產品。同時,LiteSpeed也會被用于一些業務需求比較特殊的場合,例如對Web服務器性能要求高,且應用程序及其部署需 要兼容Apache服務器。LiteSpeed于2013年發布了開源的輕量Web服務器——OpenLiteSpeed(GPL v3),移除了商業版本中偏具體業務的功能如cPanel等,更傾向于成為通用Web服務器。
scgi.rb
scgi.rb是對SCGI協議的純Ruby實現。從原理上來看,SCGI和FastCGI類似,二者的性能并無多大差別。但比起后者復雜的協議內容來說,SCGI移除了許多非必要的功能,看起來十分簡潔,且實現復雜度更低。
Web服務器和多FastCGI/SCGI服務器
與FastCGI類似,一個SCGI服務器可以動態創建服務器子進程用于處理更多請求(處理完畢將轉入睡眠),直至達到配置的子進程上限。當獲得 Web服務器請求時,SCGI服務器進程會將其轉發至子進程,并由子進程運行CGI程序處理該請求。此外,SCGI還能自動銷毀退出和崩潰的子進程,具有 良好的穩定性。
二、聞名天下
2005年,David Heinemeier Hansson(DHH)發布了基于Ruby的開發框架Ruby on Rails(Rails),聚光燈第一次聚焦在Ruby身上。但是業內普遍對Web服務器的方案感到棘手,本地環境Webrick/產品環境 FastCGI+通用Web服務器幾乎成了標配,無論是開發、部署或維護都遇到不少困難,一些吃螃蟹的人遂把此視為Rails不如J2EE、PHP方案的 證據。
Mongrel
2006年,Zed Shaw發布了劃時代的Mongrel。Mongrel把自己定位成一個“應用服務器”,因為其不僅可以運行Ruby Web應用,也提供標準的HTTP接口,從而使Mongrel可以被放置在Web代理、Load Balancer等任意類型的轉發器后面,而非像FastCGI、SCGI一樣通過調用腳本實現Web服務器和CGI程序的通信。
Mongrel采用Ragel開發HTTP/1.1協議的Ruby parser,而后者是一個高性能有限自動機編譯器,支持開發協議/數據parser、詞法分析器和用戶輸入驗證,支持編譯成多種主流語言(包括 Ruby)。采用Regel也使parser具有更好的可移植性。但是,Mongrel本身不支持任何應用程序框架,而需要由框架自身提供這種支持。
Mongrel Web服務器
Mongrel支持多線程運行(但對于當時非線程安全的Rails來說,仍然只能采用多進程的方式提高一部分并發能力),曾被推ter作為其第一代Web服務器,還啟發了Ryan Dahl發布于2009年的Node.JS。
但是當Mongrel發布后沒過多久,Shaw就與Rails社區的核心成員不和(實際上Shaw對業界的許多技術和公司都表達過不滿),隨后就 終止了Mongrel的開發。進而在其Parser的基礎上開發了其后續——語言無關的Web服務器Mongrel2(與前續毫無關系)。
盡管Mongrel迅速衰落,卻成功啟發了隨后更多優秀Ruby應用服務器的誕生,例如后文將介紹的Thin、Unicorn和Puma。
Rack
隨著Web服務器接口技術的發展,從開始時作為一個module嵌入Web服務器,到維護獨立的應用服務器進程,越來越多的應用服務器產品開始涌 現,同時相互之間還產生了差異化以便適應不同的應用場景。但是,由于底層協議和API的差別,基于不同的應用服務器開發Web產品時,意味著要實現各自的 通信接口,從而為Web應用開發帶來更多工作量。特別是對于類似Django、Rails這些被廣泛使用的Web框架來說,兼容主流應用服務器幾乎是必須 的。
2003年,Python界權威Phillip J. Eby發表了PEP 0333(Python Web Server Gateway Interface v1.0,即WSGI),提出一種Web服務器和應用程序之間的統一接口,該接口封裝了包括CGI、FastCGI、mod_python等主流方案的 API,使遵循WSGI的Python Web應用能夠直接部署在各類Web服務器上。與Python的發展軌跡相似,Ruby界也遇到了類似的挑戰,并最終在2007年出現了與WSGI類似的 Rack。
與WSGI最初只作為一份建議不同,Rack直接提供了模塊化的框架實現,并由于良好的設計架構迅速統一了Ruby Web服務器和應用程序框架接口。
Rack被設計成一種中間件“框架”,接收到的HTTP請求會被rack放入不同的管線(中間件)進行處理,直到從應用程序獲取響應。這種設計通 過統一接口,把一般Web應用所需的底層依賴,包括Session處理、數據庫操作、請求處理、渲染視圖、路由/調度、以及表單處理等組件以中間件的形式 “放入”rack的中間件管線中,并在HTTP請求/響應發生時依次通過上述管線傳遞至應用程序,從而實現Web應用程序對底層通信依賴的解綁。
Rack中間件
Rack接口部分包含兩類組件:Handler,用于和Web服務器通信;Adapter,用于和應用程序通信。截至Rack 1.6,Rack內置的handlers包括WEBrick、FCGI、CGI、SCGI、LiteSpeed以及Thin,上述handlers用以兼 容已有的常見應用服務器。而2008年后,隨著rack逐漸成為事實標準,更新的Ruby Web服務器幾乎都包含Rack提供的handler。包括Rails、Sinatra、Merb等等幾乎所有主流框架都引入了Rack Adapters的支持。
三、百花齊放
Mongrel和Rack的相繼誕生,使Ruby Web服務器、乃至應用程序框架的發展有了一定意義上可以遵循的標準。Mongrel后相繼派生出Thin、Unicorn和Puma;而Rack統一了 Ruby Web服務器和應用程序框架接口,使應用開發不再需要考慮特定的部署平臺。Ruby Web服務器開始依據特定需求深入發展。
Thin/Goliath
發布于2009年的Thin沿用了Mongrel的Parser,基于Rack和EventMachine開發,前者上文已有介 紹,EventMachine是一個Ruby編寫的、基于Reactor模式的輕量級事件驅動I/O(類似JBoss Netty、Apache MINA、Python Twisted、Node.js、libevent和libev等)和并發庫,使Thin能夠在面對慢客戶端的同時支持高并發請求。
發表自1995年的Reactor模型的基本原理是采用一個單線程事件循環緩存所有系統事件,當事件發生時,以同步方式將該事件發送至處理模塊, 處理完成后返回結果。基于Reactor模型的EventMachine具備異步(非阻塞)I/O的能力,被廣泛用于大部分基于Ruby的事件驅動服務 器、異步客戶端、網絡代理以及監控工具中。
Reactor模型
2011年,社交網絡分析商PostRank開源了其Web服務器Goliath,與Thin相似(都采用了EventMachine)但又有很 大不同,采用新的HTTP Parser,同時針對異步事件編程中的高復雜度回調函數問題,借助Ruby1.9+的纖程技術實現了線性編碼,使程序具備更好的可維護性。 Goliath支持MRI、JRuby和Rubinius等多平臺。在附加功能方面,Goliath的目標不僅是作為Web服務器,更是一個快速構建 WebServices/APIs的開發框架,但是隨著之后PostRank被Google收購,Goliath項目也就不再活躍在開源界了。
Unicorn
2009年,Eric Wong在Mongrel 1.1.5版本的基礎上開發了Unicorn。Unicorn是一個基于Unix/類Unix操作系統的、面向快客戶端、低延遲和高帶寬場景的Rack服 務器,基于上述限制,任何情況下幾乎都需要在Unicorn和客戶端之間設置一個反向代理緩存請求和響應數據,這是Unicorn的設計特點所決定的,但 也使得Unicorn的內部實現相對簡潔、可靠。
盡管來源于Mongrel,但Unicorn只在進程級運行,且吸收和利用了一些Unix/類Unix系統內核的特性,如Prefork模型。
Unicorn由1個master進程和n個fork(2)子進程組成,子進程分別調用select(2)阻塞自己,直到出錯或者超時時,才做一 些寫日志、處理信號以及維護與master的心跳鏈接等內置任務。子進程和master間通過一個共享socket實現通信,而由Unix/類Unix系 統內核自身處理資源調度。
Unicorn的多進程模型
Unicorn的設計理念是“只專注一件事”:多進程阻塞I/O的方式令其無從接受慢客戶端——但前置反向代理能解決這一問題;workers的 負載均衡就直接交給操作系統處理。這種理念大大降低了實現復雜度,從而提高了自身可靠性。此外,類似Nginx的重加載機制,Unicorn也支持零宕機 重新加載配置文件,使其允許在線部署Web應用而不用產生離線成本。
Phusion Passenger(mod_rails/mod_rack)
2008年初,一位叫賴洪禮的Ruby神童發布了mod_rails。盡管Mongrel在當時已經席卷Rails的Web服務器市場,但是面對部署共享主機或是集群的情況時還是缺少統一有效的解決方案,引起業內一些抱怨,包括DHH(也許Shaw就不認為這是個事兒)。
mod_rails最初被設計成一個Apache的module,與FastCGI的原理類似,但設置起來異常簡單——只需要設置一個 RailsBaseURI匹配轉發至Rails服務器的URI串。mod_rails服務器會在啟動時自動加載Web應用程序,然后按需創建子進程,并協 調Web服務器和Rails服務器的通信,從而支持單一服務器同時部署多個應用,還允許按需自動重啟應用服務器。
mod_rails遵循了Rails的設計原則,包括Convention over Configuration、Don’t Repeat Yourself,使其面向部署非常友好,很快得到了業界青睞,并在正式release時改名Passenger。
在隨后的發展中,Passenger逐漸成為獨立的Ruby應用服務器、支持多平臺的Web服務器。截至2015年6月,Phusion Passenger的版本號已經達到5.0.10(Raptor),核心采用C++編寫,同時支持Ruby、Python和Node.js應用。支持 Apache、Nginx和獨立HTTP模式(推薦采用獨立模式),支持Unix/類Unix系統,在統計網站Builtwith上排名Ruby Web服務器使用率第一。
值得一提的是,Phusion Passenger的開源版本支持多進程模式,但是其企業版同樣支持多線程運行。本文撰寫時,Phusion Passenger是最近一個號稱“史上最快”的Ruby Web服務器(本文最后將進一步介紹Raptor)。
Trinidad/TorqueBox
Trinidad發布于2009年,基于JRuby::Rack和Apache Tomcat,使Rails的部署和世界上最流行的Web服務器之一Tomcat結合,支持集成Java代碼、支持多線程的Resque和 Delayed::Job等Worker,也支持除Tomcat以外的其它Servlet容器。
與Trinidad相比,同樣發布于2009年的TorqueBox不僅僅是一個Web服務器,而且被設計成一個可移植的Ruby平臺。基于 JRuby::Rack和WildFly(JBoss AS),支持多線程阻塞I/O,內置對消息、調度、緩存和后臺進程的支持。同時具有集群、負載均衡、高可用等多種附加功能。
Puma
Puma——Mongrel最年輕的后代于2011年發布,作者是Evan Phoenix。
由于Mongrel誕生于前Rack時期,而隨著Rack統一了Web服務器接口,任何基于Rack的應用再與Mongrel配合就有許多不便。 Puma繼承了前者的Parser,并且基于Rack重寫了底層通信部分。更重要的是,Puma部分依賴Ruby的其它兩個流行實現:Rubinius和 JRuby,與TorqueBox類似擁有多線程阻塞I/O的能力(MRI平臺不支持真正意義上的多線程,但Puma依然具有良好并發能力),支持高并 發。同時Puma還包含了一個事件I/O模塊以緩沖HTTP請求,以降低慢客戶端的影響。但是,從獲得更高吞吐量的角度來說,Puma目前仍然需要采用 Rubinius和JRuby這兩個平臺。
Reel
Reel是最初由Tony Arcieri發布于2012年的采用事件I/O的Web服務器。采用了不同于Eventmachine的Celluloid::IO,后者基于 Celluloid——Actor并發模型的Ruby實現庫,解決了EM只能在單一線程中運行事件循環程序的問題,從而同時支持多線程+事件I/O,在非 阻塞I/O和多線程方案間實現了良好的融合。
與其它現代Ruby Web服務器不同的是,Reel并不是基于Rack創建,但通過Reel::Rack提供支持Rack的Adapter。盡管支持Rails,與Puma 也有一定的相似性,但與Unicorn、Puma和Raptor相比,Reel在部署Rails/Rack應用方面缺少易用性。實際上基于 Celluloid本身的普及程度和擅長領域,相比其它Web服務器而言,Reel更適合部署WebSocket/Stream應用。
Yahns
2013年,Eric Wong等人受Kqueue(源自FreeBSD,同時被Node.js作為基礎事件I/O庫)的啟發啟動了Yahns項目。其目標與Reel類似,同樣 是在非阻塞I/O設計中引入多線程。與Reel不同的是,Yahns原生支持Rack/HTTP應用。
Yahns被設計成具有良好的伸縮性和輕量化特性,當系統應用訪問量較低或為零時,Yahns本身的資源消耗也會保持在較低水平。此 外,yahns只支持GNU/Linux(并通過kqueue支持FreeBSD),并聲稱永遠不會支持類似Unicorn或Passenger里的 Watchdog技術,不會因為應用崩潰而自動銷毀和創建進程/線程,因此對應用程序本身的可靠性有一定要求。
四、邁向未來
回顧過去,Ruby Web服務器在發展中先后解決了缺少部署方案、與Web應用程序不兼容、運維管理困難等問題,基礎架構趨于成熟且穩定。而隨著更多基準測試結果的出現,業 界逐漸開始朝著更高性能和并發量發展,同時針對HTTP協議本身的優化和擴展引入的HTTP/2,以及HTML5的WebSocket/Stream等需 求均成為未來Ruby Web服務器發展的方向。
高吞吐量
以最新的Raptor(上文提到的Phusion Passenger 5)為例,其在網絡I/O模型的選擇上融合了現有其它優秀產品的方案,包括Unicorn的多進程模型、內置基于多線程和事件I/O模型的反向代理緩沖 (類似Nginx的功能,但對Raptor自身做了大量裁減和優化)、以及企業版具有的多線程模型(類似Puma和TorqueBox);此 外,Raptor采用的Node.js HTTP Parser(基于Nginx的Parser)的性能超過了Mongrel;更進一步,Raptor甚至實現了Zero-copy和一般在大型游戲中使用 的區域內存管理技術,使其對CPU和內存訪問的優化達到了極致(感興趣的話可以進一步查閱 這里 )。
Raptor的優化模型
另外也需要看到,當引入多線程運行方式,現有Web應用將不得不仔細檢查自身及其依賴,是否是線程安全的,同時這也給構建Ruby Web應用帶來更多新的挑戰。這也是為什么更多人寧愿選擇進程級應用服務器的方式——畢竟對大多數應用來說不需要用到太多橫向擴展,引入反向代理即可解決 慢客戶端的問題,而采用Raptor甚至在獨立模式能工作的更好(這樣就不用花時間去學習Nginx)。
除非你已經開始考慮向支持大規模并發的架構遷移,并希望節省接下來的一大筆花費了。
HTTP/2
2015年5月,HTTP/2隨著RFC7540正式發布。如今各主流服務器/瀏覽器廠商正在逐漸完成從HTTP/2測試模塊到正式版本的過渡。而截至目前,主流Ruby Web服務器都還沒有公開HTTP/2的開發信息。 HTTP-2 是在2013年由Ilya Grigorik發布的純Ruby的HTTP/2協議實現,包括二進制幀的解析與編碼、流傳輸的多路復用和優先級制定、連接和流傳輸的流量控制、報頭壓縮 與服務器推送、連接和流傳輸管理等功能。隨著HTTP/2的發布和普及,主流Ruby Web服務器將不可避免的引入對HTTP/2的支持。
WebSocket/流(Stream)/服務器推送事件(Server Sent Events,SSE)
2011年,RFC6455正式公布了WebSocket協議。WebSocket用于在一個TCP鏈接上實現全雙工通信,其目的是實現客戶端與 服務器之間更高頻次的交互,以完成實時互動業務。鑒于該特點,僅支持慢客戶端的Web服務器就無法有效支撐WebSocket的并發需求,更何況后者對并 發量更加嚴苛的要求了。而對于同樣需要長連接的流服務器和服務器推送事件服務(SSE),都避免不了對長連接和高并發量的需求。盡管高性能的Ruby Web服務器都有足夠的潛力完成這些任務,但是從原生設計的角度來看,更加年輕的Reel和Yahns無疑具有優勢。
最近Planet ruby在Ruby郵件組發布了 Awesome Webservers ,該Github Repo旨在對主流Ruby Web服務器進行總結和對比,并且保持持續更新,推薦開發者關注。