响应式编程备忘:响应式编程概览 <一>

原文:https://spring.io/blog/2016/06/07/notes-on-reactive-programming-part-i-the-reactive-landscape#reactive-use-cases
ENGINEERING   DAVE SYER   JUNE 07, 2016

响应式编程很有趣,现在有很多关于它说法,这些说法对于像作者一样的门外汉和初级企业Java开发人员而言并不是很容易理解。 这篇文章(系列文章中的第一篇)可能有助于理解它们。 本文章会尽可能具体,没有提到“外延语义”。 如果你想寻找更学术的方法或大量的 haskell 代码示例,那么互联网上就到处都是,但此文章没有这些。 响应式编程往往出现在并发编程和高性能,以至于这些概念虽然理论上完全不同,但很难将它们分开。 这必然令人困惑。 响应式编程也经常与函数式性编程(FRP 本文交替使用这两个) 混在一块。 有些人认为响应式编程式不是什么新鲜事,他们日常都在使用(他们大多使用JavaScript)。 其他人似乎认为这是来自微软的一份礼物(不久前他们发布了一些C# 扩展版,引起了轰动)。 在企业Java空间中最近出现了一些议论 (例如: 响应流),和任何新鲜事物一样,何时何地适合使用有很多容易犯的错误。

它是什么

响应式编程是一种微体系结构,涉及智能路由和事件消费,所有这些都结合在一起以改变行为。 这有点抽象,你会在网上看到很多其他的定义。 我们试图建立一些更具体的概念,来说明什么是响应式的,或者为什么它在以后可能很重要。 响应式编程的起源可能可以追溯到70年代甚至更早的时候,所以这个想法没有什么新鲜的,但是它们确实与现代企业中的某些东西产生了共鸣。 随着微服务的兴起和多核处理器的普及,这种共振(并非偶然)同时到来。 其中的一些原因可能会变得很清楚。 以下是其他来源一些有用定义: 响应式编程的基本思想是 表示与“时间相关”的某种数据类型的值。 计算出 包括这些随时间变化值本身也是随时间变化的值

. 了解关于它是什么样子的第一直觉,一个简单的方法是 想象你的程序是一个电子表格,所有变量都是 单元格。 如果电子表格中的任何单元格发生更改,则 该单元格相关的也改变了。 这与函数式编程一致。 现在 imagine that some of the cells change on their own (or rather, are 外部世界改变了):在 GUI 情况下,鼠标位置可能是个好的例子。

(来自StackOverflow的术语问题) 函数式编程与高性能、并发性、异步操作和无阻塞IO很相关。 However, it might be helpful to start with a suspicion that FRP has nothing to do with any of them. 当然,在使用反应模型时,可以自然地处理这些问题,通常对调用者是透明的。 But the actual benefit, in terms of handling those concerns effectively or efficiently is entirely up to the implementation in question (and therefore should be subject to a high degree of scrutiny). 以同步、单线程的方式实现一个完全健全有用的FRP框架是可能的,但这在尝试使用任何新的工具和库时都不太可能有帮助。

响应式示例

作为一个新手,最难得到答案的问题似乎是“它对什么有好处?” 以下是企业设置中的一些示例,说明了一般的使用模式:

外部服务调用

现在许多后端服务都是 REST-ful (即 它们基于HTTP)因此底层协议基本上是阻塞和同步的。 对于FRP来说,这可能不是一个明显的领域,但实际上它是一个有潜力的领域,因为此类服务的实现通常涉及到调用其他服务,然后根据第一次调用的结果调用更多的服务。 一个请求需要等待上一个请求的结果,如果有多个依赖请求,可怜的客户可能会在你组装好响应前因此放弃。 因此,外部服务调用,特别是调用之间依赖关系复杂的业务流程,是一个需要优化的好东西。 FRP提供了驱动这些操作的逻辑的“可组合性”承诺,这样调用服务的开发人员编写代码就更容易。

高并发消息消费

消息处理,特别是当它高度并发时,是一个常见的企业用例。 响应式框架喜欢测量微基准,并吹嘘您每秒可以在JVM中处理多少消息。 结果确实令人震惊(每秒数千万条消息很容易实现),但可能有些人为的——如果他们说他们是简单的“for”循环的基准测试,你不会那么惊讶。 However, we should not be too quick to write off such work, and it’s easy to see that when performance matters, all contributions should be gratefully accepted. 反应模式很自然地适合消息处理(因为事件很好地转换为消息),所以如果有方法更快地处理更多的消息,我们应该注意。

电子表格

也许不是一个真正的企业用例,而是企业中每个人都可以很容易地联系到的用例,它很好地抓住了FRP的原理和实现的困难。 如果单元格B依赖于单元格A,而单元格C同时依赖于单元格A和B,那么如何在A中传播更改,确保在将任何更改事件发送到B之前更新C?如果你有一个真正的反应式框架来构建,那么答案是“你不在乎,你只是声明依赖”,简而言之,这就是电子表格的强大功能。 它还强调了FRP与简单事件驱动编程的区别-,它将“智能”置于“智能路由”中。

同步/异步处理抽象

这更像是一个抽象的用例,因此我们可能应该避免误入这个领域。 这与已经提到的更具体的用例之间也有一些(很多)重叠,但希望它仍然值得讨论。 The basic claim is a familiar (and justifiable) one, that as long as developers are willing to accept an extra layer of abstraction, they can forget about whether the code they are calling is synchronous or asynchronous. Since it costs precious brain cells to deal with asynchronous programming, there could be some useful ideas there. 反应式编程并不是解决这一问题的唯一方法,但是一些FRP的实现者已经足够认真地思考了这个问题,他们的工具是有用的。 这个Netflix博客有一些非常有用的实际用例的具体例子:Netflix技术博客: Functional Reactive in the Netflix API with RxJava

比较

If you haven’t been living in a cave since 1970 you will have come across some other concepts that are relevant to Reactive Programming and the kinds of problems people try and solve with it. Here are a few of them with my personal take on their relevance:

Ruby事件机

The Event Machine is an abstraction over concurrent programming (usually involving non-blocking IO). Rubyists struggled for a long time to turn a language that was designed for single-threaded scripting into something that you could use to write a server application that a) worked, b) performed well, and c) stayed alive under load. Ruby has had threads for quite some time, but they aren’t used much and have a bad reputation because they don’t always perform very well. The alternative, which is ubiquitous now that it has been promoted (in Ruby 1.9) to the core of the language, is Fibers(sic). The Fiber programming model is sort of a flavour of coroutines (see below), where a single native thread is used to process large numbers of concurrent requests (usually involving IO). The programming model itself is a bit abstract and hard to reason about, so most people use a wrapper, and the Event Machine is the most common. Event Machine doesn’t necessarily use Fibers (it abstracts those concerns), but it is easy to find examples of code using Event Machine with Fibers in Ruby web apps (e.g. see this article by Ilya Grigorik, or the fibered example from em-http-request). People do this a lot to get the benefit of scalability that comes from using Event Machine in an I/O intensive application, without the ugly programming model that you get with lots of nested callbacks.

Actor 模型

Similar to Object Oriented Programming, the Actor Model is a deep thread of Computer Science going back to the 1970s. Actors provide an abstraction over computation (as opposed to data and behaviour) that allows for concurrency as a natural consequence, so in practical terms they can form the basis of a concurrent system. Actors send each other messages, so they are reactive in some sense, and there is a lot of overlap between systems that style themselves as Actors or Reactive. Often the distinction is at the level of their implementation (e.g. Actors in Akka can be distributed across processes, and that is a distinguishing feature of that framework).

延期结果(Futures)

Java 1.5 引入了一系列的新库,包括 Doug Lea 的 "java.util.concurrent",它其中的一部分是将延期结果封装成 Future 。 它是异步模式上简单抽象的一个很好例子,不强制实现是异步的,也不使用任何特定的异步处理模型。 正如 Netflix Tech Blog 提到的: 使用 RxJava 的 Netflix API 很好的展示了,当你只需要并发处理一组类似的任务时,Futures 是非常好的,但是一旦他们中的任何一个想要相互依赖或有条件地执行,你就会进入一种“嵌套回调”的地狱”。 响应式编程提供了一种解药。

Map-reduce 及 fork-join

并行处理上的抽象是有用的,有许多例子可供选择。 Map-reduce 及 fork-join 是Java世界中最近发展起来的,大规模并行分布式处理(MapReduce and Hadoop)及 JDK 1.7 (Fork-Join) 推动了它的发展 , 这些是有用的抽象,但(像延迟结果一样)与FRP相比,它们比较浅,FRP 可以用作简单并行处理的抽象,但它超越了后者,扩展到可组合性和声明性通信。

协程

“协程(coroutine)”是“子程序”的一种推广,它跟子程序一样有一个入口和一个出口,但当它退出时,它将控制权传递给另一个协同程序(不一定是传递给它的调用方),它积累的任何状态都会保存下来,并在下次调用时被记住。 协程可以用作更高级别功能(如Actors 及 Streams)的构建元素。 Reactive 编程的目标之一是在通信的并行处理代理上提供相同的抽象,因此协程(如果它们可用)是一个有用的构建元素。 有各式各样的协程,其中一些更严格,但比普通的子程序更灵活。 Fibers (see the discussion on Event Machine) 是一种, Generators (familiar in Scala and Python) 是另外一种.

Java中的响应式编程

Java不是一种“响应式语言”,因为它本身不支持协程。 JVM上有其他语言( 如Scala 及 Clojure ) 原生支持反应模型,但Java本身直到Java 9才支持。 然而,Java是企业发展的主力,最近在JDK之上提供响应式支持的活动很多。 我们在这里只简单地看一下。

  • 响应式流是一种非常低层次的约定,它表示为少量Java接口(加上TCK),但也适用于其他语言。 Publisher 及 Subscriber 这些接口是形成互操作类库的基本表达元素。(The interfaces express the basic building blocks of Publisher and Subscriber with explicit back pressure, forming a common language for interoperable libraries.) 响应式流已作为 java.util.concurrent.Flow 合并到JDK 9中。 该项目是 Kaazing, Netflix, Pivotal, Red Hat, Twitter, Typesafe 等公司的工程师之间的合作。

  • RxJava : Netflix 在内部使用响应式模式有一段时间了,然后他们在开源许可下开源改名为 Netflix/RxJava(随后被重新命名为“ReactiveX/RxJava”)。 Netflix在 RxJava 上用Groovy上做了很多编程,但是它对Java的使用是开放的,并且通过使用 Lambdas 非常适合Java 8 。 There is a bridge to Reactive Streams. 根据 David Karnok 的响应式分类 , RxJava 是一个“第二代”库。

  • Reactor 来自 Pivotal 开源团队(创建 Spring 的那家公司)的Java框架。 它直接建立在 Reactive Streams 上,所以不需要桥接。 Reactor IO 项目对 Netty and Aeron 之类的底层网络进行了封装。 根据 David Karnok 的响应式分代标准, Reactor 属于“第四代”类库。

  • 基于 Reactor ,Spring Framework 5.0 (2016年6月首次发布) 内置了一些响应式特性包括 HTTP 服务端与客户端。 在Web层中,Spring的老用户会看到一个非常熟悉的编程模型,该模型使用注解在 controller 方法来处理HTTP请求,这在很大程度上是将响应请求的调度和反压力问题交给框架。 Spring 构建在 Reactor 上,但也公开了API 支持使用其他替代库(例如 Reactor or RxJava). 用户可以从Tomcat、Jetty、Netty(通过 Reactor IO)和Underow中选择服务器端网络堆栈。

  • Ratpack 是一组用于通过HTTP构建高性能服务的库。 它建立在 Netty 之上,并实现互操作性的 Reactive Streams(所以您可以使用堆栈更高层的其他反应流实现)。 Spring作为原生组件受到支持,可以使用一些简单的实用程序类提供依赖项注入。 还有一些自动配置,这样 Spring Boot 用户就可以将Ratpack嵌入到Spring应用程序中,带来一个HTTP端点并监听它,而不是使用 Spring Boot 直接提供的一个嵌入式服务器。

  • Akka 是使用Scala或Java Actor 模式构建应用程序的工具包,使用 Akka 流进行进程间通信,并构建了 Reactive Streams 契约。 根据 David Karnok 的响应式分代标准, Akka 属于“第三代”类库。

为什么现在?

是什么推广了企业Java中 Reactive 的应用?好吧,这并非仅仅是一种时尚——不是一种闪闪发光的新玩具。 驱动因素是有效的资源利用,换句话说,就是在服务器和数据中心上花费更少的钱。 Reactive 承诺可以用更少的代价做更多的事情,特别是可以用更少的线程处理更高的负载。 这就是 Reactive 和非阻塞异步I/O 碰撞出的火花。 对于正确的问题,效果是戏剧性的。 对于错误的问题,影响可能会相反(实际上会使事情变得更糟)。 也要记住,即使你选择了正确的问题,也没有免费的午餐,反应式解决方案不能为你解决问题,它只是给了你一个工具箱,你可以使用它来实现解决方案。

结论

In this article we have taken a very broad and high level look at the Reactive movement, setting it in context in the modern enterprise. There are a number of Reactive libraries or frameworks for the JVM, all under active development. To a large extent they provide similar features, but increasingly, thanks to Reactive Streams, they are interoperable. In the next article in the series we will get down to brass tacks and have a look at some actual code samples, to get a better picture of the specifics of what it means to be Reactive and why it matters. We will also devote some time to understanding why the "F" in FRP is important, and how the concepts of back pressure and non-blocking code have a profound impact on programming style. And most importantly, we will help you to make the important decision about when and how to go Reactive, and when to stay put on the older styles and stacks.

数码
沪ICP备19006215号-4