使用Spring Cloud Stream和RabbitMQ实现事件驱动的微服务

310 阅读7分钟

微服务的世界是由基于REST的应用主导的。当我们谈论微服务时,一个自然的假设是一组单独的服务通过HTTP(S)REST相互交谈。但是,事件驱动的微服务在任何现代的、基于云的架构中都有重要的、关键的作用。

使用事件来交流应用状态或数据并不是一个新概念。几十年来,企业一直在使用消息传递系统,如IBM MQ。但这些都是重量级的、商业化的,需要专门的基础设施来维护。

随着AMQP协议和一些开源实现的引入,事件的使用已经变得很容易被采用。

在事件驱动架构中,应用程序通过发送和/或接收事件或消息来相互通信。这些信息可以只包含关于状态/状态变化的元数据,也可以包含主体中的实际内容。通信可以是点对点或发布-订阅。

春天云流

Spring Cloud Stream是Spring Cloud生态系统中的一个项目。它是一个框架,提供了一个基于Spring的编程模型,并在消息传递中间件的基础上增加了一个抽象层。

它使Spring应用程序能够无缝地连接到底层基础设施,而不必担心模板代码的问题。Spring (VMWare)维护RabbitMQ、Kafka、Kafka Streams和Amazon Kinesis的绑定器实现,而Google PubSub、Azure EventHub、Apache RocketMQ和Solace PubSub绑定器则由相应组织维护。

Spring Cloud Stream可以通过两种不同的方式使用

  • 使用Spring Cloud Dataflow创建一个流式数据管道
  • 使用点对点或Pub-sub模型创建事件驱动的微服务

本文解释了后者--使用Spring Cloud Stream与RabbitMQ来开发事件驱动型微服务

使用RabbitMQ的Spring Cloud Stream

RabbitMQ

RabbitMQ是一个广泛流行的消息传递平台。它有开源版本和商业版本。它是轻量级的,可以很容易地部署在笔记本电脑上;直接或使用Docker

使用 Docker 镜像。

RabbitMQ可以使用下面的docker命令进行安装。它将安装来自docker hub 的最新版本,前提是您已经登录到 docker hub。

docker pull rabbitmq

在运行容器时,不要运行 rabbitmq 容器,因为它不提供任何 GUI 来浏览交换和队列。

使用下面的命令运行管理插件。它将启动一个带有管理图形用户界面的rabbitmq容器。

docker run -d --hostname my-rabbit --name some-rabbit rabbitmq:3-management

可以使用http://localhost:15672/,并以访客身份使用默认凭证来访问该UI。

微服务

让我们来看看3个简单的微服务 - 生产者、处理器和消费者。生产者将通过REST 端点接受一个字符串,并向 RabbitMQ 主题发布一个消息。处理器将从主题订阅,将字符串转换为大写字母并发布到一个输出主题。消费者将订阅该主题并在控制台中打印该值。这 3 个服务将展示 Spring Cloud Stream 中的源 - 处理器 - 水槽概念。

带有RabbitMQ的Spring Cloud Stream

随着函数式编程的引入,源、处理器和汇的设置变得更加容易。我们将在本文中使用函数式编程。

所有3个服务的最终代码都可以在Git repo中找到。

本文中的 Topic 和 Exchange 可互换使用,因为 RabbitMQ 将一个 Topic 表示为一个 Exchange 和一个或多个绑定到该 Exchange 的队列

生产者

所有 3 个服务都需要云流、RabbitMQ 的 Spring 和start.spring.io 的 lombok 依赖项。由于生产者将通过 REST 端点接受请求,它也需要 Web 依赖关系。这被称为 Spring Cloud Stream 中的 Source a service。

创建一个简单的控制器 ,接受一个字符串并将其发送到一个主题。

import lombok.extern.slf4j.Slf4j;

正如您所看到的,Producer 微服务中没有任何代码或配置将其与 RabbitMQ 联系起来。在依赖关系中添加 RabbitMQ 绑定器为我们完成了所有的绑定。这使得切换底层消息传递提供商变得非常容易。

处理器

Processor 是一个纯粹的Spring Cloud Stream应用程序。它持续监听一个主题,并在收到消息后对其进行处理,然后发布到一个输出主题。因此,该服务仅需要云流和 Spring for RabbitMQ 的依赖关系。

添加一个组件类来处理消息。

import lombok.extern.slf4j.Slf4j;

该处理器表示为一个 java.util.Function,它接受一个字符串并以一个字符串进行响应。 函数式编程避免了对任何配置的需求。

当应用程序启动时,Spring会自动创建一个输入主题,并将此方法注册为监听器。在收到消息时,代码将把输入转换为大写字母并将其发送到输出主题。Spring 也会自动创建一个输出主题。

但这些主题是以默认的命名标准创建的。它们被创建为javaMethodName-in-和javaMethodName-out-,其中index对应的是应用程序实例的索引。

因此,当这个应用程序在本地运行时,交换器将被创建为convertToUppercase-in-0和convertToUppercase-0。但生产者微服务将事件发布到一个名为values-topic的交易所。因此,除非我们覆盖Spring创建的默认Exchange名称,否则Producer发送的消息将不会被Processor读取,因为他们将发送和监听不同的Exchange。

用下面的配置将处理器映射到值-主题上

spring:

添加组是可选的,但它将创建一个消费者组和一个持久化队列。离开组将导致创建一个匿名队列,当应用程序停止时将被销毁。消费者组也有助于高可用性和负载平衡,但由于这是一个只有一个实例的示例应用程序,添加一个组的主要原因是为了避免创建一个匿名队列。

输出交换/主题名称也在同一个yml中定义。下一个服务(Consumer)必须监听这个交换来读取消息。

消费者

消费者的依赖性列表与处理器相同。它是一个终端服务,被称为Sink,在Spring Cloud Stream中。

import lombok.extern.slf4j.Slf4j;

如上所述,Sink服务是通过创建一个java.util.Consumer这个bean来定义的,这个bean充当接收器。由于这使用了Functional编程,默认的绑定是用方法名创建的,正如在Processor部分提到的。为了消费Processor发送给upperase-topic的消息,需要添加一个配置。

spring:

至此,我们已经创建了一个功能齐全的Spring Cloud Stream。最终的代码可以在Git上找到。让我们测试一下,看看它的表现如何。

测试

启动所有3个服务。通过传递一个小写的字符串来调用生产者。例如,http://localhost:8080/values/hello

由于这些是异步、流应用,浏览器中的响应只表示生产者的成功。处理器的日志应该显示以下信息

Received hello

消费者的日志应该显示以下信息

Received the value HELLO in Consumer

这表明URL中的输入字符串如期通过了所有三个服务。

总结

使用Spring Cloud Stream开发事件驱动的微服务是简单而容易的。由于对函数式编程的支持,它可以用非常少的代码和配置来完成。

从上述微服务可以看出,Spring Cloud Stream避免了对任何模板代码的需求,并消除了代码或配置中对特定消息传递中间件实现的任何依赖。

将RabbitMQ改为另一种实现,如Azure ServiceBus或EventHub,就像改变Gradle 的依赖性一样简单。这有助于团队将其内部事件驱动的微服务迁移到任何一个公有云上,而不需要进行任何代码或配置更改。我将在随后的文章中介绍这一点。


使用Spring Cloud Stream和RabbitMQ的事件驱动微服务最初发表在Medium上的Javarevisited,在那里人们通过强调和回应这个故事来继续对话。