Ruby Ruby Web 服务器:这十五年

laofo · 2017年03月03日 · 最后由 laofo521@gmail.com 回复于 2017年03月03日 · 7 次阅读

Ruby Web 服务器:这十五年

坦率的说,作为一门年轻的计算机语言,Ruby 在最近二十年里的发展并不算慢。但如果与坐拥豪门的明星语言们相比,Ruby 就颇显平民范儿,表现始终不温不火,批评胜于褒奖,下行多过上扬。但总有一些至少曾经自称过 Rubyist 的程序员们,愉快地实践了这门语言,他们没有丝毫的歧视习惯,总是努力尝试各家之长,以语言表达思想,用基准评判高下,一不小心就影响了整个技术发展的进程。本文谨以 Ruby Web 服务器技术的发展为线索,回顾 Ruby 截至目前最为人所知的 Web 领域中,重要性数一数二的服务器技术的发展历程,试图帮助我们了解过去,预见未来。timeline 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

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 来说,仍然只能采用多进程的方式提高一部分并发能力),曾被 Twitter 作为其第一代 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 中间件

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 模型

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 的多进程模型

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 服务器进行总结和对比,并且保持持续更新,推荐开发者关注。

转自:http://insights.thoughtworkers.org/ruby-web-server/

Ruby 服务器对比

之前一直对 Ruby 的服务器不是很了解,在 stackoverflow 看到一篇文章感觉不错,顺便翻译了一下,英语不好,如果哪里错了请大家指出,避免误导他人。 原文地址:http://stackoverflow.com/questions/4113299/ruby-on-rails-server-options

The word "deployment" can have two meanings depending on the context. You are also confusing the roles of Apache/Nginx with the roles of other components.

Historic note: This article was originally written on November 6, 2010, when the Ruby app server ecosystem was limited. I've updated this article on March 15 2013 with all the latest updates in the ecosystem.

Disclaimer: I am one of the authors of Phusion Passenger, one of the app servers.

一,Apache vs Nginx

他们都是 web 服务器,都能伺服静态文件,利用恰当的模块也能伺服动态的 web 应用。Apache 更加流行,拥有更多的功能;Nginx 则相对小巧、快速、功能少。

让 Apache 和 Nginx 来伺服 Ruby 服务器都不是开箱即用(out-of-the-box)的,为此你需要使用另外的插件来组合他们。

Apache 和 Nginx 都能作为反向代理,就是说他们能够把进来的 HTTP 请求发给其他服务器,接着把该服务器的 HTTP 响应转给客户端,后面会看到为什么和这个有关系。

二,Mongrel 以及其他 production 环境的服务器 vs WEBrick

Mongrel 是 Ruby 实现的应用服务器,具体来说:

1,在自己的进程空间中加载 Ruby 应用。

2, 创建一个 TCP socket,允许它可以和外部世界通信 (例如 Internet)。Mongrel 在这个 socket 上监听 HTTP 请求,并把请求数据转发给 Ruby 应用。

3,Ruby 应用返回一个描述 HTTP 响应的对象,Mongrel 将其转换为真正的 HTTP 响应字节,并发回到 socket 中。

然而 Mongrel 已经不再维护了,其他替代的服务器是:

Phusion Passenger Unicorn Thin Puma Trinidad (JRuby only) TorqueBox (JRuby only) 接下来我会讲一讲他们和 Mongrel 的区别

WEBrick 和 Mongrel 很像,区别如下:

WEBrick 不适合用于 production。WEBrick 完全是用 Ruby 写的;Mongrel( 以及其他 Ruby 应用服务器)是部分 Ruby 部分 C,主要是 Ruby,但它的 HTTP 解析器为了性能是用 C 写的。 WEBrick 速度比较慢而且不够强壮,有普遍知道的内存泄漏问题,以及 HTTP 解析问题。 因为 WEBrick 是 Ruby 默认自带的,所以 WEBrick 经常用于 development 环境下作为默认服务器,而其他的服务器则需要另外安装。不建议在 production 环境下使用 WEBrick 服务器,虽然因为某些原因,Heroku 选择了 WEBrick 作为默认服务器,他们以前使用的是 Thin,但我不知道他们为什么换到了 WEBrick。 三,应用服务器世界

当前所有的 Ruby 应用服务器都是 HTTP 类型的,一些服务器直接将 80 端口暴露到 Internet 中,另一些则没有。

暴露 80 端口的:Phusion Passenger, Rainbows。 没有直接暴露的:Mongrel, Unicorn, Thin, Puma。这些服务器必须必须置于反向代理服务器之后,比如 Apache 和 Nginx。 我不了解 Trinidad 和 TorqueBox,所以就忽略了。 为什么有些服务器需要置于反向代理之后?

某些服务器的每个进程在同一时间只能处理一个请求,如果想同时处理两个请求,你就需要启动多个服务器实例,都伺服同一个 Ruby 应用。这种应用服务器进程的集合称为应用服务器集群(比如 Mongrel Cluster, Thin Cluster)。然后你必须启动 Apache 或者 Nginx,给集群做反向代理,Apache/Nginx 会处理好集群中不同应用实例间的请求分发。(更多内容参见章节 "I/O 并发模型”) Web 服务器可以缓存请求和响应。有些客户端发送数据、接收数据的速度缓慢,你当然不希望应用服务器在等待客户端收发数据时什么也不干,Web 服务器可以隔离应用服务器和慢客户端。Apache 和 Nginx 擅长同时很多事情,因为他们是多线程或者基于事件的。 大多数的应用服务器可以伺服静态文件,但不擅长,而 Apache 和 Nginx 可以更快速度处理这件事情。 人们经常直接使用 Apache 或者 Nginx 伺服静态文件,而不会处理需要转发的请求(forward requests),这是比较安全的策略。Apache 和 Nginx 足够聪明,可以保护应用服务器远离恶意请求。 为什么有些服务器可以直接暴露在 Internet 中?

Phusion Passenger 和其他应用服务器不一样,其中一个特点是可以融入其他服务器。 Rainbows 的作者公开指出,Rainbows 可以直接暴露在 Internet 中。他非常确认在解析 HTTP 过程中不会轻易遭受攻击。当然,作者也并不做任何担保,并表示不同应用环境下有其相应的风险。 四,应用服务器对比

在这一章中,我会比较我提过的大多数服务器,但不包括 Phusion Passenger。Phusion Passenger 和其他的不一样,我会单独开出一章。我还会忽略 Trinidad 和 TorqueBox,因为我对他们不是很了解,只有你用到 JRuby 的时候才会涉及到他们。

Mongrel 只有最基础的功能。像之前提到的,Mongrel 仅仅是单线程、多进程,所以它只用于集群(cluster)中。没有进程监控,意味着如果集群中一个进程崩溃了,则需要手动重启。人们需要使用额外的进程来照看 Mongrel,比如 Monit 和 God。
Unicorn 是从 Mongrel 中 fork 出来的。支持监控一定数量的的进程:如果一个进程崩溃了,则会被主进程自动重启。它能让所有进程都监听同一个共享的 socket,而不是每个进程使用单独的 socket,这会简化反向代理的配置。像 Mongrel 一样,Unicorn 是单线程、多进程。 Thin 利用 EventMachine 库,实现基于事件的 I/O 模型。它并不是使用 Mongrel 的 HTTP 解析器,没有基于 Mongrel。它的集群节点没有进程监控,所以你需要去监控进程是否崩溃。每个进程监听各自的 socket,不像 Unicorn 一样共享 socket。理论上来说,Thin 的 I/O 模型允许高并发,这也是 Thin 被应用的大部分场合。一个 Thin 的进程只能处理一个并发请求,所以你还需要集群。关于这个古怪的性质,更多内容参见 “I/O 并发模型”。 Puma 也是从 Mongrel 中 fork 出来的,但和 Unicorn 不一样的是,Puma 是基于多线程,使用 Thread Pool 实现。因为 GIL 的存在,所以 MRI 不能利用多核实现 CPU 并行,所以你需要特别确认的是你能利用多核。更多内容参见 “I/O 并发模型”。 Rainbows 通过给不同的库实现多种并发模型 。 五,Phusion Passenger

Phusion Passenger 和其他的不一样。他直接融入 Apache 或者 Nginx,类似于 Apache 的 mod_php。就像 mod_php 使 Apache 伺服 PHP 应用一样, Phusion Passenger 也可以使 Apache 或者 Nginx 伺服 Ruby 应用。Phusion Passenger 的目标是所有的事情做起来尽可能地减少麻烦。

如果使用 Phusion Passenger 的话,你不需要为你的应用启动一个进程或者集群,为 Apache/Nginx 配置静态目录,或者设置反向代理。

你只需要:

编辑 web 服务器的配置文件,写入 Ruby 应用下 public 文件夹的路径 没有第二步。 所有的配置工作都在 Web 服务器配置文件的指导下做完了,Phusion Passenger 几乎自动化了所有事情,不需要启动集群以及管理进程。启动或者停止进程,他们崩溃时重启,这些都被自动化了。和其他应用服务器相比,Phusion Passenger 所需要做的改动工作非常少,这是人们使用 Phusion Passenger 的主要原因。

和其他应用服务器不同的是,Phusion Passenger 主要是用 C++ 写的,速度很快。

Phusion Passenger 的企业版有更多的特性,比如自动回滚重启,支持多线程,容错部署。(such as automated rolling restarts, multithreading support, deployment error resistance, etc.)

基于以上原因,Phusion Passenger 是当前最流行的 Ruby 应用服务器,服务于超过 50,000 站点,包括大型站点:New York Times, Pixar, Airbnb。

六,Phusion Passenger vs 其他服务器

相对其他的服务器 ,Phusion Passenger 提供了更多的特性功能:

根据访问量自动调整进程的数量。在资源有限的服务器上,我们运行多个 Ruby 应用,而我们的应用不对外公开,而且组织内的访客每天的访问量也很低,例如 Gitlab, Redmine 等。Phusion Passenger 能够在进程不使用的时候挂起他们,需要的时候恢复进程,为更重要的应用腾出资源。相比之下,其他的服务器的所有进程会一直运行着。 有些服务器不适合某些特定的负荷。例如 Unicorn 为轻量快速的请求而设计,See the Unicorn website section "Just Worse in Some Cases". Unicorn 不擅长的负荷:

> 流(e.g. Rails 4 live streaming or Rails 4 template streaming)

> 调用 HTTP API

Phusion Passenger Enterprise 4 及后续版本中多种 I/O 模型,使它非常适合这种负荷。

其他的应用服务器需要用户为每个应用至少启动一个实例,相比之下,Phusion Passenger 支持一个实例多个应用。这大大减少管理员的开支。 Phusion Passenger 支持多个 MRI Ruby, JRuby 和 Rubinius。Mongrel, Unicorn 和 Thin 只支持 MRI,Puma 也支持三个。 Phusion Passenger 不仅支持 Ruby,它还支持 Python WSGI,所以同样能运行 Django 和 Flask 应用。实际上,Phusion Passenger 正在向多语言服务器发展的道路上前进,Node.js 的支持已经列在计划中。 Out-of-band garbage collection。Phusion Passenger 可以在请求的循坏外执行 Ruby 的垃圾回收,可以潜在地减少几百毫秒的请求时间(reducing request times by hundreds of milliseconds)。Unicorn 也有类似的功能,但是 Phusion Passenger 的版本更加灵活,因为 1,it's not limited to GC and can be used for arbitrary work。2,Phusion Passenger 的版本适应多进程应用,而 Unicorn 则不行。 自动回滚重启(Automated rolling restarts)。Unicorn 以及其他应用服务器的回滚重启需要脚本来执行,Phusion Passenger 企业版自动帮你完成了。 还有更多的特性和优势,暂不一一列举。你可以参考全面的 Phusion Passenger 手册 ( Apache version, Nginx version),或者到官网得到更多信息。

七,I/O 并发模型

单线程,多进程。 Ruby 应用服务器中比较常见、流行的 I/O 模型,主要是因为 Ruby 生态系统中的多线程支持比较差。一个进程同时仅且只能处理一个请求,web 服务器通过多进程来进行均衡负载。这种模型比较稳定,开发者不会轻易造成并发 bug。这种模型适合执行快速的短请求,不适合速度慢、长请求阻塞 I/O 的运算,例如 调用 HTTP API。 纯多线程 。现在 Ruby 生态系统已经很支持多线程了,所以这种 I/O 模型变得切实可行。多线程支持高 I/O 并发,既适合短请求也适合长请求。开发者也很容易引入并发 bug,幸运的是大多数框架按照这种方式设计,所以也不太可能发生。有一个需要注意的事情是,因为使用了全局解释器锁(GIL),MRI Ruby 解释器不能均衡使用多个 CPU 内核,即使有多个线程。为此,你可以使用多个进程,每个进程使用一个 CPU 内核。JRuby 和 Rubinius 没有 GIL,所以他们的一个进程可以均衡负载多个 CPU 内核 。 结合多线程、多进程 。Phusion Passenger Enterprise 4 以后版本实现了。你可以轻易在以下模型切换:单进程多线程,纯多线程,多进程多线程。这种模型给出了最好的选择方式。 事件。这种模型和之前提到的模型不一样。它允许极高的 I/O 并发,以及非常适合长请求。为实现此功能,需要从应用到框架的详细支持。然而主要的框架(Rails 和 Sinatra)并不支持事件模型。这也是实际上一个 Thin 进程同时不能处理多个请求的原因,就像单线程多进程模型一样。只有专门的框架才充分利用事件 I/O 模型,例如 Cramp。 八,Capistrano

Capistrano 和其他的不一样。在之前的所有章节中,“部署” 指的是在服务器上启动 Ruby 应用,然后对访客开放,但是在这之前需要一些准备工作,例如:

将 Ruby 应用的代码和文件上传到服务器机器上。 安装应用所依赖的库。 创建或者迁移数据库。 启动或者停止应用依赖的守护进程,例如 Sidekiq/Resque 或者 whatever。 启动应用时需要做的所有其他事情。 在 Capistrano 的语义中,“部署” 意味着去做所有的准备工作。Capistrano 不是一个服务器,反而是一个自动做那些准备工作的工具。你告诉 Capistrano 你的服务器在哪里,以及每当你部署新版本应用的时候需要执行的命令,Capistrano 就能帮你上传 Ruby 应用到服务器上并且运行你指定的命令。

Capistrano 总是与应用服务器组合使用,并不会替代应用服务器。反之亦然,应用服务器也不会替代 Capistrano。

当然 Capistrano 也不是必须使用的。如果你可以使用 FTP 或者 手动上传 Ruby 应用,每一次都执行相同的动作。其他人厌倦这种重复,所以他们在 Capistrano 中自动执行这些步骤。

转自:https://ruby-china.org/topics/25276

Ruby Web 服务器发展时间轴

需要 登录 后方可回复。