BT

你的观点很重要! 快来参与InfoQ调研吧!

虚拟座谈会:反应式编程

| 作者 Mirko Stocker 关注 0 他的粉丝 ,译者 陈菲 关注 0 他的粉丝 发布于 2014年6月23日. 估计阅读时间: 23 分钟 | ArchSummit社交架构图谱:Facebook、Snapchat、Tumblr等背后的核心技术

亲爱的读者:我们最近添加了一些个人消息定制功能,您只需选择感兴趣的技术主题,即可获取重要资讯的邮件和网页通知

反应式编程是当前非常热门的话题,构建反应型系统的程序库正如雨后春笋般出现在不同平台和语言中。类似Reactive Manifesto(查看InfoQ对Francesco Cesarini和Viktor Klang的采访)这样的倡议正在推进这一想法,另外Reactive Steam(查看InfoQ上的Reactive Streams with Akka Streams)还努力为不同反应式编程库提供互操作性。

但是反应式编程到底是什么样的?它们间各自又是如何实现的?InfoQ将三位来自反应式编程的先驱者聚集到虚拟论坛,认识并详细了解各个项目。

备注:我们很可能忽略了你最喜欢的处理数据流的编程库或其它反应式用例。你可以给我们留言告诉我们其它你想让我们覆盖的编程库(以及一位我们可以联系的优秀的论坛参与者),我们将在虚拟论坛的后续文章中继续讨论。

参与者:

Viktor Klang是Typesafe的一名首席架构师,Akka项目前领导者。

Timothy Baldridge是Cogniteck一名Clojure开发人员,Core.Async贡献者。

Jafar Husain是Netflix一名技术主管,同时也是RxJava的贡献者。

我们要求他们回答了以下问题:

  1. 请简单介绍一下你们的编程库或框架。
  2. 你们是如何实现反应性/并发?其构建于什么基元、概念及语言结构上?开发人员可以控制代码的执行方式及处理背压吗?
  3. 它们都是如何与平台中的其他功能集成的,比如:I/O库、集合(Collections)、可用算法等?
  4. 与语言中的其他方式相比,为什么你们的解决方案更好?其主要功能是否防止了错误,降低了困难,以及能使用新的编程风格?
  5. 你们的编程库是哪类问题的最好解决方案,什么是运用它们的初始动机?还有在什么时候你们会采用其它的方法?
  6. 我们是否需要重新思考编程的方式?也就是说,这些解决方案是否有某些限制?是否可以只用纯函数?还是说需要将所有东西封装起来,亦或必须具有延续性?
  7. 你们的编程语言或平台是否带来了好处,还是说它会使事情更加复杂化或使彻底性变得不可能?在实施上,有哪些特定挑战你们愿意与我们分享?

InfoQ请简单介绍下你们的编程库或框架。

Viktor Klang:Akka是基于JVM(主要针对Java和Scala,但也绑定到JRuby、Clojure及其它更多语言)的反应式应用的程序库和运行环境。Akka采用Actors(查看Actor Mode)作为以下方面的主要结构:并发、分布、快速恢复能力、从小设备到大型服务器、单节点到数千节点集群的不同规模的扩展。

Timothy Baldridge:Core.Async编程库允许在Clojure(JVM上)和ClojureScript(JavaScript虚拟机上)中使用CSP风格编程(Communicating Sequential Processes)。顾名思义,该范式提倡了将代码组织为逻辑线程通信于各信道上。理解CSP很好的方法是将其看成制造流水线,其中处理执行的逻辑线程为工人,工人则用队列所提供的输送带来沟通。

Jafar Husain:Rx提出了一个问题:事件和类似数组或集合的组合间有什么不同?现在大部分开发者处理事件和组合的方式非常不同。Rx为事件和组合带来了统一的编程模式。该编程库通过使用类似匹配、过滤或减少等方式将它们进行转换,从而允许我们在处理事件时采取与组合相同的方式。与其构建状态机来响应事件顺序,我们不如用这些方法从单一事件来创建复杂事件。

InfoQ:你们是如何实现反应性/并发?其构建于什么基元、概念及语言结构上?开发人员可以控制代码执行方式及处理背压吗?

Timothy Baldridge:我们将代码组织成逻辑线程。这些线程与OS线程可能1:1匹配也可能不是,这取决于开发人员。逻辑线程间的通信是通过等待队列,即信道完成。这些信道提供了背压,并支持同一信道中的多个读写操作。由于该模型的简易性,我们根据需要可以很容易地将并行拓展到更高层次:只要往同一信道分配更多的逻辑线程用于读写。

Jafar Husain: RX引进了新的组合类型:Observable。Observable类似于UI元素中的事件,但Observable是类似可以用于传递的列表或集合的第一类对象。

与事件一样,我们可以订阅回调(callback)到Observable,这样每当异步数据到达时,我们都会收到提醒。然而Observable添加了完成(completion)这一概念到事件流。这一简单添加允许开发人员构建复杂的,基于事件的系统,该系统甚至无需退订回调就能完成。

一般情况下,当构建类似用户界面这样长期使用的应用时,我们必须小心地退出事件订阅,以防止内存泄漏。即使它们以后不会再被触发,事件也能控制处理程序(Handler)(比如:网页DOM加载时会触发“document.onload”)。与此相反,Observable流传送完数据后,处理程序会自动退出订阅。

我们可以通过使用Scheduler控制操作应何时发生。Scheduler控制Observable何时及何地通知我们数据已到达。Scheduler可以保证订阅或提醒在不同线程中发生,或一定时间后在同一线程中。

举个例子,我们可以安排两个Observable执行于线程池中,将它们并行起来。在类似JS这样的单线程环境中,Scheduler可以用于观察事件循环中关键时刻的事件流。

关于背压,我们总是可以从Observable中退订以阻止数据流,然后再重新订阅。我们正在探索更加复杂的方式,你将在Rx的后续版本中看到它们。

Viktor Klang:正如前面提到的,我们主要抽象就是Akka Actors(“Actors”),Actor模型的一个JVM实现。Actor之间在逻辑上相互隔离,这意味着多个Actor可以并行运行,它们之间存在叫做ActorSystem – Actor的逻辑层次结构。

Actor的核心是一个行为,该行为适用于其传入的信息,然后我们通过发送信息与Actor沟通,Actor会依次处理这些信息。在处理信息时,该信息可以决定创建新Actor;发送其所知信息到Actor;修改处理下一个信息的行为;或者这些行为的任意组合。不论是Actor的创建还是发送信息到Actor都是异步进行的。这说明Actor本身是事件驱动的。

一个Actor包含以下内容:

  • 创建它的“父类”Actor
  • 一个行为:应用于下一个信息的处理
  • 一个邮箱:当其忙于处理信息时,可用于存储入站信息
  • 一个调度器:用于协调Actor的执行
  • 还有其所创建的零到N个“子类”Actor

一个Actor是一个轻量级构造,典型的大小在450 bytes,意味着它可以在大型硬件系统中同时运行数百个万个实例。

执行

Actor的执行由Akka的调度程序(Dispatcher)完成,它们通常由ExecutorService支持。所有这些,开发人员都可以通过编程方式或外部配置实现。事实上,大部分东西都可以由开发人员配置和定制,比如邮箱实现、ExecutorService和调度程序等。

背压

背压通过Acking/Naking入站信息来实施,这意味着无等待,异步背压。

快速恢复

快速恢复由所谓的监督(Supervision)完成,其中从Actor内部抛出的异常将升级到其父类中,同时失败Actor的执行将被暂停,直到其父类决定好如何处理该故障。可能会出现以下几种结果:

  • 恢复(忽略故障,恢复失败Actor信息处理)
  • 重新启动(丢弃失败Actor的旧实例,然后新建一个,与此同时保持邮箱的完好性)
  • 停止(终止失败的Actor)
  • 升级(重新抛出故障,升级问题到“祖父”类)

这种监督层次(Supervisor Hierarchy)意味着Actor可以创建新的Actor用于执行潜在的危险操作,然后使用监督处理任何故障,从而避免风险。这通常被称为“错误内核模式(The Error Kernel Pattern)”。

有趣的是由于信息驱动的特征结合了故障探测器(Failure Detectors),当监督层次分散在多个节点时,它也能运行。

常见模式是多个Actor组使用了不同调度器和ExecutorService用于隔离它们之间的执行。因此如果有一组Actor执行被破坏,其它的则不会受影响。而这通常被称为“防水层(Bulkheading)”

InfoQ:它们都是如何与平台中的其他功能集成,比如:I/O库、集合(Collection)和可用算法等?

Jafar Husain:Observable执行了大部分集合变换函数,这些函数有Java8中的流类型,C#中的枚举类型以及Javascript中的数组类型。如果你知道如何使用类似匹配,过滤及降低等函数用于转换集合,很容易就可以利用Rx构建复杂异步系统。

Rx提供了帮助方法(helper method)用于转换任何异步接口为Observable。这使得Rx能够简单地逐步集成到其它现有系统中。当然你也可以从小地方入手,比如在系统中某一事件子集中使用组合,然后逐步扩散。

Viktor Klang:Akka带有基于Java 的NIO功能的IO库,该库将IO暴露为简单且熟悉的消息传递:发送和接收数据块。

对于集合,你可以在Actor中任意使用,但当发送带有集合的消息时,我们强烈建议发送不可变的集合。

除此之外,你可以根据需要使用任何JVM库。

Timothy BaldridgeJava和JavaScript虚拟机上的大部分平台库通过回调支持异步。在最底层,Core.Async信道使用了相同模型。因此,很容易就能从宿主虚拟机获取回调将事务放入到信道中,或者将回调连接到信道去调用一些虚拟机方法。

InfoQ:与同语言中的其他方式相比,为什么你们的解决方案更好?其主要功能是否防止了错误,降低了困难,及允许了新的编程风格?

Viktor Klang:Actor是个通用结构,它结合了并发、分布和故障管理,允许从上到下及从内到外的拓展。对于更详细的用例,类似FuturesAgents等其它工具则可能更加适用。

Timothy Baldridge:通常情况下,当应用程序到达到一定规模,开发人员会创建分布式队列将项目分割成小块且更易于管理的部分。该模型同时也为拓展带来了更多选项,开发人员可以根据需要调整队列中读者和写者数量,从而提高性能。

值得一问的是,为什么不在一开始就以这种方式创建我们的系统?为什么不以高度解耦系统开始,将应用程序设计为逻辑线程集沟通于进程队列上。然后,当我们需要拓展应用程序时,我们可以将这些进程队列替换为分布式队列。

以这种方式构建的系统往往更易于调试,因为每个组件在通过信道连接到其他组件前,都可以单独进行测试。

Jafar Husain:Rx学起来非常简单。它充分利用了开发人员的现有知识:如何通过使用类似匹配、过滤和降低等转换函数构建集合。事实上,最难的部分莫过于放弃这一想法:事件在某种层度上与我们每天正在用的集合不同。

Rx还帮助开发人员避免常见的类似内存泄漏这样的陷阱。当构建传统基于事件的系统时,开发人员通常依赖于状态机来决定什么时候从事件中退订。Rx允许我们构建以声明形式指定其结束条件的事件流。一旦事件流结束,它会清除所有未退订订阅。

InfoQ你们的编程库是哪类问题的最好解决方案,什么是运用它们的初始动机?还有在什么时候你们会采用其它的方法?

Timothy Baldridge当应用程序需要与其它系统进行异步交互时,Core.Async可以很容易就成功应用。这些系统可以是外部队列服务、数据库、甚至是HTML DOM。尽管如此,这一模式还是引入了小量的额外成本。因此如果你的唯一目标是并行的话,这个库不见得是个聪明的选择。比如:我就不会使用core.async构建3D光追踪器。所以如果你面临的问题是同步的,且大量并行,应该考虑别的更好的选择。

Jafar Husain:Rx的想法来自Erik Meijer和Brian Beckman对迭代器模式(Iterator pattern)和观察者模式(Observer pattern)之间基本通信的观察。这两个模式都允许数据生成器逐步发送数据到数据消费者,一次一个。不同点在于,迭代器模式中由消费者控制从生成器获取数据,而在观察者模式中则由生成器控制将数据推送给消费者。

Erik和Brian在观察者模式中注意到一个奇怪的漏洞。在迭代器模式中,当数据序列结束时,生成器发送到消费者的结束信号具有良好的定义,而在观察者模式中则完全没有这些语义。比如:DOM事件就没有定义好的方法用于发送信号给消费者,通知它们数据已完全达到。

Erik和Brian意识到这两个无处不在的设计模式可以统一起来,只要往观察者模式中添加“完成”这一概念。其结果就是个新的类型:Observable。这两个定义是对偶的,这意味着,任何可以用于构造Iterable的操作器都可以用于定义Observable。而这更意味着查询事件流很可能和查询数据库一样。

Viktor Klang:Akka由Jonas Bonér创建,目的在于将Erlang中好的概念移入到JVM:尤其是拥有独立的通过信息沟通的进程,和通过Supervision处理故障。

Akka背后的驱动原理之一是所有的基本操作的位置都是透明的,因此如果想结束Actor信息发送或监督它,其位置在哪里并不重要。因此如果你想将应用程序分布于多个机器,不论从更高的处理能力方面,还是快速恢复能力,Akka都是完美的工具。

InfoQ我们是否需要重新思考编程方式?也就是说,这些解决方案是否有某些限制?是否可以只用纯函数?还是说需要将所有东西封装起来,或必须具有延续性?

Timothy Baldridge:值得庆幸的是,一元或持续传递不是必须的。Core.Async中的轻量级线程支持代码重写宏。该库用户只要将他们的代码封装在“go”块中,然后把其它的交给宏就可以了;重写代码到连接信道的回调中。这是该编程库最强大的功能之一,它允许开发人员编写出来的代码更像他们每天编写的命令式代码。

唯一需要多花点心思的地方是涉及IO的内容。该库将逻辑线程按其是否被OS线程支持分为两类。这两类线程适用于不同任务。专用线程通常用于IO,而轻量级线程则推荐用于CPU较密集的任务。

Viktor Klang:我想说在一开始,你会一从开始就考虑沟通,这一流动于Actor之间的通信协议来改变编程方式。

如果想快速且有趣地完成这一过渡,我们可以将其看成一个与人交互的过程。因为我们总是彼此发送信息,可以是邮件、语言、即时通信或其它方式。而不是直接刺激别人的神经(与共享内存并行比起来)。

另外一个有趣的变化是我们可以开始考虑如何将架构分解成单独部分,然后独立运行。

第二个变化是对故障管理的考虑,对具有风险的操作的授权保障了应用程序的关键部分,避免了连锁故障。

由于Actor与其它Actor能够并行,因此不在Actor间共享可变状态变得很重要: Actor应只通过消息进行交互。

但是Actor内部运行的代码,其行为是正常的,类似“call-me-some-methods”这样的代码。

Jafar Husain:如果你已经知道如何使用功能组成转换集合,你就不需要改变你的编程方式。值得指出的是:Observable其实是持续monad的细微改写版本,其副作用将延迟到你退订后。然而,实践中,开发人员无需了解monad就能使用RX。在Netflix,我们培训开发人员使用该技术时,甚至不提及“Monad”一词。

InfoQ你们的编程语言或平台是否带来了好处,还是说它会使事情更加复杂化或使彻底性变得不可能?在实施上,有哪些特定挑战你们愿意与我们分享?

Viktor Klang: Akka Actor使得所创建的应用程序,不论其规模大小,都能根据需要简易地扩展或收缩。比如,我们近期在Google Compute Engine上完成的试验就在单一集群上运行了2500个Akka节点。

Scala,作为Akka的实现语言,对不可变、高效的数据结构有很好的支持。也使得用“案例类”创建不可变信息类型变得更加简单。

而说到挑战,我想到了两点:创建大多数并发协调的无锁版本;对Akka集群进行研究和实施,而这本身就是一漫长有趣的过程。

Jafar Husain: 如果你的编程语言中带有闭包,显然以函数式风格编程会更简单。这可能是Rx导入到Java中最大的障碍了。Ben Christensen负责Netflix贡献的流行开源Rx的Java接口,已经为类似Scala、Clojure和Groovy这样的宿主语言创建了适配器,使得RX能更容易地在JVM上使用。随着Java 8的发布,在Java中使用Rx将变得更加简单,因为它第一次引入了闭包到该语言中。

Timothy Baldridge:Clojure语言所支持的较先进的宏系统允许开发人员根据需要扩展语言。这允许我们在编写整个core.async库时,根本不用接触Clojure编译器。这点的附加好处是导入编程库到ClojureScript只需几个小时,而非几天或几周。

这在我看来,显示了Clojure和core.async的真正力量。能在JVM上为Clojure编写异步代码,然后将其在浏览器中“刚刚好运行”于ClojureScript上,这已经非常强大。

关于专家

Jafar Husain作为软件开发人员已经工作了18年。他曾为通用电气、微软和Netflix等公司开发过软件。目前他是Javascript标准委员会TC39的成员。他擅长通过使用函数型反应式编程构建Web服务器和客户端。他还是“Falkor”协议的架构师,该协议是所有Netflix设备的动力来源。作为具有高度评价的演讲者,Jafar曾在QCon和YOW!上就反应式编程做了相应演讲!还在Channel 9做了相关主题的访谈。他还攥写了互动式培训软件帮助开发人员学习功能型反应式编程。

 

Timothy Baldridge(@timbaldridge)是Cognitect Inc.的一名开发人员。他来自美国科罗拉多州丹佛市的山岭地区。Timothy是个掌握Clojure、C#、Python和Erlang等多种编程语言的开发人员。最近,他深入参与到Clojure的Core.Async库的开发中,其中他参与设计和实现,并维护着该状态机的被称作“go”的代码重写宏。

 

Viktor Klang(@viktorklang)目前是Typesafe首席架构师,也是Akka项目前任技术主管。他在JVM上具有很深的背景,目前他对并发、异步、分布式和弹性编程特别感兴趣。

 

查看英文原文:Virtual Panel on Reactive Programming

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

应该翻译成响应式编程 by Yuan Seth

RT.

吐槽一下翻译。。。 by keegan xiang

"该编程库通过使用类似匹配、过滤或减少等方式将它们进行转换"

map, filter, reduce 这种名词还要进行一次翻译吗??不看原文真的不知道在说啥。。。

Re: 吐槽一下翻译。。。 by keegan xiang

第二个地方又给翻译成了这些:

”该编程库通过使用类似匹配、过滤或减少等方式将它们进行转换“

Re: 吐槽一下翻译。。。 by bing paul

谢谢你的评论,这里的确翻译的很差,以后会努力改进

Re: 应该翻译成响应式编程 by 孙 奇辉

Responsive才是响应式,Reactive翻译为反应式很正确! 这是在物理化学里面早就有的科技词汇React(词根)。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

5 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT