1.3 响应式的整体蓝图
从异步和事件驱动的理念出发,出现了许多模式的响应式解决方案。这个蓝图广阔而拥挤。图1-1摘录了这一蓝图并描述了主要事物之间的关系。
但不要忘记我们的目标:构建更好的分布式系统。其他“响应式”在这里帮助我们实施这些系统。
响应式特征,尤其是出现响应式系统的原因来自分布式系统。正如你将在第3章中看到的,构建分布式系统很困难。2013年,分布式系统专家编写了第一版“响应式宣言”(https://oreil.ly/6z8mt),并引入了响应式系统概念。
是的,无须应用响应式原则就可以构建分布式系统。响应式提供了一个蓝图,以确保在设计和开发系统时不会忽略任何重要的已知问题。另外,你可以将这些原则应用于非分布式系统。
响应式系统首先是响应式的。即使在高负载或遇到故障时,响应式也必须及时处理请求。为了实现这种响应式,响应式宣言建议使用异步消息传递作为构成系统的组件之间通信的主要方式。你将在第4章中看到这种通信方法如何实现弹性和恢复力,这是固态分布式系统的两个基本属性。本书的目的是向你展示如何用Quarkus构建这样的响应式系统。因此,构建响应式系统是我们的首要目标。
在分布式系统的核心注入异步消息传递并不是没有后果的。你的应用程序需要使用异步代码和非阻塞I/O,即操作系统提供的无须主动等待完成即可将I/O交互排队的能力。(我们将在第4章介绍非阻塞I/O。)后者对于提高资源利用率至关重要,如CPU和内存,这是响应式的另一个重要方面。如今,许多工具包和框架,如Quarkus、Eclipse Vert.x(https://vertx.io)、Micronaut(https://micronaut.io)、Helidon(https://helidon.io)和Netty(https://netty.io)使用非阻塞I/O正是这个原因:用有限的资源做更多的事情。
图1-1:响应式整体蓝图
然而,拥有一个利用非阻塞I/O的运行时并不足以实现响应式。你还需要编写包含非阻塞I/O机制的异步代码。否则,资源利用效益就会消失。编写异步代码是一种范式转变。从传统(命令式)开始,do x; do y;,现在,将代码塑造为on event(e)do x; on event(f)do y;。换句话说,响应式系统不仅是一个事件驱动的架构,而且你的代码也将成为事件驱动的。实现这类代码最直接的方法之一是回调:在接收到事件时注册调用的函数。与futures、promises和coroutines一样,其他所有方法都基于回调,并提供更高级别的API。
你可能想知道为什么电子表格会出现在这个蓝图中。电子表格是响应式的。在单元格中写入公式并(在另一个单元格中)更改公式读取的值时,此公式的结果将更新。单元格对值(事件)的更新做出响应,结果(响应)就是新的结果。是的,你的经理可能是一个比你更好的响应式开发人员!但别担心,这本书会改变这一点。
第5章介绍的响应式编程也是一种编写异步代码的方法。响应式编程使用数据流来构造代码。你观察数据在这些流中传输并对其做出响应。响应式编程提供了一个强大的抽象和API来塑造事件驱动的代码。
但是使用数据流会带来一个问题。如果你有一个快速的生产者直接连接到一个缓慢的消费者,则可能会淹没消费者。正如你将看到的,我们可以在两者之间缓冲或使用消息代理,但想象一下,如果没有这些缓冲区,消费者的信息就会被淹没。这将不利于响应型组织所倡导的响应能力和反脆弱思想。为了帮助我们解决这个特定问题,响应流(https://oreil.ly/5c275)提出了一种异步、非阻塞的背压协议,消费者向生产者发送信息,告知其可用性。正如你可以想象的那样,这可能不适用于任何地方,因为某些数据源的速度无法降低。
在过去的几年里,响应流的流行度有所增加。例如,RSocket(https://rsocket.io)是一种基于响应流的网络协议。R2DBC(https://r2dbc.io)提出了使用响应流的异步数据库访问。此外,RxJava(https://oreil.ly/QNEOJ)、Project Reactor(https://oreil.ly/eUHAL)和SmallRye Mutiny(https://oreil.ly/QNEOJ)采用了响应流来处理背压。最后是Vert.x(https://oreil.ly/tznoI)允许将Vert.x背压模型映射为响应流。
我们对响应式蓝图快速概览到此结束。正如我们所说,这个蓝图充满了许多术语和工具,但永远不要忽视响应式系统的总体目标:构建更好的分布式系统。这是本书的重点。