"reactive programming"的概念

下面的内容大可能是翻译来的。node

Reactive Programming?react

What is Reactive Programming?

为了了解Reactive——从编程范式至其背后的动机,有必要了解如今的开发者和公司在十年前未曾面对的挑战。git

游戏的改变主要有两大方面:github

  • 硬件的提高
  • 因特网

Why things are different now?

(原文对比了一遍2005年和2014年因特用户的增加:10亿至29.5亿;Facebook用户的增加:550万至13亿; twitter从无至有,0至2.7亿用户)web

时代改变了,如今一个网站就须要处理十年前整个因特网的流量。实下,容易发现软件对于平常生活愈来愈重要。也容易发现,之前的一些范式并再也不适用于如今,更不适用于未来。编程

The four Reactive principles

Reactive applications 构建于四个指导性的原则。api

 

  • 目标是一个responsive的程序
  • 一个responsive的程序必定既scalable又resilent.responsiveness离开了scalability和resilence是不可能实现的
  • 一个message-driven的架构是scalale, resilent以及最终实现responsive系统的基础

让咱们在高层了解下每个原则,理解为了开发一个适用于如今环境的软件,为何它们是缺一不可的。安全

Responsive

当咱们说一个程序是"responsive"的时候,是在说什么?服务器

A responsive system is quick to react to all users — under blue skies and grey skies—in order to ensure a consistently positive user experience.网络

一个responsive的系统是对全部用户都能快速反应的——无论晴天阴天——来保证一个始终积极的用户体验。

在各类条件下都“快速”而“积级”的用户体验,好比外部系统出问题或者流量激增,依赖于Reactive application的两个特质:resilence,以及scalability。一个消息驱动架构提供了一个responsive系统的基础。

为何消息驱动架构对于responsiveness如此重要?

这个世界是异步的。

 假如你如今来杯咖啡,却发现糖和奶油没了。

一种作法是:

  • 开始煮咖啡
  • 去商店买糖和奶没
  • 回家
  • 啥都齐了,开始喝咖啡
  • 享受生活

另外一种作法是:

  • 去商店买糖和奶油
  • 回家
  • 煮咖啡
  • 等着咖啡煮好
  • 忍受咖啡因短缺
  • 崩溃

就像你看到的,一个消息驱动的架构提供给你异步边界(asynchronous boundary),使你从时间和空间中解放出来。在这个文章的其他部分咱们将继续深刻异步边界的概念。(这段举的例子挺好,但是看不出这个例子跟消息驱动有啥关系呀)

Consistency at Walmart Canada 一致性之于加拿大亚马逊

(译注:这个一致性和分布式系统的一致性不同……)

在加入Typesafe公司以前,我是负责构建亚马逊加拿大的新电商平台的Play和Scala团队的技术leader。

咱们的目标是提供一致的积极的用户体验,无论:

  • 不管什么类型的物理设备被用来访问walmart.ca,不设它是桌面电脑,平板仍是移动设备
  • 当前的流量峰值,无论它是因为激增才出来的,仍是持久的
  • 一个主要的基础设施不可用,好比一整个数据中心。

响应时间和总体的用户体验必须保持一致,无论上面的状况是否发生。一致性对于你的网站是相当重要的,由于当下,这就是你的品牌。一个很差的用户体验是难以被忘记或者忽略的,无论是发生在线上仍是在一个线下的商店。

Responsive retail at Gilt 

Gilt的Responsive零售。电商领域的一致性(的重要性)并非碰巧出现的。好比Gilt也是同样,Gilt是一个闪购网站,天天中午当他们发布了当日的销售商品时流量就会激增。

让我考虑下一个闪购网站的用户体验。若是你在11:58访问了Gilt,12:01又访问了它。那么你但愿两次访问都有积极的体验,无论是否是Gilt在12:01那时候流量突增。

Gilt提供始终积极、responsive的用户体验,而且经过Reactive来实现这一切。经过这篇ReadWrite interview with Eric Bowman of Gilt 了解Gilt向基于Scala的微服务架构(Scala-based microservices architecture)的迁移。

Resilient

大部分的程序都被设计和开发成适用于“晴天”,可是事情可能往坏的方面发展。每隔几天就会有报告说一个主要的应用不可用,或者因为黑客的攻击形成服务不可用、数据丢失、或者储誉受损。

A resilient system applies proper design and architecture principles in order to ensure responsiveness under grey skies as well as blue.

一个弹性的系统采用了一些设计和架构的原则,使得无论外部状况的好坏,都是responsive的

Java和JVM都是用来把一个程序无缝地布署到多个操做系统,相似的是,201x年的内部互联的应用程序都是关于应用程序层级的组合、链接和安全。(注:这句有点绕……忽略,看下边的)

如今的程序大都由必定数量的其它程序组装而成,经过web service和其它网络协议整合在一块儿。如今一个应用可能依赖于大量的其它服务——10,20或者更多,它所依赖的这些服务在本身的信赖防火墙(trust firewall)以外。它可能同时在为大量外部用户提供服务——既包含人,也包括其它系统。

考虑到整合的复杂性,有多少开发人员:

  • 分析和建模全部外在的依赖
  • 把整合进的每一个服务的理想响应时间整理成文档,进行性能测试——在峰值和日常——来全面评估最初的指望
  • 把全部的预期,失败状况和其它非功能的需求编码到系统的核心逻辑中
  • 分析和测试每一个服务的失败情形的影响
  • 分析每一个外部依赖的安全,识别是否整合这个外部依赖是否给系统带来最的安全隐患

Resiliency is one of the weakest links of even the most sophisticated application, 可是Resilency做为一种过后的思考的状况很快就会终结。现代的程序必须在其核心就是resilent的,以保持在现实世界多变的环境下,而不仅是理想状况下,保持responsive。 性能、持久性、安全都是resilency的方面。你的应用必须在各个层次都是resilent的。

 Message-driven resiliency

在一个消息驱动的内核之上构建应用的美妙之处在于你天然地获得了不少有价值的构造模块。

Isolation 隔离性是一个自愈(self-heal)的系统所须要的。当隔离性在被恰当地使用,咱们能够依据一些因素把不一样类型的工做进行分离,好比失效的风险、不一样的性能、CPU和内存的使用,等。一个isolation的组件的失效并不会影响整个系统的responsiveness, 而且给予了失效的那个系统一个被治愈的机会。

Location transparency 位置透明性使得咱们与不一样集群结点上的不一样进程间的通讯就像与同一个VM的进程内通讯同样。

A dedicated separate error channel 一个精心设计的分离的错误信息通道使得咱们能够把错误信号重定向到其它地方,而不是抛回到调用者面前。(注:这个做用应该类型于Akka中的event stream这种东西,使得某些失败的信息能够被订阅,使得不光在当前是调用了某个失败组件的组件能够知道它失败了,而是全部相关的组件均可以了解到这个状况,便于他们便是做出响应)

这些因素帮助咱们把健壮的错误处理和容错合并进咱们的系统中。Akka的supervisor hierarchies的实现展现了这点。

消息驱动架构提供的这些核心的构建模块有助于resiliency,进而有助于responsiveness——不只在理想状况下,并且在各类并不是理想的、现实世界的状况下。

 

The 440 million dollar resiliency mistake

来见识下骑士资本集团在2012年发生软件故障的经历(software glitch experienced by Knight Capital Group)。在一次软件升级中,另外一个休眠的、被整合在一块儿程序被不经意地唤醒,而且开始放大交易量。

接下来的45分钟发生的事情是一场恶梦。

骑士资本的自动交易系统洪水般地向NASDAQ(注:纳斯达克)进行错误交易,使得公司进入了一个须要付出数十亿美圆代价的位置。过后,这家公司花了4.4亿美圆来扭转局势。在这个故障中,骑士资本不能中止泛水般的错误交易,NASDAQ不得不终止骑士资原本中止这一切。骑士资本的股票在一天跌了63%, 几乎不能作为一个公司存活了下来,只能等着它的股票恢复了一些价值以后被一些投资者接管。

骑士资本的系统是高性能的,可是不是resilent的。高性能可是没有resilience使得问题被放大,就像骑士资本发现的那样。他们甚至没有一个中止开关来使得本身的系统在出现严重问题时中止交易, 因此,当真的出了问题时,他们的自动交易系统在45分钟内耗尽了整个公司的资本。

这就是设计和开发只能用于"晴天“的程序的结果。现在,软件是咱们的私人生活和生意的关键组成。若是咱们没有为”阴天“的状况提早设计和准备,那么即便这种状况只存在了不到一个小时,也会给咱们形成巨大的损失。

 Scalable

Resililency和Scalability手拉手,一块儿共建始终responsive的程序。

A scalable system is easily upgraded on demand in order to ensure responsiveness under varioius load conditions.

一个可扩展的系统能够在面对不一样的负载时轻松地进行升级,来保证respnsiveness

任何一个在网上卖过东西的人都明白一个事实:你的流量最大的时候就是你卖的东西最多的时候。除非流量的突增是因为故意的黑客袭击,否则指望流量突增是挺好的一件事。在一个突增的流量中,人们愿意花钱给你。

那么如何面对这种流量突增,或者是一个长久的可是大量的流量增加。

首先选择你的范式(paradigm),而后选择拥抱这种范式的语言和工具。不少状况下,开发者只是简单地选择语言和框架……。一旦选择了工具,就很难改回来. 因此,就像你在处理任何一个重大的投资同样进行这些决定。若是你的技术选择基于一些原则和分析,那么你就已经领先一步了。

 Thread-based limitations to concurrency 基于线程的并发的不足

一个相当重要的技术选择就是框架的并发模型。在较高的层次,有两种并发模型:

  • 传统的基于线程的并发模型,基于调用栈和共享内存
  • 消息驱动的并发

一些流行的MVC框架,好比Rails,是蔡于线程的。这类框架的典型特色包括:

  • 共享的可变状态
  • 每一个请求一个线程
  • 对于可变状态的并发访问——变量以及对象实例——使用锁和其它复杂的同步结构来进行管理

这些特色结合动态类型、解释型语言好比Ruby,会迅速达到性能和扩展性的上限。你能够认为全部本质是脚本语言的语言都是如此。

Out or up?

让咱们考虑扩展一个应用的不一样方式。

向上扩展(scaling up)是关于最大化的利用一个CPU/服务器的资源,一般须要购买更强大的,专用的、更贵的机器。

向外扩展(scaling out)是关于把计算分布到由廉价的commdity机器组成的集群中。这样在成本上更划算,可是若是你的系统是基于时间和空间概念的(注:”基于时间的“能够认为程序的各个动做是同步的,须要阻塞,等待,”基于空间的“能够认为是基于一个进程内部的内存共享,好比不线程间共享的变量),那么向外扩展就很是困难。 就像咱们以前提到的那样,一个消息驱动的架构提供了与时间和空间解耦合所须要的异步边界(asynchronous boundary),提供了按需向外扩展(scale out on demand)的能力,这种能力也被叫作elasticity. Scaling up是关于更高效地利用已有的资源,而elasticity是关于在系统须要改变时按需增长新资源。可以”scale out, on demand"的能力,就是Reactive programming在扩展性方面的终极目标。

Reactive应用程序很难构建于基于线程的框架之上,想一想就知道把一个基于可变状态,线程和锁的的程序进行scale out有多困难。开发人员不只须要利用单个机器上多核的优劣,在某些时候,开发者也须要利用计算机集群的力量。共享可变状态也使得scale up变得困难,即便不是不可能。任何曾尝试操做两个线程共享的可变状态的人都可以理解保证线程安全有多复杂,而且也可以理解为了确保线程安全所须要花费的多余努力所带来的性能上的惩罚。

Message-driven

一个消息驱动的架构是Reactive应用程序的基础。一个消息驱动的程序能够是事件驱动的(event-driven),基于actor的(actor-based),或者二者的结合。

一个事件驱动的系统基于事件,这些事件被一个或更多监听者监听。这和imperative programming不一样,所以调用者不须要阻塞以等待被调用的例程的响应。事件并非被导向特定的地址,而是被监听,咱们将会更深刻地讨论其隐含的意义。

基于actor的并发是消息传递架构的一种延伸,在基于actor的并发架构中,消息能够跨越线程的边界或者被传递到不一样物理机器上的另外一个actor的邮箱(mailbox)中。这使得elasticity——scale out on demand——变得可能,由于actor能够被分布到网络的各处,可是仍然能够像他们在同一个VM里同样通讯。

消息和事件的不一样在于消息是有方向的,而事件只是”发生“并无方向。消息有一个清晰的目的的,而event能够被一个或者多的监听者监听。

让咱们深刻探讨下事件驱动和基于消息的并发。

Evnt-driven concurrency

典型的程序用命令的风格开发—一个命令的线性序列—而且是基于调用栈(call stack)的。调用栈的主要功能是追踪例程(routine,感受应该是函数呀)的调用者,在阻塞调用者的同时执行被调用的例程,当执行完毕后把控制流交还给调用者,而且返回执行结果的值(或者不返回)

事件驱动的程序并不关注于调用栈,而是关注于触发事件。事件能够被编码成消息,放在队列里,被一个或者更多监听者监听。事件驱动和命令式的巨大区别在于调用者不被阻塞,不用在等待响应的时候占据一个线程(caller does not block and hold onto a thread while waiting for a response)。事件循环自己能够是单线程的,可是仍然能够实现并发,由于能够在(有时是单线程 的)事件循环处理新的请求的同时调用例程来完成工做(注:由于对例程的调用被搞成了非阻塞的,即不会阻塞event-loop)。再也不是阻塞于一个请求,等待它被处理完,如今调用者的标识能够和请求消息自己一块儿被传递,使得(若是被调用的例程选择这么作)调用者能够被回调,在回调时传递响应(the caller can be called back with a response)。

选择一个事件驱动架构的主要结果是他们可有会遇到一个现象,叫作回调地狱(callback hell)http://callbackhell.com 。 回调地狱之因此发生,是由于消息的接受者是匿名函数而不是可寻址的接受者(addressable recipients).一般的解决方案仅仅关注于句法—aka, the Pyramid of Doom—而忽略了推导和调试代码中表达的event所产生的后续事件的困难。

Actor-based concurrency

基于actor的程序以在多个actor之间异步的消息传递为中心。

一个Actor由如下的属性组成:

  • 用于接收消息的mailbox
  • actor的逻辑,它依赖于模式匹配(pattern matching)来决定这个actor如何处理不一样类型的消息
  • 隔离的状态(isolated state),而不是共享的状态。使用隔离的状态来存储请求之间的上下文。

像事件驱动的并发同样,基于actor的并发也避开了调用栈,转向轻量级的消息传递。actor能够返回以及发送消息,甚至传递消息给本身。一个消息能够传递给本身消息,使得它先处理队列里的其它消息,而后再去结束一个长时间运行的请求。基于actor的并发的一个巨大好处是,它不只能够有事件驱动的并发的优势,并且可让计算跨越网络边界变得更容易,而且能够避免callback hell,由于消息被定向到actor。这是一个强大的概念,有助于构造易于设计、实施、维护的高可扩展性的程序。不用考虑“空间”和“时间”,也不用考虑深度嵌套的回调函数,你只须要考虑actor之间的消息流动。

另外一个基于actor的架构的主要好处是组件之间解耦合。调用者不用阻塞本身的线程等待响应,所以调用者能够迅速地转去进行其它工做。被调用的例程,封装在一个actor中,只需在必须的回复调用者。这开启了不少可能性,好比分布例程(routines)到一个集群中,由于再也不有调用栈把程序耦合在单一的内存中,actor模型可使布署一个拓扑(topology)只是一个虚拟的配置而再也不是编码在程序里。

Akka是一个基于actor的工具箱和运行时- Typesafe Reactive Platform的一部分,用来构造并发的、分布式的、容错的,基于actor的程序,运行于JVM。Akka还有其它的一些用于构造Reactive application的不可思忆的特性,像是用于rsilience的supervisor hierarchies,以及能够把分布工做来实现扩展性。对Akka的深刻研究超出了本文的范围,可是我强烈推荐访问Let it Crash blog 获取更多关于Akka的内容。

我也强烈推荐阅读Benjamin Erb’s Diploma Thesis,Concurrent Programming for Scalable Web Architectures, 被用于本节的参考。

Conclusion

以上的内容描述了时下软件开发的的表面,引导咱们认识到为何Reactive programming不只仅是一个潮流,并且是如今的软件开发者须要学习的范式。无论你选择什么语言和工具,优先考虑scalability和resilient来实现responsiveness是惟一能知足用户需求的方式。每过一年,这点就会变得更加剧要。

想要手把手的学习吧?下载Typesafe Activator ,在今天开始构建你的Reactive applications. (广告呀……)

 


 

下面是另外一篇文章

  • Reactive

    is readily responsive to a stimulus

    • react to events (event-driven)
    • react to load (scalable)
    • react to failures (resilient)
    • react to users (responsive)

     

    • Event-Driven

      Systems are composed from loosely coupled event handlers.

      • Events can be handled asynchronously, without blocking.
    • Scalable

      An application is scalable if it is able to be expanded according to its
      usage.

      • scale up: make use of parallelism in multi-core systems
      • scale out: make use of multiple server nodes

      Important for scalability: Minimize shared mutable state.
      Important for scale out: Location transparency, resilience.

    • Resilient

      An application is resilient if it can recover quickly from failures.
      Failures can be:

      • software failures
      • hardware failures
      • connection failures

      Typically, resilience cannot be added as an afterthought; it needs to be
      part of the design from the beginning.
      Needed:

      • loose coupling
      • strong encapsulation of state
      • pervasive supervisor hierarchies
    • Responsive

      An application is responsive if it provides rich, real-time interaction withits users even under load and in the presence of failures.Responsive applications can be built on an event-driven, scalable, andresilient architecture. Still need careful attention to algorithms, system design, back-pressure, and many other details.

相关文章
相关标签/搜索