关于反应式编程的注释第一部分:反应式环境
响应式编程很有趣(再次),目前有很多杂音,对于局外人和简单的企业Java开发人员(例如作者)而言,并不是所有的内容都很容易理解。本文(系列文章的第一篇)可能有助于阐明您对这些大惊小怪的理解。该方法尽可能具体,没有提及“符号语义”。如果您正在寻找一种更具学术性的方法和Haskell中的大量代码示例,则互联网上到处都是,但是您可能不想在这里。
响应式编程通常与并发编程和高性能混为一谈,以至于很难将那些概念分开,而实际上它们在原理上是完全不同的。这不可避免地导致混乱。反应式编程也经常被称为功能性反应式编程或FRP或与功能性反应式编程(FRP)结合使用(在这里我们将二者互换使用)。有些人认为Reactive并不是什么新鲜事物,而是整天都在做的事情(大多数情况下使用JavaScript)。其他人似乎认为这是微软的礼物(当他们不久前发布一些C#扩展时,微软对此给予了极大的关注)。在企业Java领域中,最近出现了一些嗡嗡声(例如,请参见Reactive Streams计划 ),并且与任何闪亮的事物和新事物一样,在何时何地以及应该何时何地存在许多容易犯的错误。使用。
它是什么?
响应式编程是一种微体系结构,涉及智能路由和事件消耗,所有这些都结合在一起以改变行为。这有点抽象,您在网上也会遇到许多其他定义。我们尝试建立一些更具体的概念,以表示反应是什么意思,或者为什么它在接下来的事情中可能很重要。
响应式编程的起源可能可以追溯到1970年代甚至更早,所以这个想法没有什么新意,但是它们确实在现代企业中引起了共鸣。在微服务兴起和多核处理器无处不在的同时,这种共鸣已经(并非偶然)到达。希望其中的一些原因将变得清楚。
以下是来自其他来源的一些有用的封装定义:
The basic idea behind reactive programming is that there are certain datatypes that represent a value "over time". Computations that involve these changing-over-time values will themselves have values that change over time.
还有...
An easy way of reaching a first intuition about what it's like is to imagine your program is a spreadsheet and all of your variables are cells. If any of the cells in a spreadsheet change, any cells that refer to that cell change as well. It's just the same with FRP. Now imagine that some of the cells change on their own (or rather, are taken from the outside world): in a GUI situation, the position of the mouse would be a good example.
(来自Stackoverflow的术语问题 )
FRP与高性能,并发性,异步操作和无阻塞IO具有很强的亲和力。但是,从怀疑FRP与它们中的任何一个都没有关系开始可能会有所帮助。当然,在使用响应式模型时,这种顾虑可以自然地处理,通常对调用者透明。但是,就有效或有效处理这些问题而言,实际收益完全取决于所讨论的实现(因此应受到高度审查)。也有可能以同步,单线程的方式实现一个完美而又有用的FRP框架,但这在尝试使用任何新工具和库中实际上并没有帮助。
反应性用例
作为新手,最难回答的问题似乎是“这有什么用?“以下是来自企业环境的一些示例,这些示例说明了一般的使用模式:
外部服务调用如今,许多后端服务都是基于REST的(即,它们通过HTTP运行),因此底层协议从根本上是阻塞和同步的。FRP的领域可能并不明显,但实际上这是一块肥沃的土地,因为此类服务的实施通常涉及调用其他服务,然后再根据首次调用的结果调用更多服务。如果要在发送下一个请求之前等待一个呼叫完成,那么会有大量的IO进行,那么可怜的客户端会在您设法收集答复之前感到沮丧。因此,优化外部服务调用,尤其是调用之间的依赖关系的复杂编排,是一件好事。FRP保证了驱动这些操作的逻辑的“可组合性”,因此对于调用服务的开发人员来说更容易编写。
高度并发的消息使用者消息处理,尤其是高度并发的消息处理,是企业常见的用例。响应式框架喜欢测量微基准,并吹嘘您可以在JVM中每秒处理多少消息。结果确实是惊人的(每秒很容易获得数千万条消息),但可能有些人为-如果他们说他们正在对一个简单的“ for”循环进行基准测试,您将不会感到印象深刻。但是,我们不应该过快地注销此类工作,并且很容易看出,当绩效很重要时,所有贡献都应被感激地接受。反应模式自然适合消息处理(因为事件可以很好地转换为消息),因此,如果有一种方法可以更快地处理更多消息,则应引起注意。
电子表格也许不是真正的企业用例,但企业中的每个人都可以轻松地联系到一个用例,它很好地体现了FRP的理念和实施难度。如果单元格B依赖于单元格A,并且单元格C同时依赖于单元格A和B,那么您如何传播A中的更改,确保在将任何更改事件发送到B之前更新C?如果您有一个真正的可响应框架可以构建,那么答案是“您不在乎,您只需声明依赖项”,这简而言之就是电子表格的强大功能。它还强调了FRP和简单的事件驱动编程之间的区别-将“智能”置于“智能路由”中。
(A)同步处理上的抽象这更多是抽象用例,因此我们可能应该避免进入领域。在此和已经提到的更具体的用例之间也存在(很多)重叠,但是希望仍然值得一些讨论。基本主张是一个熟悉的(并且是合理的)主张,只要开发人员愿意接受额外的抽象层,他们就可以忘记正在调用的代码是同步的还是异步的。由于处理异步编程需要花费宝贵的脑细胞,因此那里可能会有一些有用的想法。响应式编程不是解决此问题的唯一方法,但是FRP的一些实施者已经对此问题进行了充分的思考,以使其工具有用。
该Netflix博客提供了一些真实有用的实际用例示例: Netflix Tech博客:带有RxJava的Netflix API中的功能性响应
比较
如果您从1970年以来一直不在洞穴里生活,您将遇到其他一些与响应式编程以及人们尝试使用它解决的问题有关的概念。以下是其中一些与我个人相关的观点:
Ruby Event-Machine Event Machine是并发编程(通常涉及非阻塞IO)的抽象。Rubyists努力了很长时间才能将一种专为单线程脚本编写的语言转换为可用于编写服务器应用程序的语言,该服务器应用程序a)工作良好,b)性能良好,并且c)在负载下仍然有效。Ruby拥有线程已有很长一段时间了,但是它们使用不多且声誉不佳,因为它们并不总是性能很好。Fibers (sic)是一种替代语言,现在已经被广泛使用(在Ruby 1.9中)成为该语言的核心。Fiber编程模型有点像协程(请参见下文),其中一个本地线程用于处理大量并发请求(通常涉及IO)。编程模型本身有点抽象并且难以推理,因此大多数人都使用包装器,而事件机是最常见的。Event Machine不一定要使用Fibers(它会抽象这些问题),但是很容易在Ruby Web应用程序中使用带有Fibers的Event Machine来查找代码示例(例如, 请参见Ilya Grigorik的本文或em-http中的纤维化示例-request )。人们经常这样做,以获得在I / O密集型应用程序中使用事件机器所带来的可伸缩性,而无需使用大量嵌套回调获得的丑陋编程模型。
Actor模型类似于面向对象的编程,Actor模型是可追溯到1970年代的计算机科学的深层线程。参与者提供了对计算的抽象(与数据和行为相反),从而自然而然地允许了并发,因此从实际意义上讲,它们可以构成并发系统的基础。Actor互相发送消息,因此它们在某种意义上是反应性的,并且在将自己称为Actor或Reactive的系统之间存在很多重叠。通常,区别在于它们的实现级别(例如Actors
可以在Akka中跨进程分布,这是该框架的显着特征)。
延迟结果(未来) Java 1.5引入了一组丰富的新库,其中包括Doug Lea的“ java.util.concurrent”,其中一部分是延迟结果的概念,封装在一个Future
。这是通过异步模式进行简单抽象的一个很好的示例,而无需强制实现异步或使用任何特定的异步处理模型。正如Netflix的Tech Blog:使用RxJava的Netflix API中的Functional Reactive很好地展示了, Futures
当您需要并发处理一组相似的任务时,这是个很好的选择,但是一旦它们中的任何一个想要相互依赖或有条件地执行,您就会陷入“嵌套回调地狱”的形式。响应式编程对此提供了解决方法。
并行处理中的Map-reduce和fork-join抽象很有用,并且有许多示例可供选择。在Java世界中最近发展起来的Map-reduce和fork-join由大规模并行分布式处理( MapReduce和Hadoop )以及1.7版的JDK本身( Fork-Join )驱动。这些是有用的抽象,但是(如延迟的结果)与FRP相比,它们是浅薄的,FRP可以用作简单并行处理的抽象,但超出了可组合性和声明性通信。
协同程序一个“协同程序”是一个“子程序”的概括-它有一个进入点和退出点(S)与子例程类似,但它退出时,将控制权交给另一个协程(不一定是调用者),和任何累积的状态会在下次调用时保留并记住。协程可以用作Actor和Streams等更高级别功能的构建块。响应式编程的目标之一是通过通信并行处理代理提供相同类型的抽象,因此协程(如果可用)是有用的构建块。协程有多种口味,其中一些比一般情况下限制性更强,但比香草子程序更具灵活性。纤维(请参阅有关事件计算机的讨论)是一种味道,而生成器(熟悉Scala和Python)是另一种味道。
Java的反应式编程
Java不是本地支持协程的,因此它不是“反应性语言”。JVM上还有其他语言(Scala和Clojure)更本地地支持响应模型,但是Java本身直到版本9才支持。但是,Java是企业开发的强大力量,最近在JDK之上提供Reactive层已开展了很多活动。我们在这里仅对其中的几个进行简要介绍。
反应性流是一种非常低级的协定,表示为少数Java接口(加上TCK),但也适用于其他语言。这些接口表达了Publisher
和Subscriber
具有明显的背压,形成了可互操作的库的通用语言。反应性流已作为java.util.concurrent.Flow
在版本9中。该项目是Kaazing,Netflix,Pivotal,Red Hat,Twitter,Typesafe和许多其他公司的工程师之间的协作。
RxJava :Netflix在内部使用了反应模式一段时间,然后他们在开放源代码许可下以Netflix / RxJava (后来更名为“ ReactiveX / RxJava”)发布了他们正在使用的工具。Netflix在RxJava之上的Groovy中进行了大量编程,但是它对Java的使用是开放的,并且通过使用Lambdas非常适合Java 8。有通往反应式流的桥梁 。根据David Karnok的Generations of Reactive分类,RxJava是“第二代”库。
Reactor是Pivotal开源团队(创建Spring的团队)的Java框架。它直接建立在响应流上,因此不需要桥接。Reactor IO项目为Netty和Aeron等低层网络运行时提供了包装。根据David Karnok的反应世代分类,反应堆是“第四代”库。
Spring Framework 5.0 (2016年6月的第一个里程碑)具有内置的响应功能,包括用于构建HTTP服务器和客户端的工具。Web层中Spring的现有用户将找到一个非常熟悉的编程模型,该模型使用注释来装饰控制器方法以处理HTTP请求,大部分情况下将响应请求的分发和对框架的反压力问题移交给了框架。Spring在Reactor的基础上构建,但也公开了API,这些API允许使用选择的库(例如Reactor或RxJava)来表达其功能。用户可以从Tomcat,Jetty,Netty(通过Reactor IO)和Undertow中选择服务器端网络堆栈。
Ratpack是一组用于通过HTTP构建高性能服务的库。它建立在Netty的基础上,并实现了Reactive Streams以实现互操作性(例如,您可以使用更高级别的其他Reactive Streams实现)。Spring作为本机组件受支持,并且可以使用一些简单的实用程序类来提供依赖注入。还有一些自动配置,以便Spring Boot用户可以将Ratpack嵌入Spring应用程序中,调出HTTP端点并在其中监听,而不用使用Spring Boot直接提供的嵌入式服务器之一。
为什么现在?
是什么推动了Reactive在Enterprise Java中的兴起?嗯,这不只是(全部)技术时尚-人们带着闪亮的新玩具跳入潮流。驱动程序是有效的资源利用,换句话说,是在服务器和数据中心上花费更少的钱。Reactive的承诺是您可以事半功倍,特别是可以用更少的线程处理更高的负载。这是反应式和非阻塞式异步I / O的交叉点。对于正确的问题,其影响是巨大的。对于错误的问题,效果可能会相反(实际上会使情况更糟)。还要记住,即使您选择了正确的问题,也没有免费的午餐之类的东西,而且Reactive不会为您解决问题,它只是为您提供了一个可用于实施解决方案的工具箱。
结论
在本文中,我们对响应式运动进行了非常广泛和高级别的研究,将其设置在现代企业的环境中。有许多用于JVM的Reactive库或框架,所有这些都在积极开发中。它们在很大程度上提供了相似的功能,但是越来越多地归功于响应流,它们可以互操作。在该系列的下一篇文章中,我们将深入探讨一些实际的代码示例,以更好地了解“响应式”的含义及其重要性的细节。我们还将花一些时间来理解FRP中的“ F”为何如此重要,以及反压和非阻塞代码的概念如何对编程风格产生深远的影响。最重要的是,我们将帮助您做出重要的决定,决定何时以及如何进行Reactive,以及何时保持使用较旧的样式和堆栈。