vlambda博客
学习文章列表

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

Microservices and Reactive Architecture

全球市场正在迅速发展,随之而来的是在过去几十年中引领 IT 领域及其他领域主要公司的商业模式。

需要成为第一个推销其产品的人已成为公司的口头禅。在这样的阶段,社交媒体和整个互联网可以让您快速传播您的品牌并快速影响市场,快速发布拦截社会需求的提案是成功的关键。

在这方面已经提出了许多市场分析,描绘了一种深度转型的状态。这里有些例子:

  • By 2020, more than 75% of the S&P 500 will be companies that we have not heard of yet
  • By 2020, every business will become a digital predator or digital prey
  • Among surveyed executives, 87% believe digital technologies will disrupt their industries and yet only 44% indicated their organizations were taking appropriate measures to avert disruption

这几乎看起来像是一个世界末日的场景,不是吗?幸运的是,情况并非如此。预期的情况是需要越来越快的创新需求来满足以我们不习惯的速度发展的市场。

这种需求的答案是数字化转型。它是什么?这是通过采用数字技术改造公司的所有主要资产(业务战略、营销方法模型、产品、目标等)的业务转型。通过这种方式,公司将能够加速业务的销售和增长。

这个过程需要通过采用敏捷方法和 DevOps 对公司结构进行重组,这需要使公司结构更加快速和高效,以便他们能够设计和发布高效、可扩展和高性能的架构——简而言之,微服务。

在本章中,我们将介绍以下主题:

  • MicroProfile and the principles of microservice architecture (MSA)
  • Service-oriented architecture (SOA) versus MSA
  • From monolith to microservices
  • Reactive systems and reactive programming

MicroProfile and the principles of MSA

在本节中,我们将描述微服务的基本原理及其特性,并介绍这种设计和构建应用程序的新方式的优缺点。

What are microservices?

微服务架构基于这样的概念,即应用程序应该包含一组松散耦合、独立和原子的服务,这些服务实现了业务能力。使用这种方法,可以轻松构建将大型企业应用程序(也称为单体应用程序)拆分为更小且一致的上下文(称为微服务)的软件。

它还支持公司技术堆栈的快速发展,这要归功于自主和独立的 DevOps 团队的建立。微服务通常由小型团队创建和管理,这些团队必须有足够的自主权来更改微服务的内部实现细节,无论是否对架构的其余部分产生很小的影响。

由于其模块化结构,MSA 可以使用持续交付/部署等方法快速快速地发布复杂的大型应用程序。一个微服务必须能够被任何客户端调用,不管它是用什么技术实现的;所以它必须与语言、平台和操作系统无关。

为了被使用,它应该为第三方公开 API,代表他们之间的一种合同。

Benefits of microservices

部署 MSA 提供了一些重要的好处:

  • Single responsibility: Each microservice should cover a well-defined business domain—the domain-driven design (DDD) approach can help developers build software that adheres to this concept to obtain an autonomous and atomic system.
  • Explicitly published interface: A producer service publishes an interface that is used by a consumer service. This is one of the most important points because the published interface represents the contract between the producer and the consumer—once the interface has been published, the API should not be modified. A new version of a contract must be followed by a new version of the interface to avoid unpredictable impacts to the service consumers.
  • Independent deploy, update, replace, and scale (DURS): A business domain could be made by a different set of microservices. To guarantee the continuity of business services, both in the case of issues or a new release version of one of the microservices that make up the suite, each service should be independently deployed, updated, replaced, and scaled.
  • Isolation: This is a prerequisite for resilience and elasticity. A microservice should share nothing with other microservices. In this way, it's easier to scale each service as well as allowing each of them to be monitored, debugged, and tested independently. It could also enable fault isolation to avoid issues with the entire business channel if only one microservice is affected by a problem. An issue, for example, an unclosed database connection or a memory leak, must impact only the faulty service and not the entire application. Using this strategy, you will be able to isolate the fault to a single service, avoiding propagating it to the rest of the suite.

  • Diversified technology: As I described earlier, MSA divides a complex domain into smaller contexts that are followed by dedicated teams. These teams are completely independent of each other and, for this reason, are able to choose the best technology to implement the microservice based on their skills, the business requirements, the strategic choices made by the company, and so on.
  • Smart endpoints and dumb pipes: Each microservice owns its domain logic but it should not cover a big context. The domain must be atomic but small and well-defined. Also, the communication protocol must be simple—at the moment, the de facto standard is represented by RESTful over HTTP.
  • Independent scaling: Each microservice can scale independently with different strategies—horizontal scaling and vertical scalingbased on what is needed. This is a different approach than that in monolithic applications, which may have different requirements and must be deployed as a single unit (e.g., Enterprise Application Archive (EAR) and Web Application Archive (WAR)).
  • Improved communication across the team: A microservice is typically built by a full-stack team that has all the necessary skills. All members work as a single team, with the same objectives, significantly improving the quality of what they build. They remain wholly responsible for a project, even in the production environment, ensuring quality in the project life cycle both in terms of technological evolution and monitoring.
  • Independent upgrades: Each service should be deployed independently from other services. In this way, a developer can decide to revisit the underlying implementation of the system without the risk of impacting other parts of the suite of services. The use of continuous integration (CI)/continuous delivery (CD) can help automate the process of checking code quality and releasing new software versions with confidence.

在列出所有这些积极方面之后,我们可以说微服务是任何场景下的理想解决方案吗?诚实的答案是否定的。

微服务并不是解决应用程序中所有架构问题的灵丹妙药。他们还需要在资源和技能获取方面进行大量投资。

在下一节中,我们将总结缺点,以便我们拥有所有元素来为我们的项目做出正确的选择。

Drawbacks of microservices

构建 MSA 并不容易。首先,您需要投资于人际交往能力。此外,您必须采用敏捷和 DevOps 等方法来创建能够获得以下内容的全栈团队:

  • Service replication: One of the benefits of MSA is that it is easy to scale up. But to do this, you need an infrastructure that enables this feature using a standard mechanism based on, for example, metadata. Platform as a Service (PaaS) is the ideal environment but to use it, your teams should have or obtain competencies in this area and your applications must be cloud ready or cloud-native. Therefore, you need to heavily invest in people and resources.
  • Service discovery: In a microservice ecosystem, services are distributed to guarantee load balancing and failover into immutable infrastructure provided by, for example, containers in a PaaS or immutable VM images using the major virtualization providers. Services scale up and down based on certain predefined metrics. In this scenario, it is difficult to know the exact address of a service until it is deployed and ready to be used. The exact service endpoint address is usually mapped by service registration and service discovery. Developers and operational people need to write code and maintain infrastructure that makes it easy to do so. The architectural design phase is different from the past. Therefore, you need to refactor the existing applications or design new ones in a different manner using these new actors.
  • Service monitoring: In a distributed environment, it is essential to monitor services and log information. This enables you to take proactive action if, for example, a service is consuming unexpected resources (such as CPU, memory, or threads) or has some issues (such as resource leak, memory leak, or a database connection leak). It's extremely important to create a system that is able to aggregate logs from different microservices, visualizing them in an easy way for operation teams, developers, and business users. If you don't build this infrastructure, you could encounter some difficulties in the production environment since you will not able to debug the application and react to an issue in an effective way.

  • Resiliency: Usually, a full business workflow is built by the invocation of multiple services being distributed in different environments—for this reason, it is easy to predict that one of these services could fail, independent of the hard work made by the development team to test it. For this reason, developers should try to avoid the failure by automatically implementing corrective actions to guarantee that the system's workflow continues to operate properly. It's not very easy to predict failure, so your team must contain highly skilled senior staff that can help design this type of software.
  • DevOps: There is no microservice without automation. CI/CD are essential for microservice-based applications to succeed. You need to identify bad software quality, performance issues, runtime execution exceptions, and so on, at the earliest opportunity via well-automated unit testing, performance testing, static code analysis, and deployment pipelines. It's extremely hard to build and maintain this type of automation in a source and infrastructure life cycle.
  • Resource and network overhead: A full suite, which is needed to implement a complex business domain, is built by multiple microservices. This means that a great number of applications require more resources to run than traditional monolith applications. You need to build this type of infrastructure or learn to use a cloud-native platform in all core development areas—Infrastructure as a Service (IaaS), PaaS, Software as a Service (SaaS), and so on. Do you have all of the necessary skills to choose, develop, and maintain these platforms?

另一个重要的考虑因素是 as-is 基础架构。也许,在您的组织中,有很棒的单体应用程序或 SOA 应用程序运行良好。你应该放弃它们还是完全重写它们?如果两者都不是,您如何将它们迁移到面向云的基础架构?

另一个难以管理的方面是数据的一致性——在技术实现方面,如何将其保存在分布式和异构的系统中?

因此,在收集所有点(正面和负面)之后,挑战在于确定何时使用 MSA 有意义。

对于那些在过去 10 到 15 年间构建企业应用程序的人来说,服务、隔离和数据所有权等概念会让人想起另一种架构模型,它是企业应用程序的基础——SOA。

那么MSA和SOA有什么区别呢?让我们在下一节中看看它们。

SOA versus MSA

面向服务的体系结构是一种设计软件的方式,其中业务服务通过标准通信协议进行通信,通常是简单对象访问协议 (SOAP),通过网络。 SOA 的主要目标是独立于供应商、产品和技术。 SOA 的主要单元是服务——一个可以远程访问、独立执行和更新的小功能单元。

服务之间的通信通过直接数据交换发生,或者它可能涉及由负责管理的协调器企业服务总线(ESB)协调的两个或多个服务执行流程。

SOA 中有两个主要角色——服务提供者和服务消费者。第一个是在 SOA 中定义的服务,而第二个是消费者与 SOA 交互的点。此外,数据存储在 SOA 中的所有服务中共享。

然而,问题在于 SOA 通常与 ESB 相关联,后者用于集成单体应用程序——它被认为是一种复杂的架构,难以扩展,并且与专有的供应商解决方案极为耦合。因此,有时很难以积极的方式思考 SOA。

在 2000 年代初 SOA 平台大规模传播和使用之后,由于我们前面描述的原因,这种架构缓慢而渐进地衰落。

但是,MSA 可以看作是 SOA 的演变——微服务规范中定义的许多技术都是从使用面向服务的架构和模式的大型企业组织中使用它们的开发人员的经验中诞生和整合的。出于这个原因,MSA 正在从社区中获得越来越多的积极反馈,并且它们的使用在企业公司中迅速扩大。

Differences between MSAs and SOAs

正如我之前提到的,MSA 和 SOA 之间有一些共同点,以至于您可以将 MSA 定义为 SOA 的演进。但是这两种架构设计的区别是什么?SOA 在哪里失败了?

让我们尝试总结一下它们的不同特征和概念:

MSA

SOA

它基于使用敏捷和 DevOps 方法的团队协作。它支持选择实施技术的自由,没有共享资源或治理模型。

它基于共享的公共治理,并使用 ESB 和标准通信协议实现。

一个基本原则是每个微服务都必须拥有数据,因此它必须具有独立的数据存储。

它允许服务共享数据库。

其中一个关键点是自动可扩展性,这是容器的原生优势。 MSA 可以定义为云原生架构。

它诞生于容器出现之前——可扩展性策略不太适合云环境。

执行环境不仅基于应用程序服务器,还可以通过经典方式(在其上部署应用程序)或使用 Uber JAR 使用小型 Web 服务器,例如 Tomcat、Undertow、Jetty 等特征。也可以使用 Javascript 引擎,例如 Node.JS,反应式系统,例如 Vert.x,Scala 生态系统等等。这是一个多语言架构。

它基于一个通用平台,通常是传统的 Java EE 应用服务器,用于部署到它的所有服务。

它更喜欢 HTTP、REST 等轻量级协议,以尽可能提高环境的性能。

它支持多种消息协议,但主要使用的是 SOAP。

服务之间的通信基于不太复杂和简单的消息传递系统——HTTP 动词使用 JSON 对象或异步消息传递模式(Java Message Service (JMS)、Advanced Message队列协议 (AMQP)、消息队列遥测传输 (MQTT)、服务器发送事件< /strong> (SSE)、推送事件等)。

服务之间的通信基于使用 SOAP 协议的 XML 消息,通常由单个 ESB 编排。

它有一个严格的限界上下文概念。

它专注于业务功能重用,因此不能保证完全隔离。

DevOps、CI、CD 和敏捷方法是 MSA 架构中的主流。

CI 在 SOA 环境中使用和支持。 MSA 列中列出的其他一些是近年来推出的,但并不代表设计的基本理念。

主要概念之一是软件和基础设施的不变性。代码更改应导致新的应用程序发布,而基础设施修改应导致新容器映像的发布。 MSA 通常与云环境有关。

代码更改需要更新单体,而基础设施更新应该通过修改属性的配置设置来获得。

它专注于解耦,即使以违反不要重复自己(DRY)模式为代价。

它最大限度地提高了应用服务重用在代码和业务实施方面的能力。

最后,我们不能说一种架构绝对出色,而另一种架构则完全错误。 SOA 有一些积极的方面。也许,导致其下降的几点是:

  • Strong coupling between services due to protocol communication and code sharing that increases the time to release a new version and make it difficult
  • Vendor lock-in due to the presence of ESB proprietary implementation
  • Limit of the scalability due to the technologies used

我们可以将 MSA 视为类固醇上的 SOA — 它重用优势并通过新的思维方式克服限制并设计焦点 使用伟大的t new云环境的特点。

评估 MSA 的最常见方法是重构经典的 Java EE 单体应用程序。通过这种方式,人们可以检查新设计中是否存在关键特性以及如何实现它们。之后,他们会开始考虑从零开始构建一个微服务云原生应用。

From monolith to microservices

通常,大型组织已经在代表其业务核心的现有单体应用程序中投入了大量资源。

根据 Gartner 研究(Kurt Potter、Sanil Solanki 和 Ken McGee,经营、增长和转变业务 IT 支出:分类和解释方法,Gartner G00308477,2016 年 6 月 27 日):

"The CIO of a company invests 70% of his budget to maintain the current portfolio, 19% to evolve the existing applications and only 11% to build new applications."

此外,还有一些例子,例如The Majestic Monolith中描述的例子(https://m.signalvnoise.com/the-majestic-monolith-29166d022228),这表明单体应用程序并不是像有人说的那样是魔鬼。

那么,为什么要拆除巨石呢?

反应总是一样的。在大多数情况下,单体应用在缩短上市时间、敏捷性有限、技术发展困难、成为 CD 的障碍以及增加技术债务等方面显示出其局限性。

What is a monolith?

就 Java EE 而言,单体应用程序或单体应用程序是通常作为单个单元分发的应用程序,例如 WAR 或 EAR 存档。所有功能都打包到这个单元中,并分为多个层,负责实现应用程序的特定区域:

  • User experience in a frontend layer is implemented using the Model-View-Controller (MVC) or Model View/View Model patterns
  • A business layer responsible for exposing business services to the frontend layer or third-party consumers
  • A data layer that interacts with the database to manage the standard create, read, update, delete (CRUD) functions

这只是对整体结构的最小描述——应用程序越复杂,用于实现它的模式和层就越多。

您可以在 https://developers.redhat.com/ticket-monster/ 更好地理解这类应用程序设计的结构。

Migration path

考虑到迁移过程将允许您创建基于微服务的云就绪应用程序,而不是微服务云原生应用程序。

一些限制源于旧 Java EE 规范的性质,这些规范不是面向微服务的;主要的当然是数据库。

请记住,每个微服务都必须拥有数据,因此它必须与单个数据库实例相关联。很难将为单体应用程序设计的单个数据库实例拆分为与由微服务实现的新有界上下文相关联的多个实例。

外键、数据完整性和数据库事务等概念,旨在使用 原子性、一致性、隔离在发生错误的情况下保证有效性,durability (ACID) 范式,很难通过由分布式和异构应用程序管理的多个数据库实例来维护。

要将单体架构重构为微服务,您可以评估并实施以下步骤:

  1. Divide the frontend layer from the backend layer: The separation should not only be from the code point of view (e.g., creating two Maven projects) but also from the distribution unit point of view (e.g., creating two WAR files, one for the frontend and the other for the backend). These two macro areas of the project have usually different release life cycles and need different skills. Having two teams that develop these areas will give you the opportunity to best implement these actions.
  2. Split the backend layer into multiple microservices: You should analyze your complex domain and divide it into multiple simpler domains that must be fully consistent. Remember that to be a real microservice, you should also create a dedicated database for each of the new microservices. You could decide to postpone this process to a second phase. In this way, you could benefit from all of the positive aspects of an MSA in terms of ease of development, isolation, resiliency of code, and ease of deployment. But without the full ownership of the data and continuing to use a shared data store, your multiple distribution units continue to form a monolith.
  3. Change the way to expose your data: Replace obsolete communication protocols, such as Enterprise Java Bean (EJB) remote, or complex methods such as SOAP web services, with a light communication model such as HTTP RESTful.
  4. Change the data transfer protocol: Remove strong coupling modes, such as Java objects, for example, using Java serialization, or using heavy and complex structures, such as XML payload, in favor of loose coupling, and an easy and light structure such as JSON objects.
  5. Remove heavy orchestrator actors: If your monolith runs in an SOA, replace ESB objects with the use of Enterprise Integration Pattern (EIP) to orchestrate the workflow process inside a coarse-grained service.

这些是从单体结构迁移到 MSA 时应实施的基本步骤。考虑一下,一次性执行我们描述的步骤以及每个步骤中描述的各个点并不是强制性的。

您可以继续逐步分解单体,以评估更新对您的应用程序、团队工作流程和公司结构的影响。正如敏捷方法中所建议的那样,将工作分成几个小冲刺可以帮助您评估工作的真正影响并更快对潜在的失败做出反应。

Reactive systems and reactive programming

reactive 一词在 MSA 的定义中被广泛使用。

但是反应式架构基于什么?反应的定义是在刺激后给出答案。因此,反应式软件能够调整其行为并对接收刺激作出反应。

要为微服务构建反应式架构,必须结合以下模型:

  • Reactive programming: A development model based on the observation of streams of data, which provides a way to react to their changes and propagate them toward other actors
  • Reactive system: An architectural model that is used to build robust and highly reactive distributed systems based on asynchronous communication using messages

反应式系统的定义于 2014 年 9 月通过反应式宣言 (https://www.reactivemanifesto .org/)。反应式系统的关键概念如下:

  • Responsive: The system must respond in a timely manner.
  • Resilient: The system must remain reactive and functional even in case of failure of some of its components.
  • Elastic: The system must remain reactive uniformly, regardless of the workload to which it is subjected.
  • Message-driven: The reactive systems must communicate with each other through asynchronous messages to favor their decoupling and isolation.

因此,我们可以将响应式系统视为构建分布式系统的架构设计,而响应式编程是响应式系统中定义的原则的实现,必须遵守响应式宣言。

Reactive systems

反应式系统是对一组原则的定义,这些原则需要保持响应能力并构建能够响应请求的系统,即使出现故障或负载很重。

为了构建这样的系统,反应式系统选择被认为可靠且高性能的消息驱动模型。所有组件使用异步发送和接收的消息相互交互。为了解耦发送者和接收者,组件使用虚拟地址发送和接收消息。

但什么是虚拟地址?它是通常表示为 URL 的目标标识符。交付语义取决于反应式编程框架中用于实现它们的技术。

在这个设计中,流的执行过程中没有阻塞——发送者不等待响应;他们可能稍后会收到回复,与此同时,他们可以继续完成发送其他消息的工作。

所以反应式系统的主要核心是异步消息传递设计,它应该能够保证以下属性:

  • Elasticity: The ability to scale horizontally that comes from the decoupling provided by message interactions. A load balance strategy is used by consumers of the messages, which are sent to a virtual address to distribute the load and guarantee high performance.
  • Resilience: The ability to handle failure and to recover messages that comes from the ability to handle failure without blocking the execution of the services workflow. Thanks to the asynchronous aspect, components don't wait for responses and a potential failure of one component would not impact others. Furthermore, when a node that is consuming a message fails in the operation, the message can be processed by another node registered on the same address.

拥有这两个特征的系统可以被认为是响应式的。

当您打算构建分布式且超出调用者控制的微服务系统时,这套原则非常重要。

Reactive programming

反应式编程可以被认为是反应式系统中定义的准则的实现。它是一种基于异步数据流的编程模型,面向数据的流动和传播。

在反应式编程中,软件必须对外部刺激做出反应,这些刺激可以是任何东西——变量、数据结构、用户输入、缓存、属性等等。这些刺激称为流。

用于将参与者调用到响应式编程中的名称与用于实现它的框架有关。

在本章中,我们将尝试快速分析能够实现反应式系统的主要 Java 框架:

  • RxJava
  • Spring reactor
  • Vert.x

对于每个框架,我们将基于 Maven 软件项目管理工具实现一个简单的示例。要尝试这些示例,您需要有一个支持 Maven 构建的 IDE 或在您的工作站中安装 Maven — 按照 https://maven.apache.org/install.html 来做。

最后,我们将快速解决如何使 Java EE 单体响应式化,而不需要通过我们之前列出的框架之一重写应用程序,而是利用 Java EE 提供的最新异步和消息传递特性。

RxJava

RxJava 是一个 Java 框架,它实现了 Reactive Extensions (ReactiveX http://reactivex.io/) 中描述的规范关于使用可观察流进行异步编程的 API。使用 RxJava 框架,开发人员可以通过可观察序列轻松创建基于事件的异步应用程序。

该框架建立在观察者模式之上以创建事件序列——它还公开了允许开发人员组合这些序列的类和方法。它还可以轻松处理 feature 难以实现但在反应式系统中绝对必要的功能,例如线程、同步、线程安全、并发数据结构、等等。

在本节中,我们将使用该框架的第 2 版,因为第 1 版已于 2018 年 3 月 31 日结束生命周期。它在 Apache 2.0 许可下发布并且是开源的。

RxJava 的主要元素如下:

  • Observables: They are the sources for the data and begin to provide data once a subscriber listens on them. An observable source may emit nothing or any number of items and it can finish successfully or with an error. Sources may never terminate and can potentially produce an infinite stream of events. This feature is the same as the ones that are provided by the stream class that was introduced in Java 8.
  • Subscribers: An observable can have multiple subscribers. Every time a new item is published by the observable, the onNext() method is called on each subscriber until the observable finishes its data flow. If the outcome is successful, then the onComplete() method will be called on each subscriber; otherwise, the onError() method will be called on each subscriber. In every situation, the flow must be processed and an action must be taken to react on the stimuli received by the system and represented by the data stream.

要使用 RxJava2 框架,您必须将其添加到项目的依赖项声明中。如果您使用的是 Maven,您可以通过以下代码段添加 RxJava 来实现:

<!--https://mvnrepository.com/artifact/io.reactivex.rxjava2/rxjava -->
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>2.1.14</version>
</dependency>

现在,我们准备编写简单的第一个类:

package com.reactive.examples;

import io.reactivex.*;

public class RxJavaSimpleTest {
public static void main(String ... args) {
Disposable subscribe = Flowable.just("Welcome on RxJava2 World")
.subscribe(System.out::println);
subscribe.dispose();
}
}

在这个类中,我使用了一个 Flowable 类,如 Javadoc (http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html),实现了响应式流模式。它公开了中间运算符、工厂方法以及使您的应用程序能够使用响应式数据流的功能。

Flowable 对象基于字符串“Welcome on RxJava2 World” 创建一个数据流。之后,它将给定的字符串发送给订阅者(在我的示例中,System.out::println 方法),最后完成。运行该类后,您将在控制台上看到以下输出——“Welcome on RxJava2 World”

RxJava2 提供了基类,使您能够处理不同类型的数据流并对它们执行不同的操作。

下面是另一个 RxJava2 基类的简单使用示例:

package com.reactive.examples;

import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.functions.Consumer;
public class RxJavaBaseClassesTest {

public static void main(String... args) {
// A flow of exactly 1 item or an error
Single.just(1)
.map(i -> i * 5)
.map(Object::toString)
.subscribe(System.out::println);

// A flow with exactly one item.
Maybe.just("May be I will do something...").subscribe(System.out::println);

// Never sends any items or notifications to a MaybeObserver
Maybe.never().subscribe(o -> System.out.println("Something is here...never happened"));

// A flow without items but only a completion or error signal
Completable.complete().subscribe(() -> System.out.println("Completed"));

// Example of simple data flow processing
Flowable.just("mauro", "luigi", "marco")
.filter(s -> s.startsWith("m"))
.map(String::toUpperCase)
.subscribe(System.out::println);
}
}

如果你执行这个类,你应该看到以下输出:

5
May be I will do something...
Completed
MAURO
MARCO

输出反映了我在 Java 类中对组件行为的描述。我得到了以下内容:

  • The exact computation in the first line: 1 * 5 result
  • The output that confirms the execution of a Maybe condition
  • The output that confirms the end of the execution process
  • The result of a simple filter and map operation

RxJava 包含一个模块列表,这些模块可以与用于实现 MSA 中使用的主要模式的其他重要框架进行交互,如下所示:

  • Hystrix: A latency and fault tolerance library that's used to prevent the cascading failure, which is derived from service outage third-party remote systems
  • Camel-rx: A library that simplifies the use of Camel components to implement a Reactive Extension
  • Vert.x RxJava: A module that provides easy integration with Vert.x toolkit, which we will examine later
  • RxJava-jdbc: A library that enables you to change the way you interact with the database, usually in synchronous and blocking mode, using the stream API to process result sets and do functional composition of statements

这个框架在 Java SE 环境中的影响是非常重要的。这种经验催生了反应式系统计划,旨在为在 Jav虚拟机(JVM上运行的异步流定义标准协议)。

RxJava 团队是该计划的一部分,该计划在 Java SE 9 中定义了一组接口 java.util.concurrent.Flow.*,专门用于实现定义的概念通过 Reactive Streams — 实际上,JDK 9 中可用的接口等同于它们各自的 Reactive Streams 对应物。

尽管提供的示例非常简单,但它们仍然清楚地表明了反应式架构中的应用程序设计有多么不同。经典的请求/回复模型,带有等待回复的后续块,被完全异步的非阻塞模型所取代,例如发布/订阅模型。对于 Java EE 开发人员,这是 Java 消息服务 (JMS) 规范中已知且广泛使用的模型。

诸如 RxJava 之类的框架扩展了这些概念,即使在暴露的场景和 API 的使用(通常是 MSA)中也允许使用它们。 Javascript 开发人员,或者一般来说,那些更习惯于在客户端环境中使用新技术工作的人,他们是面向 HTML 5 的,他们非常了解这些范式,这是他们实现模型的基础。

您可以通过访问库的 GitHub 存储库找到有关 RxJava 的更多详细信息:https://github.com/ReactiveX/ RxJava/

Spring WebFlux and reactive stacks

我们可以将 RxJava 视为一个低级框架,包含响应式概念的特定实现。但是架构框架,例如 Spring,是如何考虑企业环境中的反应式结构的呢?

在 Spring Suite 中开发的传统 Web 框架 Spring Web MVC 构建在 Servlet API 和 servlet 容器(主要是 Tomcat)之上。在 Spring 5.0 中引入了名为 Spring WebFlux 的响应式实现。它完全支持Reactive Streamsnon-blocking,并在不同的服务器类型上运行:

  • Traditional, such as a Servlet 3.1+ container (Tomcat)
  • A new performant web server based on Java NIO, such as Undertow
  • Asynchronous event-driven network servers, such as Netty

从历史上看,Spring 代表了允许我们克服 Java EE 平台中存在的限制的框架。如第 1 章中所述, Jakarta EE——JEE的新开源生活,说的不妥Spring 代表了推动 Java EE 生态系统持续不断改进的进化推动力。

此外,在反应式架构的情况下,Spring 提供了能够实现反应式流所要求的解决方案,而以前的 Java EE 版本不容易获得。

在响应式架构中,以及在云环境和微服务系统中,都需要非阻塞 API 来处理具有少量线程的高并发请求,从而以更少的硬件资源轻松扩展基础架构。

Java EE 7 和 Servlet 3.1 确实为非阻塞 I/O 提供了 API,但一些核心 API 是同步的,例如 Filter,或阻塞的,例如 getParametergetPart 方法。

这就是促使 Spring 构建可以在任何非阻塞运行时中使用的新通用 API 的原因。 Netty 的核心基于这些概念,因此被选为 Spring WebFlux 的运行时执行环境。

Spring WebFlux 是 Reactive Stream 规范的新 Spring 实现。主要目标是创建一组新的功能更丰富的 API,使开发人员能够编写 async 逻辑,这是响应式编程的基础。 Spring WebFlux 的核心库是 Reactor(https://github.com/reactor/reactor);它提供了两个主要的 API:Mono 和 Flux。 感谢这些新的 API, 可以轻松处理 0..1 的数据序列和0..n 通过一组丰富的方法和实现。

那么要在 Spring 中构建一个响应式堆栈,我应该离开 Spring MVC 并只使用 Spring WebFlux 吗?

答案是否定的。 这两个模块可以共存,并且旨在相互保持一致。特别是,在 MSA 中,您可以自由地使用多语言微服务,无论是在编程语言方面还是在所选框架实现的功能方面。

对于 Spring,您可以使用 Spring MVC、Spring WebFlux 控制器,或者与 Spring WebFlux 一起使用功能端点。差异与编程模型密切相关,可以是带注释的控制器或功能端点。

让我们尝试通过 Spring WebFlux 实现一个通过 HTTP 协议暴露服务的简单示例。

我们可以使用 Spring Initializr 来快速引导我们的应用程序。您应该连接到 https://start.spring.io/,然后执行以下步骤:

  1. Set com.microservice.webflux as the project's Group.
  2. Set demo as the Artifact name.
  3. Set reactor in the Search for dependencies field and select Reactive Web from the drop-down menu.
  4. Click the Generate Project button to download a scaffold of a Maven project.

这是您到目前为止所做的事情的快照:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

下载后,将项目文件解压缩到一个目录中,然后使用您喜欢的 IDE 打开您的 Maven 项目,以创建构建服务所需的类。

请确保您已将 Spring 存储库设置到 Maven settings.xml 文件中。否则,您需要根据自己的喜好从以下选项中进行选择:

要创建的第一个类是负责处理请求和创建响应的处理程序:

package com.microservice.webflux.demo;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class DemoHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Welcome on Spring WebFlux world!"));
}
}

这个 handler 类具有静态行为,并且 always 返回字符串 “欢迎来到 Spring WebFlux 世界! ".在实际场景中,它可以返回从数据库查询派生或通过计算生成的项目流。 handler 方法返回一个 Mono 对象,Java 8 的 Reactive 等价物 CompletableFuture,它包含一个 服务器响应 body.

在创建 handler 之后,我们需要构建一个 router 类来拦截指向特定路径的请求,在我们的例子中是 /welcome,并且返回调用 handler 方法的结果作为响应:

package com.microservice.webflux.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class DemoRouter {

@Bean
public RouterFunction<ServerResponse> route(DemoHandler demoHandler) {
return RouterFunctions.route(RequestPredicates.GET("/welcome")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), demoHandler::welcome);
}
}

现在我们有了 handlerrouter,是时候构造 WebClient 类了,它需要保存调用的内容和将其交换为特定的返回值类型,在我们的例子中是一个字符串:

package com.microservice.webflux.demo;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class DemoWebClient {

// Externalize the URL in a configuration file
private final WebClient client = WebClient.create("http://localhost:8080");
private final Mono<ClientResponse> result =
client.get().uri("/welcome").accept(MediaType.TEXT_PLAIN)
.exchange();

public String getResult() {
return ">> result = " + result.flatMap(res -> res.bodyToMono(String.class)).block();
}
}

这是一个经典示例,与传统的 Spring MVC RESTTemplate 相比,Spring WebFlux 引入了一个重要的变化。 Spring MVC 的 WebClient 实现是阻塞的,而 Spring WebFlux 是非阻塞的,遵循响应式哲学。

最后,我们需要使我们的简单应用程序可执行。 Reactive Spring WebFlux 支持嵌入 Netty 服务器作为 HTTP 运行时,它将托管应用程序并通过 HTTP 协议公开服务:

package com.microservice.webflux.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
DemoWebClient demoWebClient = new DemoWebClient();
System.out.println(demoWebClient.getResult());
}
}

我的类的 main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用程序。现在,使用 Maven,通过 mvn clean package 命令或通过您的 favorite IDE 编译应用程序,以获得将在 < kbd>$PROJECT_HOME/target directory 即解压 Spring Initializr 创建的脚手架项目的位置。

最后,是时候通过 java -jar target/demo-0.0.1-SNAPSHOT.jar 命令运行我们的应用程序了(确保您在 $PROJECT_HOME 目录中)。

您将获得如下输出:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

您会注意到,在输出的末尾有一个字符串,Welcome on Spring WebFlux world!,这是 DemoHandler 类中定义的方法的结果。您还可以使用浏览器测试方法调用并将 URL 设置为 http://localhost:8080/welcome。您将获得相同的结果:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

如果您更喜欢遵循测试驱动开发 (TDD),您可以构建您的 JUnit 测试来验证服务的输出是否符合您的预期:

package com.microservice.webflux.demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoRouterTests {

@Autowired
private WebTestClient webTestClient;

@Test
public void testWelcome() {
webTestClient.get().uri("/welcome").accept(MediaType.TEXT_PLAIN)
.exchange().expectStatus().isOk()
.expectBody(String.class).isEqualTo("Welcome on Spring WebFlux world!");
}
}

通过启动 mvn test 命令,您将能够验证输出是否符合您的预期。

总之,我们刚刚实现了一个简单的场景,向您展示了如何使用 Spring Reactor 和 Spring WebFlux 以反应方式实现、公开和使用 API。

Vert.x

Vert.x 是一个开源的 Eclipse 工具包,用于构建分布式和反应式系统。

Eclipse Vert.x 提供了一种灵活的方式来编写轻量级和响应式的应用程序,因为它实现了 Reactive Stream 原则。

它被设计为云原生——它允许许多进程以很少的资源(线程、CPU 等)运行。这样,Vert.x 应用程序可以更好地在云环境中使用其 CPU 配额。创建大量新线程不会造成任何不必要的开销。

它定义了一个基于事件循环的异步和非阻塞开发模型,该模型处理请求并避免客户端长时间等待而服务器端因大量调用而受到压力。

由于它是一个工具包而不是一个框架,因此 Vert.x 可以用作典型的第三方库,您可以自由选择您需要定位的组件。

它不提供一体化解决方案,但它为您提供了可以组装以构建自己的架构的脚手架。您可以决定在现有应用程序(例如 Spring 应用程序)中使用 Vert.x 组件,可能会将特定部分重构为响应式并更好地响应重负载。由于其大量事件处理,它也是实现 HTTP REST 微服务的不错选择。

Vert.x 在 JVM 之上运行,但它不能单独在 Java 中运行。您可以使用围绕 JVM 的所有编程语言生态系统。您也可以 将 Vert.x 与 Groovy、Ceylon、JavaScript(可能使用 Java 提供的 JavaScript 引擎,如 Nashorn)和 JRuby 结合使用。

可以在此处查看包含 Vert.x 模块列表的图表:https: //www.eclipse.org/community/eclipse_newsletter/2016/october/article4.php。如您所见,有一些组件可以帮助您构建反应式微服务。

Vert.x 的主要组件是verticle。 verticle 是可编程单元,您可以在其中实现应用程序的业务逻辑。尽管它在 Vert.x 工具包中是可选的,但强烈建议使用它来在您的架构中获得明确的职责分离。它提供了一个类似于 actor 的部署和并发模型,类似于您可以使用 Akka(一个用于构建弹性消息驱动应用程序的工具包)获得的模型,并且它始终在同一个线程上执行。

使用事件循环机制,单个线程可以执行多个 Verticle,并且单个 Verticle 实例通常每个核心启动一个线程/事件循环。

一个 Vert.x 应用程序可以由多个 Verticle 组成;每个人都实现了领域业务逻辑的特定部分,以遵循单一职责和职责分离的原则。每个 Verticle 应该以松耦合的方式与其他 Verticle 通信——为此,Vert.x 使用事件总线,即轻量级分布式消息传递。当一个 Verticle 想要监听来自事件总线的消息时,它会监听一个特定的虚拟地址,如 Reactive Stream 规范中定义的那样。

现在,是时候创建我们的第一个简单的微服务应用程序了。正如我之前提到的,对于 Spring Boot 和 Spring WebFlux,以及对于 Vert.x,有一个项目生成器实用程序可以帮助您为项目创建脚手架。生成器可在 http://start.vertx.io/ 获得。

使用以下建议值设置字段(随意将它们替换为您想要的任何值):

  • Version: 3.5.0
  • Language: Java
  • Build: Maven
  • GroupId: com.microservice.vertx
  • ArtifactId: demoVertx
  • Selected dependencies: Vert.x Web

以下是我之前描述的简单截图:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

下载 ZIP 文件后,将上下文解压缩到一个目录中,使用您最喜欢的 IDE 打开项目,最后,从您的 IDE 或使用以下 Maven 命令启动第一个构建:

$ mvn clean compile

这样,您就创建了一个简单的可运行 Vert.x 应用程序。

你会注意到 com.microservice.vertx.demoVertx 包中有一个简单的 Verticle 类 MainVerticle.java,它创建一个 HTTP 服务器,监听 port 8080,并返回一个简单的纯文本消息, “Hello from Vert.x!”:

package com.microservice.vertx.demoVertx;

import io.vertx.core.AbstractVerticle;

public class MainVerticle extends AbstractVerticle {

@Override
public void start() throws Exception {
vertx.createHttpServer().requestHandler(req -> {
req.response().putHeader("content-type", "text/plain").end("Hello from Vert.x!");
}).listen(8080);
System.out.println("HTTP server started on port 8080");
}
}

要运行应用程序,您可以执行以下操作:

  • Launch the $ mvn compile exec:java command
  • Build a fat JAR, with the $ mvn package command, and then launch the executable JAR with the java -jar target/demoVertx-1.0.0-SNAPSHOT-fat.jar command

最终结果将是相同的——一个正常运行的 HTTP 服务器:

....
HTTP server started on port 8080
Jun 20, 2018 11:09:16 AM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
INFO: Succeeded in deploying verticle
....

您还将有一个通过 HTTP 公开的服务,您可以在 URL http://localhost:8080 处调用该服务:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

现在,我们准备更新我们的源代码,以便我们可以实现一个简单的微服务。

首先,我们创建 RESTful Web 服务所需的类。我们必须构建一个简单的 Java Plain Old Java Object (POJO) 来存储作者的信息:

package com.microservice.vertx.demoVertx;

import java.util.concurrent.atomic.AtomicInteger;

public class Author {

private static final AtomicInteger COUNTER = new AtomicInteger();
private final int id;
private final String name;
private final String surname;

public Author(String name, String surname) {
this.id = COUNTER.getAndIncrement();
this.name = name;
this.surname = surname;
}

public String getName() {
return name;
}

public String getSurname() {
return surname;
}

public int getId() {
return id;
}
}

然后,我们制作AuthorVerticle,目的是暴露数据;在我们的示例中,该书的作者列表。有数据的静态初始化,以及 Router 和 HTTP 服务器的通常创建以服务请求:

package com.microservice.vertx.demoVertx;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.json.Json;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import java.util.LinkedHashMap;
import java.util.Map;

public class AuthorVerticle extends AbstractVerticle {

private final Map<Integer, Author> authors = new LinkedHashMap<>();

private void populateAuthorsData() {
Author mauro = new Author("Mauro", "Vocale");
authors.put(mauro.getId(), mauro);
Author luigi = new Author("Luigi", "Fugaro");
authors.put(luigi.getId(), luigi);
}

private void getAuthors(RoutingContext routingContext) {
routingContext.response().putHeader("content-type", "application/json; charset=utf-8")
.end(Json.encodePrettily(authors.values()));
}

@Override
public void start(Future<Void> future) {
populateAuthorsData();
Router router = Router.router(vertx);
router.get("/authors").handler(this::getAuthors);
vertx.createHttpServer().requestHandler(router::accept).listen(8080,
result -> {
if (result.succeeded()) {
future.complete();
} else {
future.fail(result.cause());
}
});
}
}

最后,我们重构了 MainVerticle 类,它将负责部署我们之前创建的 Verticle:

package com.microservice.vertx.demoVertx;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;

public class MainVerticle extends AbstractVerticle {

@Override
public void start(Future<Void> future) throws Exception {
vertx.deployVerticle("com.microservice.vertx.demoVertx.AuthorVerticle", res -> {
if (res.succeeded()) {
System.out.println("Deployment id is: " + res.result());
} else {
System.out.println("Deployment failed!");
}
});
}
}

现在,我们可以启动应用程序:

$ mvn compile exec:java

然后,我们可以使用 URL http://localhost:8080/authors 调用 RESTful 服务。您将获得以下结果:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》微服务与反应式体系结构

您可以按照 TDD 测试您的服务,还可以实现 JUnit 测试:

package com.microservice.vertx.demoVertx;

import io.vertx.core.Vertx;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(VertxUnitRunner.class)
public class AuthorVerticleTest {

private Vertx vertx;

public AuthorVerticleTest() {
}

@Before
public void setUp(TestContext context) {
vertx = Vertx.vertx();
vertx.deployVerticle(AuthorVerticle.class.getName(), context.asyncAssertSuccess());
}

@After
public void tearDown(TestContext context) {
if (vertx != null) {
vertx.close(context.asyncAssertSuccess());
}
}

/**
* Test of start method, of class AuthorVerticle.
* @param context
*/
@Test
public void testStart(TestContext context) {
final Async async = context.async();
vertx.createHttpClient().getNow(8080, "localhost", "/authors", response -> {
response.handler(body -> {
context.assertTrue(body.toString().contains(""name" : "Mauro""));
async.complete();
});
});
}
}

通过运行 $ mvn test 命令检查结果。

在这里,我们再次实现了一个简单的示例,向您展示如何使用 Vert.x 构建反应式微服务。您可以在 https://vertx.io/docs/ 的 Vert.x 文档中找到更多详细信息。

Reactive Java EE monolith

Java EE 在其规范中不包括反应范式。原因很简单——它诞生于以这种方式设计架构的能力被开发出来之前。出于这个原因,让 Java EE 响应式并不是很容易。

尽管如此,最新的 Java EE 版本还是引入并随后改进了 Reactive Stream 规范的关键特性之一——异步性。

因此,如果您不想使用我们之前描述的框架之一或其他选项(例如 Akka)来重写您的应用程序,您可以尝试重新访问您的 Java EE 经典实现以使其面向响应式。

Java EE 具有以下两个可能对本主题有用的主要方面:

  • The ability to process requests in asynchronously
  • The ability to decouple communications between its components via messages

让我们更详细地研究这些功能。

Asynchronous processing

企业应用程序中有一些常见的场景,通常是同步和阻塞的,例如数据库或远程服务器调用。在这些情况下,对这些方法的调用需要一些时间才能返回,通常是因为客户端线程被阻塞等待响应。让我们考虑一个简单的例子——一个 Java EE servlet,它使用服务查询数据库,并打印记录列表,在我们的例子中是作者列表(是的,我知道,我有点以自我为中心):

@WebServlet(urlPatterns = "/authors")
public class AuthorServlet extends HttpServlet {
...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
final List<Author> authors = authorService.getAuthors();
final ServletOutputStream out = resp.getOutputStream();
for (Author author : authors) {
out.println(user.toString());
}
}
}

您必须等待在 authorService.getAuthors() 中实现的数据库查询调用完成以获取响应。因此,在处理请求之前,客户端会一直处于阻塞状态。

让我们尝试使 servlet 异步:

@WebServlet(urlPatterns = "/authors", asyncSuppported = true)
public class AuthorServlet extends HttpServlet {

...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
final AsyncContext asyncCtx = req.startAsync();
asyncCtx.start(() -> {
final List<Author> authors = authorService.getAuthors();
printAuthors(authors, asyncCtx.getResponse().getOutputStream());
asyncCtx.complete();
});
}

private void printAuthors (List<Author> authors, ServletOutputStream out) {
for (Author author : authors) {
out.println(author.toString());
}
}
}

通过一个简单的更新,我们使我们的 servlet 异步,让我们的客户端在等待接收对所发出请求的响应时执行其他操作。

好吧,您可以争辩说现在没有人编写 servlet。但是,你确定吗?

在 MSA 中,标准通信基于 RESTful Web 服务,在 Java EE 世界中,由 JAX-RS 实现——这些规范建立在 servlet 规范之上。

然后,我们可以以 RESTful 方式重新访问我们的简单 servlet:

@Path("/authors")
public class AuthorResource {

...
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAuthors() {
final List<Author> authors = authorService.getAuthors();
return Response.ok(authors).build();
}
}

现在,我们可以让它异步:


@Path("/authors")
public class AuthorResource {

...
@GET
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void getAuthors(@Suspended AsyncResponse response) {
final List<Author> authors = authorService.getAuthors();
response.resume(Response.ok(authors).build());
}
}

注释、@Asynchronous上下文依赖注入 (CDI) 方法参数 @Suspended AsyncResponse response 用于使其异步。快速获胜!

但是请记住,永远不要永远等待,永远不要将您的应用程序与其他服务的问题结合起来。因此,如果出现不可恢复的问题,请始终设置超时以中止调用。

这是我们简单示例的最终版本:

@Path("/authors")
public class AuthorResource {

...

@GET
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void getAuthors(@Suspended AsyncResponse response) {
response.setTimeout(2, TimeUnit.SECONDS);
response.setTimeoutHandler(resp ->
resp.resume(Response.status(Status.REQUEST_TIMEOUT).build()));
final List<Author> authors = authorService.getAuthors();
response.resume(Response.ok(authors).build());
}
}

如果您的应用程序包含重负载和软负载查询的混合,这种方法非常有用。这样,您可以获得更高的吞吐量并有机会微调您的应用服务器。

我们已经讨论了如何异步公开我们的 API。但是 应该怎么做才能在业务逻辑层得到同样的结果呢?

我们可以使用 EJB 来实现它。有些人认为 EJB 是魔鬼:我不同意。

这种负面印象是由于与 2.1 版本相关的复杂且性能不佳的实现而产生的。新的从3.0版本到3.2版本,简化了使用和配置,并以大量的新特性丰富了平台。

我认为使用 EJB 远程公开您的服务是不赞成的。您永远不应该通过 EJB 远程通信,因为它是一种强耦合且性能较差的方式。但是,EJB 本地 可以在 Java EE 之外使用container 因为它们只是 POJO,简化了一些核心主题,例如线程安全的处理、并发性、事务生命周期等。我认为 Jakarta EE 中可以在下一个特性中实现的改进将是从规范中删除远程 EJB。

现在,让我们回到我们的业务层实现。

这是经典的同步实现:

@Stateless
public class AuthorService {

public Author createAuthor(final String name, final String surname) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
return author;
}
}

让我们让它异步:

@Stateless
public class AuthorService {

@Asynchronous
public Future<Author> createAuthor(final String name, final String surname) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
return new AsyncResult<>(author);
}
}

这很简单——告诉容器使调用异步,使用 @Asynchronous 注释,并将返回类型从经典 POJO 更改为 Future ,实现通过 AsyncResult

之后,我们可以实现我们的 RESTful 类来使用新特性:

@Path("/authors")
public class AuthorResource {

@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response createAuthor(@FormParam("name") final String name, @FormParam("surname")
final String surname) {
final Future<Author> authorFuture = authorService.createAuthor(name, surname);
try {
final Author author = authorFuture.get(2, TimeUnit.SECONDS);
return Response.ok(author).build();
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return Response.serverError().build();
}
}
}

但是,我们可以使用 Java SE 8 中引入的 CompletableFuture 类以更好的方式做到这一点:

@Stateless
public class AuthorService {

@Asynchronous
public void createAuthor(final String name, final String surname, final
CompletableFuture<Author> promise) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
promise.complete(author);
}
}

我们没有返回 Future,而是创建了我们的方法 void 并告诉 Java EE 容器注入一个负责通知结束的 CompletableFuture的操作。

现在,我们可以重新审视 RESTful 实现:

@Path("/authors")
public class AuthorResource {

@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void createAuthor(@FormParam("name") final String name, @FormParam("surname") final
String surname, @Suspended AsyncResponse response) {
CompletableFuture<Author> promise = new CompletableFuture<>();
authorService.createAuthor(name, surname, promise);
promise.thenApply(response::resume);
}
}

我更喜欢这种方法而不是使用 FutureFuture 是目前在 Java EE 中实现异步的唯一解决方案,但与 CompletableFuture 相比,它有一些局限性;例如,它无法构建和使用新的 lambdas 功能。

Messaging communications

Java EE 提供了实现松散耦合服务的能力,这些服务可以相互通信,保持交付和容错的保证。这个特性是通过 JMS 实现的,该 JMS 是从 Java EE 平台开始就引入的。本规范的目的是解耦服务和处理消息,异步生成和使用它们。

该规范的新版本 JMS 2.0 是在 Java EE 7 中引入的,它简化了该规范的使用并减少了实现消息生产者和消费者所需的样板。

以下是消息生产者的一个简单示例:

public class AuthorProducer {

@Inject
private JMSContext jmsContext;

@Resource(lookup = "jms/authorQueue")
private Destination queue;

/**
* Send Message.
*/
@Override
public void sendMessage(String message) {
/*
* createProducer: Creates a new JMSProducer object that will be used to
* configure and send messages.
*/
jmsContext.createProducer().send(queue, message);
}
}

现在,是时候实现消费者了:

@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/authorQueue")})
public class AuthorMDB implements MessageListener {

/**
* @see MessageListener#onMessage(Message)
*/
public void onMessage(Message rcvMessage) {
TextMessage msg = null;
try {
if (rcvMessage instanceof TextMessage) {
msg = (TextMessage) rcvMessage;
System.out.println("Received Message from queue: " + msg.getText());
} else {
System.out.println("Message of wrong type: " + rcvMessage.getClass().getName());
}
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
}

在 Java EE 中实现解耦消息服务很容易。

显然,这不足以宣称传统的 Java EE 方法是响应式友好的。

但是我们上面描述的方法可以帮助您将传统的单体更新为更少的同步和更少的 I/O 阻塞,从而更好地服务于设计企业和云原生架构的新方法。

Summary

在本章中,我们讨论了微服务、MSA 和反应式架构,以及 SOA 的原理。我们学习了如何对应用程序进行现代化改造,从而将单体架构转变为 MSA。我们还回顾了主要的开源框架,如 Spring 和 Vert.x,是如何实现响应式原则的,以帮助开发人员构建可扩展、可靠且具有成本效益的面向云的应用程序。最后,我们了解了如何更新传统的 JEE 应用程序以实现异步和非阻塞系统,这是构建自己的 MSA 的第一步。

在下一章中,我们将研究云原生应用程序和微服务,以便我们了解如何设计和实现它们。