InfoQ

文章

运用Ruby纤程进行异步I/O:NeverBlock和Revactor

作者 Werner Schuster 译者 李明(nasi) 发布于 2008年10月10日 上午12时11分

社区
Ruby
主题
编程,
性能和可伸缩性,
数据访问,
Ruby on Rails,
运行时
标签
Ruby 1.9,
MySQL,
Rails,
Rubinius,
并发,
数据库,
Ruby on Rails

纤程(Fibers)慢慢地进入了Ruby开发者们的视线中,将作为新的并发原语而广为使用。通过对纤程和非阻塞(或者说异步)I/O这两种方法的组合,可以解决用户空间线程问题或者Ruby 1.9的巨型解释器锁(Giant Interpreter Lock,简称GIL)的问题,这个问题使得Ruby语言线程每次只能有一个是激活的。

最大的问题要数和使用那些阻塞的系统调用了,尤其是I/O方面的。阻塞的系统调用,就拿读取来说,直到数据准备好的时候才会返回。在用户态的线程系统中,这意味着这个进程中的所有线程也都会阻塞。解决方法是将 I/O系统与阻塞I/O的方式进行解耦。有一种方法是在引发一个阻塞的系统调用以前捕捉它,以一种非阻塞的方式来处理这个I/O请求,然后挂起这个纤程,给另外的纤程运行的机会。一旦系统收到这个I/O请求的回应的话,纤程会再度被调度。

纤程提供了一个好用的并发工具来实现这个概念,而不会让使用者去忍受些什么不愉快。目前,两个程序库都着重于使用纤程实现解决方案。比较新的解决方案是NeverBlock程序库(Github代码库包括NeverBlockNeverBlock PG支持ActiveRecord的NeverBlock PG适配器,以及支持异步操作的MySQLPlus MySQL适配器)。更加通用的解决方案是建立在诸如Actor和进程通信等Erlang的思想之上的,来自Tone Arcieri的Revactor程序库。我们对来自NeverBlock项目的Mohammad A. Ali来自Revactor项目的Tone Arcieri进行了访谈。

NeverBlock

InfoQ: 准确点儿说,NeverBlock做了些什么?依我看,它提供了一个连接池把传入代码包装在纤程当中,等到连接可用的时候再恢复纤程。NeverBlock的主要贡献是什么?

Mohammad A. Ali实现池机制对于NeverBlock其他部分的正常工作是必须的。最初的想法是,我们需要为应用程序提供即时的IO并发,例如Rails、Merb或者Ramaze等不支持完全线程安全的应用。

想要达到这个目标的话,我们需要一个web服务器来包装来自纤程的请求和IO程序库,纤程来自于NeverBlock的纤程池,而IO程序库(不仅仅是DB而是全部的IO操作)则是那些与纤程有关并利用其进行暂停和恢复请求的程序库。所有的一切都要使用一个诸如EventMachine或者Rev这样的事件循环来进行搭配。

InfoQ: NeverBlock::PG 是基于NeverBlock构建的,而且PostgreSQL驱动已经支持了非阻塞I/O且立等可用。如果这样的话,再引入NeverblockPG做什么呢?如果想添加另外一个事件驱动的话,比如说Mysql,还没有非阻塞I/O的支持,那么NeverBlock会提供什么帮助呢?

Mohammad A. Ali NeverBlock::PG驱动可以让非阻塞操作透明化,你只需要使从纤程池中spawn一个纤程出来即可。这样的话,你就可以这么写程序:

pool.spawn do
 res1 = db.exec(query1)
 res2 = db.exec(query2_that_depends_on_query1)
end

一个普通的非阻塞实现会比这更加复杂。多亏了纤程,我们可以写出这种看似明明是阻塞的代码,却能够以非阻塞的行为运行。

对于NeverBlock::PG驱动,我们再更深入一点。我们刚刚发布了一个全新的activerecord-neverblock-postgresql适配器,将非阻塞IO带到了 ActiveRecord中。我觉得很容易猜到NeverBlock的下一个目标是什么了。

最有意思的是,当你将NeverBlock应用于全栈应用的时候,它能以一种几乎透明的方式来实现。

NeverBlock不能让一个阻塞的驱动变得非阻塞。它能让一个非阻塞的驱动的操作看起来更像是阻塞的方式,而不会牺牲非阻塞的特性。这既是说,我们未来的努力方向是类似于Asymy的(事件化mysql驱动)。

InfoQ: 你的程序库需要纤程(Fibers)的支持,而纤程只有Ruby 1.9才支持。你认为这对用户来说是一个问题吗(考虑到Ruby 1.9依然变化频繁而且并没有太多人去尝试)?你的程序库的特性会不会对用户去尝试Ruby 1.9起到一个刺激作作用呢?

Mohammad A. Ali 我相信纤程是Ruby 1.9中最棒的事情之一。同样的功能可以通过Ruby 1.8的continuation实现,但是性能上要差得多。另外稳定的1.9目前离发布也为期不远了,我觉得我们真的应该前行了。我相信 NeverBlock、Revactor或者类似的东西提供的优点会帮助大家心甘情愿地转换过去的。

InfoQ: 看了看NeverBlock开放的源代码:你对Fiber使用了开放类并加入了一些方法。这么做的目的是什么?

Mohammad A. Ali 纤程对于存储纤程本地变量方面的功能是缺失的,然而线程可以做到。我需要这些功能来替代事务的当前ActiveRecord实现并使得纤程可查。我还需要它们来做到非阻塞操作的可选性,还有线程的上下文等等。

InfoQ: 你听说过Revactor吗?

Mohammad A. Ali 听过,我还搞过一点Revactor。实际上我用Rev做为NeverBlock除了EventMachine以外的第二后端(仅仅是实验性质的)。但是 NeverBlock和Revactor的目的不同。Revactor为Ruby引入了一个新的并发模型,而NeverBlock目的是以最小的变化把并发引入当前的Ruby程序中。

编辑注:在本次采访完成之时,第一个异步MySQL驱动的尝试──MySQLPlus已经可用了。

Revactor

InfoQ: Revactor的当前状态是什么?

Tony Arcieri: 在无数的项目当中有些被忽视,然而在很多商业应用上已经获得了成功。

InfoQ: Revactor新版本的计划是什么?

Tony Arcieri: 最近Aman Gupta发布了一个“poor man's Fibers”的Ruby 1.8的实现。这样的话将Revactor移植到1.8也是可以的了,然而性能上可能会很差。

目前,Revactor还只是支持将全部的Actors放在同一个Ruby线程中。而更好的做法是可以在不同的线程中运行的Actors之间互相传递消息,但是目前还做不到。我已经和一些有兴趣实现它的人们聊过,希望很快就可以将这种方式引入Revactor。

InfoQ: 用户需要直接对Revactor的actors进行编程吗?还是可以使用RevActor为其他的程序库实现后端,这样可以(对开发者)透明化?

Tony Arcieri: Revactor 和Rubinius中的Actor实现基本上兼容,但是目前Rubinius中的Actors还没有一个简单的方法可以像Erlang的gen_tcp那样进行网络编程。这也就是说,开发者们想使用Actors编写网络应用的话,可以先从Revactor开始,将来再移植到Rubinius上。

InfoQ: 你是如何调度阻塞I/O请求的?你使用内核线程来运行I/O请求吗?

Tony Arcieri: 当所有已发现的Actor消息都处理了、而且没有其他Actors是运行态的时候,Revactor使用一个我编写的、叫做Rev(和 EventMachine类似)的事件程序库来监控I/O事件。Rev使用Ruby 1.9中的rb_thread_blocking_region()函数来做到阻塞监控I/O读取的系统调用,因此不需要再使用独立的内核线程。 Revactor动态地扩展了Ruby的TCPSocket类(Revactor::TCP),这样能做到看起来像阻塞的调用方式,但是实际上则是传回给 Actor调度器。对现存的库进行Moneky Patch(动态打补丁)来做到Revactor::TCP替换Ruby Sockets是很容易做到的。比如说,Revactor就发布了一个小补丁,来实现在Mongrel中使用Actors而不是线程来并发。

InfoQ: 你如何处理I/O请求序列?是批量请求处理吗?

Tony Arcieri: 从表面上看,调用方式是“阻塞”的。当一个I/O请求处理结束后,当前的Actor就会休眠,并在下一次I/O竞争发生的时候被重新调度。这使得那些依赖于阻塞方式接口的程序库可以很方便的构建在Revactor之上。比如说ActiveRecord或者DataMapper之类的(目前还没有运行在 Revactor之上)。

InfoQ: Rubinius上Actors的状态是什么?

Tony Arcieri: 它做了所有Revactor能做的事,并且宣布将支持TCP套接字“active模式”。这意味着在从TCP套接字中读取输入的时候,和以前不同的是,传入的数据是通过标准内部Actor消息异步送达到指定的Actor的。这使得Actor可以并行处理I/O和内部Actor消息。Rubinius虚拟机目前正在使用C++重写,在重写之后希望可以包含实现“active模式”消息传递的全部特性。当它一旦可用的时候,我就会去尝试一下。

InfoQ: 有计划让Revactor支持Rubinius吗?

Tony Arcieri: 没有,Revactor大量的利用到了YARV的特性。Rubinius有一个很棒的并发模型和以Task和Channel的形式的I/O支持,而且Rubinius现有的Actor实现让这两者都非常的有效率。Revactor和Rubinius Actors在duck type方面很大程度上是相似的,所以编写两者都兼容的程序并不是一件很头疼的事情。

InfoQ: 在Rubinius上运行Actors或者Revactor有什么优势呢(超过Ruby 1.9)?

Tony Arcieri: 现在的话还是Ruby 1.9平台更好一些,对于现有的库有着更好的兼容性。Rubinius正在开发当中,而且目前在重写。在将来,Rubinius上会有很多优势能超越 Ruby 1.9,比如用于并发和I/O的Task/Channel抽象就会比Ruby 1.9中现存的更加清晰。在1.8和1.9上用于I/O的解决方案一直都是运行一个诸如EventMachine或者Rev的事件框架,与Ruby内建的 I/O并行工作,而Rubinius在I/O方面一开始就已经占优势了。

InfoQ: 你知道使用Revactor的项目吗?

Tony Arcieri: 我已经听说了很多人在一些内部项目中使用它了,主要是用于并发HTTP客户端。我还不知道任何已经发布的项目用到了它。

InfoQ: 依赖Ruby 1.9的话,会不会对Revactor或者使用纤程的库的推广造成阻碍?

Tony Arcieri: 我当然可以想象的到这种情况。我也被Ruby 1.9的bug折磨过,所以能想象绝大部分的试用者都会小心翼翼的使用的。我已经了解到最近很多利用纤程来实现事件框架的项目冒了出来,比如Ry Dahl的Flow web server。

查看英文原文:Using Ruby Fibers for Async I/O: NeverBlock and Revactor


志愿参与InfoQ中文站内容建设,请邮件至editors@cn.infoq.com。也欢迎大家到InfoQ中文站用户讨论组参与我们的线上讨论。

深度内容

和Google互补的搜索引擎Wolfram|Alpha

Wolfram|Alpha与Google究竟是什么关系,Wolfram|Alpha自己是如何定位的?Wolfram|Alaph在多大程度上是语义网搜索呢?InfoQ中文站就等等这些问题采访了Wolfram研究公司中国区商务经理王翔。

SOA契约成熟度模型

本文说明了所推荐的契约版本管理设计策略是如何与SOA成熟度模型发生联系的。文章目的是为实现版本管理和可组合性提供一个路线图。

数据服务简介

Vijay Narayanan在这篇文章中对数据服务的几个方面进行了介绍,它们都是SOA实践者和数据架构师感兴趣的内容。本文对数据服务的几个方面进行了介绍,包括需求定义,基本原理和好处、范围、开发以及消费模式。

分块云计算

在本文中,Jimmy Nilsson描述了一种他在过去数年间观察到的一种正在缓慢成长的架构风格,他把这种风格称为“分块云计算”。

豆瓣网技术架构变迁

罗马不是一天建成的,豆瓣的技术架构也是随着用户规模的增长一直在持续变化中。在本次演讲中,豆瓣的首席架构师洪强宁将与大家一起分享从上线时的单台服务器架构开始一直到现在的豆瓣架构变迁历程。

融合思想:深入探索S#arp架构

Billy McCafferty展示了S#arp架构,它在ASP.NET MVC框架的基础上,荟萃了当今的最佳实践,应用在ASP.NET Web应用程序的架构设计中。

王雷谈开源以及新兴市场计划

中国作为新兴市场中的新兴市场,是Sun在美国之外实施SSE(SUN Startup Essentials)项目重点关注的地区。在QCon Beijing 2009期间,InfoQ中文站有幸对此项目的负责人王雷先生进行了采访,探讨了关于开源、新兴市场、SSE等话题。

使用HTML5构建下一代的Web Form

HTML5 是由 WHATWG发起的,最开始的名称叫做Web Application 1.0,而后这个标准吸纳了Web Forms 2.0的标准,并一同被W3C组织所采用,合并成为下一代的HTML5标准。