API网关(REST)+事件驱动的微服务


16

我有一堆微服务,这些服务根据API网关模式通过REST API公开。由于这些微服务是Spring Boot应用程序,因此我正在使用Spring AMQP来实现这些微服务之间的RPC风格的同步通信。到目前为止,一切进展顺利。但是,我对事件驱动的微服务体系结构了解得越多,对诸如Spring Cloud Stream之类的项目的了解越多,我就深信我可能会采用RPC同步方法以错误的方式进行操作(特别是因为我需要按比例缩放)以便每秒响应来自客户端应用程序的数百或数千个请求)。

我了解事件驱动架构背后的观点。我不太了解的是当坐在期望对每个请求都响应的模型(REST)后面时,如何实际使用这种模式。例如,如果我将我的API网关作为一个微服务,而另一个将存储和管理用户的微服务作为一个微服务,那么我该如何GET /users/1以纯粹的事件驱动方式对诸如a之类的事物进行建模呢?

Answers:


9

在我之后重复:

REST和异步事件不是替代方案。它们是完全正交的。

您可以拥有一个或另一个,或同时拥有两个或两者都不存在。对于完全不同的问题领域,它们是完全不同的工具。实际上,通用的请求-响应通信绝对能够异步,事件驱动和容错


作为一个简单的示例,AMQP协议通过TCP连接发送消息。在TCP中,每个数据包都必须由接收方确认。如果数据包的发送方未收到该数据包的ACK,它将继续重新发送该数据包,直到被ACK确认或直到应用程序层“放弃”并放弃连接为止。显然,这是一个非容错的请求-响应模型,因为每个“数据包发送请求”都必须具有一个伴随的“数据包确认响应”,并且由于无法响应而导致整个连接失败。然而,AMQP是用于异步容错消息传递的标准化且被广泛采用的协议,它通过TCP进行通信!是什么赋予了?

这里发挥作用的核心概念是,可伸缩的松耦合容错消息传递是由您发送的消息而不是如何发送的消息定义的。换句话说,在应用层定义了松散耦合

让我们看一下直接与RESTful HTTP或与AMQP消息代理间接通信的两个方面。假设甲方希望将JPEG图像上传到乙方,乙方将对图像进行锐化,压缩或增强。甲方不需要立即处理该图像,但需要对其进行引用以供将来使用和检索。这是REST中可能采用的一种方法:

  • 甲方通过以下方式向乙方发送HTTP POST请求消息:Content-Type: image/jpeg
  • 乙方在等待时处理图像(如果图像较大,则处理很长时间),可能会做其他事情
  • 乙方将HTTP 201 Created响应消息发送到甲方,该消息的Content-Location: <url>头带有链接到已处理图像的标头
  • 甲方认为其工作已经完成,因为它现在已经参考了已处理的图像
  • 将来某个时候,当甲方需要经过处理的图像时,它会使用先前Content-Location标头中的链接来获取它

201 Created响应码告诉客户,不仅是他们的请求成功,这也创造了新的资源。在201响应中,Content-Location标头是指向创建的资源的链接。这是在RFC 7231第6.3.2和3.1.4.2节中指定的。

现在,让我们看看这种交互如何在AMQP之上的假设RPC协议上进行:

  • 甲方向AMQP消息代理(称为Messenger)发送一条包含该图像的消息以及将其路由到乙方进行处理的说明,然后使用该图像的某种地址响应甲方
  • 甲方等待,可能正在做其他事情
  • Messenger将甲方的原始消息发送给乙方
  • 乙方处理消息
  • 乙方向Messenger发送一条消息,其中包含处理后图像的地址和将消息路由至甲方的说明
  • Messenger向乙方发送来自乙方的消息,其中包含已处理的图像地址
  • 甲方认为其工作已经完成,因为它现在已经参考了已处理的图像
  • 将来某个时候,当甲方需要图片时,它会使用该地址(可能是通过向其他方发送消息)来检索图片

您在这里看到问题了吗?在这两种情况下,只有乙方处理完图片甲方才能获得图片地址。然而,甲方并不需要立即使用该图像,并且,即使处理完成,也可以毫不关心!

我们可以通过使乙方告诉A中的B AMQP的情况下很容易地解决这个接受的图像进行处理,给人的地址,其中图像完成处理后。然后,乙方可以在将来某个时候向A发送一条消息,指示图像处理已完成。AMQP消息救援!

除了猜测什么:您可以使用REST实现相同的目标。在AMQP示例中,我们将“这里是已处理的图像”消息更改为“正在处理图像,稍后可以获取”消息。要在RESTful HTTP中做到这一点,我们将再次使用202 Accepted代码Content-Location

  • 甲方通过以下方式向甲方发送HTTP POST消息:Content-Type: image/jpeg
  • 乙方立即发回一个202 Accepted包含某种“异步操作”内容的响应,该内容描述了处理是否完成以及在处理完成后该图像在哪里可用。还包括一个Content-Location: <link>标头,该标头在202 Accepted响应中是指向由响应主体表示的资源的链接。在这种情况下,这意味着它是我们异步操作的链接!
  • 甲方认为其工作已经完成,因为它现在已经参考了已处理的图像
  • 将来某个时候,甲方需要处理的图像时,它首先获取链接到标头中的异步操作资源Content-Location以确定处理是否完成。如果是这样,则甲方然后使用异步操作本身中的链接来获取已处理的图像。

唯一的区别是在AMQP模型中,乙方告诉甲方何时完成了图像处理。但是在REST模型中,甲方检查在实际需要图像之前是否进行了处理。这些方法具有同等的可伸缩性。随着系统的变大,异步AMQP和异步REST策略中发送的消息数量都以等效的渐近复杂性增加。唯一的区别是客户端发送的是额外的消息,而不是服务器。

但是REST方法还有很多技巧:动态发现和协议协商。考虑同步和异步REST交互是如何开始的。甲方向乙方发送了完全相同的请求,唯一的不同是乙方响应的特定成功消息。如果甲方希望选择图像处理是同步还是异步该怎么办?如果甲方不知道乙方是否能够进行异步处理怎么办?

嗯,HTTP实际上已经为此提供了一个标准化协议!它称为HTTP首选项,特别respond-async是RFC 7240第4.1节的首选项。如果甲方需要异步响应,则它包含Prefer: respond-async带有其初始POST请求的标头。如果乙方决定接受该请求,它将发回包含的202 Accepted响应Preference-Applied: respond-async。否则,乙方将简单地忽略Prefer标头并201 Created像往常一样发送回去。

这使甲方可以与服务器进行协商,以动态地适应与之通信的任何图像处理实现。此外,使用显式链接意味着甲方除乙方外无需了解其他任何方:没有AMQP消息代理,没有神秘的甲方知道如何将图像地址实际转换为图像数据,没有第二个B异步如果需要同时发出同步请求和异步请求,等等。它仅描述所需的内容,可选的内容,然后对状态码,响应内容和链接做出反应。加入Cache-Control标头提供有关何时保留本地数据副本的明确指示,现在服务器可以与客户端协商客户端可以保留其本地(甚至离线!)资源的资源。这就是您在REST中构建松耦合的容错微服务的方式。


1

当然,是否需要纯粹由事件驱动取决于您的特定情况。假设确实需要,那么可以通过以下方法解决问题:

通过侦听不同事件并在其有效负载中捕获信息来存储数据的本地只读副本。尽管这可以使您以适合特定应用程序的形式快速读取(更)该数据,但这也意味着您的数据最终将在各个服务之间保持一致。

GET /users/1使用这种方法进行建模,可以监听UserCreatedUserUpdated事件,并将有用的用户数据子集存储在服务中。然后,当您需要获取该用户的信息时,只需查询本地数据存储即可。

有一分钟,让我们假设公开/users/端点的服务没有发布任何事件。在这种情况下,您可以通过简单地缓存对发出的HTTP请求的响应来实现类似的目的,从而无需在某个时间范围内每个用户发出多个HTTP请求。


我明白。但是在这种情况下,如何对客户端进行错误处理(和报告)呢?
Tony E. Stark

我的意思是,如何处理UserCreated事件时向REST客户端报告错误(例如,重复的用户名或电子邮件或数据库中断)。
Tony E. Stark

这取决于您在哪里执行操作。如果您位于用户系统内部,则可以进行所有验证,写入那里的数据存储,然后发布事件。否则,我认为这是完全可以接受的执行标准HTTP请求到/users/端点,并允许该系统如果成功公布其活动,并响应与新实体的要求
安迪·亨特

0

对于事件源系统,当表示状态的某些内容(例如数据库或某些数据的聚合视图)发生更改时,异步方面通常会起作用。使用您的示例,对GET / api / users的调用可以简单地从服务中返回响应,该服务具有系统中用户列表的最新表示。在另一种情况下,对GET / api / users的请求可能导致服务使用事件流,因为用户的最后一个快照生成了另一个快照并仅返回结果。事件驱动的系统不一定完全是从请求到响应的异步,而是倾向于处于服务需要与其他服务交互的级别。异步返回GET请求通常没有任何意义,因此您可以简单地返回服务的响应,

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.