基于Ruby Fiber的异步事件驱动模型在监控告警模块中的应用2012-11《电信网技术》
高伟1,2,王纯1,2,李炜1,2
(1.北京邮电大学网络与交换国家重点实验室 北京 100876,
2.东信北邮信息技术有限公司 北京 100191)
摘要:由于Ruby语言线程模型的限制,通常使用事件驱动方式来实现Ruby应用中的并发。Fiber(纤程)是Ruby语言中的新特性,使用Fiber可以解决事件驱动模型中常见的异步操作、工作流程割裂等问题。本文简单介绍了基于Ruby Fiber实现的异步事件驱动模型在监控告警模块中的应用。
关键词:Ruby、事件队列、Fiber、控制反转、非阻塞IO
Application of Asynchronous Event-Driven Model in Monitoring and Alarm Module Based on the Ruby Fiber
Gao Wei1,2, Wang Chun1,2, Li Wei1,2
(1.State Key Laboratory of Networking and Switching Technology, Beijing University of Posts and Telecommunications, Beijing 100876, China; 2. EB Information Technology Co. Ltd, Beijing 100191, China)
Abstract:Due to the limitations of the threading model of the Ruby language, it's typical to use event-driven approach to achieve the concurrence in Ruby applications. Fiber is a new feature in the Ruby language which we can work with to solve common problems in event-driven model, such as asynchronous operation, work flow fragmented etc. This paper briefly described the application of Asynchronous Event-Driven Model based on the Ruby Fiber in Monitoring and Alarm Module.
Key Words:Ruby , Event Queue, Fiber, Inversion of Control, Non-Blocking IO
1. 引言
Ruby语言是一种为简单快捷面向对象编程而创的脚本语言,于20世纪90年代由日本人松本行弘开发。Ruby on Rails(简称Rails)是一个使用Ruby语言编写的开源Web应用框架,由于其部署容易、开发效率高、功能丰富等特点,迅速普及开来,使用Rails开发的Web项目日渐增多。与此同时,对Web应用的性能、工作状态及异常情况进行监控的需求也在显著增加。使用Ruby语言开发的监控告警模块,既可以作为独立的监控工具使用,也可以轻易集成到Web应用系统之中,具有一定的通用性,适应了以上两种需求。
监控告警功能的开发需要同时跟踪监控许多性能及状态数据,探测异常事件等,因此对并发性要求很高。在Ruby语言中,单独的Ruby进程有自己的解释器实例,多进程通信实现复杂。常用的并发实现有两种:基于多线程或基于事件方式。
本文主要介绍了基于Ruby语言开发的异步事件模型在监控告警模块中的应用,并对其中涉及到的问题进行了讨论。
2. Ruby及Fiber介绍
2.1. Ruby线程模型
Ruby语言为了避免共享非线程安全代码所带来的隐患,在其绿色线程结构中增加了GIL(Global Interpreter Lock)。GIL是一个保护数据完整性的锁机制,每次只允许数据被一个线程修改,避免线程损坏数据,但也不允许真正的并发运行[1]。
在Ruby1.8版本中,一个Ruby Interpreter仅会被映射到一个操作系统级线程,其多线程是建构在用户态之上的轻量模型,并不能充分利用多核的性能。到了Ruby1.9版本,其多线程模型进行了改进,虽然可以映射到多个本地线程,但由于GIL的存在,同一时刻仍然只能执行一个线程。图1显示了两个版本中Ruby 线程模型的变化。

图 1 Ruby线程模型
由于Ruby线程模型的限制,本文中使用事件驱动模型来解决并发问题,实现监控告警功能的核心部分。
2.2. Ruby Fiber介绍
Fiber也叫纤程,是Ruby1.9版本中引入的新特性,也是更广泛的概念Coroutine(协程)在Ruby语言中的实现。纤程提供了比线程更加轻量级的并发(lightweight concurrency)的方式。
纤程与用户态线程类似,但它并不由操作系统(或虚拟机)进行调度,而是自己主动让出CPU控制权(通过调用yield,来允许其他纤程执行)[2]。这是非抢占式调度,通过在纤程之间协同(cooperative)合作来实现操作流程的切换。纤程可以运行在多个线程内,但只有在同一个线程内的多个纤程才可以协同执行。
相对线程而言,Ruby纤程相具有以下优点:
l 避免了传统的函数调用栈,可以多次进入和返回,使得无限递归成为可能
l 创建和销毁更加轻量级、用户态的调度,极大降低上下文切换的开销,使得近乎无限并发的“微线程”成为可能
l 用户态的协同调度,可以避免锁机制带来的麻烦和性能开销
l 避免由于单个线程的阻塞操作而导致整个Ruby进程被挂起
纤程既能将控制权让给彼此,也能通过将控制权交给调度纤程,让其决定下一个调度的纤程,实现集中式调度。由于纤程的这些优点,使用它来实现异步事件队列模型非常合适。
3. 异步事件驱动模型设计
3.1. 事件封装
监控告警模块工作中需要执行多种任务,如采集系统运行数据、对数据进行处理、检测到异常后及时告警、进行善后处理等。这些任务是被相应事件触发的,如定时事件、突发事件、例行事件等[3]。由此可以定义出各种事件及对应的优先级。
定时器事件:如Web页面检测等例行性任务,需要设定各种定时器,定时器到时就会产生相应事件。事件处理优先级低。
异常类事件:当检测到被监控系统出现异常或故障时会产生此类事件。事件优先级中。
实时类事件:如管理员接入监控系统,通过交互命令获得当前被监控系统的状态数据等,产生该类事件。事件优先级高。
事件的关键属性可以抽象为:
TYPE——事件类型;PRIORITY——事件优先级;TASKS——该事件所绑定的任务
3.2. 事件队列设计
对应三种事件优先级,分别设置三个队列保存到达的事件。事件分发者通过检查事件的TYPE字段,为不同事件分配优先级并加入到不同的优先级队列;调度器Scheduler负责选取下一个待处理事件,交给执行器Executor;执行器Executor负责执行事件所绑定的任务。当几个优先级队列中都有事件时,优先级高的事件先处理;相同优先级的事件,按FIFO方式处理。图2展示了事件队列模型的结构。

图 2 事件队列示意图
调度器是一个循环体,与执行器在同一个线程中执行(Fibers无法跨线程调度)。控制在调度器、执行器间交互,调度器是主Fiber,而执行器则是一组子Fibers。调度器选择待处理事件,创建一个子Fiber,在子Fiber中执行事件所绑定的任务。任务执行完成后,子Fiber将控制交回给调度器。图3展示了调度器与执行器简化后的核心代码。

图 3 调度器与执行器的简化代码
使用Fiber比使用线程的效率更高。事件处理会触发任务执行,任务执行过程可以包含多种操作,如数据库查询、文件读写、Socket通信等。如用线程实现,任务执行的工作流程不可暂停和重入,一旦执行阻塞操作,就会导致整个进程被挂起。用纤程实现,则可以减少平均等待时间。
Ruby Fiber可以保存工作现场,实现多次进入和退出。当捕获到阻塞操作时,执行任务的Fiber就创建一个新线程,以异步方式执行该操作。Fiber自己则被挂起(Pending),将控制返回(yield)给调度器。异步操作完成会生成相应事件,当调度到该事件时,执行器就返回到挂起的Fiber,恢复其执行流程。如果要执行多个阻塞操作,可以重复以上动作。
3.3. 控制反转
以上介绍的异步事件模型中,调度器只负责调度事件,执行器只负责执行任务,具体的业务流程则被封装在任务中实现。开发者将对事件的处理逻辑封装在闭包(Closure)中,绑定到事件,由执行器在事件发生后调用[4],这是一种典型的控制反转(Inversion of Control)模式。

图 4 异步操作流程示例
Ruby语言中对Closure的支持非常丰富,具有代表性的是Block(代码块)。处理逻辑被封装为Block,并被绑定到事件中。如果业务逻辑复杂,涉及到多个事件,实现代码可能会被划分到不同blocks里。不同的block会被不同事件触发,在不同上下文环境中执行。这样就会出现问题:业务逻辑被割裂,流程混乱,增加了开发和维护难度[5]。
利用Ruby Fiber,通过对异步操作的封装,可以实现伪同步API,将业务逻辑重新聚合,同时并不损失非阻塞IO的性能。图4是一个简单的示例,代码第一次访问google网站查看其是否可用,如果可用,第二次访问google进行检索。通过http_get方法对异步操作的封装,任务执行代码被集中在一个block中,逻辑清晰易懂。
3.4. 非阻塞IO的实现
在异步事件模型中,如何实现Ruby的非阻塞IO操作至关重要。
Ruby语言自身对非阻塞操作的支持不多,需要额外的扩展来解决针对这个问题,目前已经有了很多实现工具,EventMachine就是典型代表。
EventMachine是一套用于实现快速简洁事件处理的Ruby类库。它使用Reactor模式,提供事件驱动IO。EventMachine的底层类库是C/C++语言实现的,通过底层的非阻塞操作系统调用,为高层的非阻塞实现提供支持。通过Ruby层面的封装,EventMachine对外提供了各种非阻塞API。
基于EventMachine可以构建许多事件驱动型应用。结合EventMachine对非阻塞IO的支持,并充分应用Ruby Fiber的特性,可以非常好的实现异步IO操作。既能充分利用非阻塞IO的性能,又能以看似阻塞的方式进行流程开发,使业务逻辑聚合,开发维护更加高效。
4. 结束语
在监控告警系统中,异步事件驱动模型作为其核心结构,性能要求非常高。虽然ruby语言由于并发结构限制,无法真正利用多核处理器的优势;但仍然可以通过充分开发单个进程的执行效率,尽可能提高事件处理和任务执行速度。
使用Ruby Fiber,结合非阻塞IO的实现,可以有效提高Ruby进程的处理能力,减少阻塞等待时间;同时改善了异步事件驱动模型中常见的工作流程割裂问题,提高了开发和维护效率。
参考文献:
[1] Concurrency is a Myth in Ruby, [online], http://www.igvita.com/2008/11/13/concurrency-is-a-myth-in-ruby/
[2] Knuth, Donald Ervin (1997), Fundamental Algorithms. The Art of Computer Programming. 1 (3rd ed.). Addison-Wesley. Section 1.4.2: Coroutines, pp. 193–200
[3] Li Wan, Jianxin Liao, Xiaomin Zhu, "A frequent pattern based framework for event detection in sensor network stream data", SensorKDD '09 Proceedings of the Third International Workshop on Knowledge Discovery from Sensor Data, 2009, p 87-96
[4] Philipp Haller, Martin Odersky, Event-Based Programming without Inversion of Control. Lecture Notes in Computer Science, 2006, Volume 4228/2006, 4-22
[5] Rob von Behren, Jeremy Condit and Eric Brewer, Why Events Are A Bad Idea(for high-concurrency servers). In:HOTOS'03: Proceedings of the 9th Conference on Hot Topics in Operating Systems. Berkeley: USENIX Association 2003. 24—39