本文翻译自 How To Implement Synchronous Interactions Between Microservices (opens new window),原作者:OLEKSII

在之前的文章《如何理解微服务架构》中,我们回顾了微服务架构的优缺点。在采用这种架构构建的应用程序中,服务之间的交互可能是一个挑战。实现这种交互有不同的方法。在本篇文章中,我们将回顾同步交互(HTTP/HTTPS)。

# 同步微服务交互

让我们回顾一下上面的示例。在本例中,我们采用的是每个服务一个数据库模式。这意味着服务不共享数据库。每个服务都有自己的数据库或根本没有数据库。

# 同步微服务交互示例

用户访问酒店预订网站并选择入住日期。Hotel Service(酒店服务) 只返回一个包含尽可能少的酒店客房信息的列表。然后,用户选择一个特定的酒店客房。因此,我们需要输入酒店客房和入住日期。预计将返回完整的酒店信息:

  • 酒店摘要。例如,名称、地址、评级、描述、可用服务、房间描述等。Hotel Service(酒店服务)使用自己的数据库获取所需数据。
  • 酒店附近的景点。可以是餐厅、剧院、电影院、博物馆等。这些信息通常不会更改。因此,这些信息可以保存在 Hotel Attractions Service(酒店景点服务)数据库中。
  • 酒店附近的活动。这些是不时发生的事件。第三方事件服务可用于获取入住期间酒店附近的活动(如节日、音乐会、展览等)。

总之,响应包含来自三个不同服务的数据,它们通过 HTTP/HTTPS 进行同步通信。

# 同步微服务模式

乍一看,从Hotel Service(酒店服务)Hotel Attractions Service(酒店景点服务) 以及从 Hotel Attractions Service(酒店景点服务)第三方事件服务 发出请求似乎就足够了。然而,这种微服务交互方法存在一些常见问题。

# 服务发现

第一个问题是 Hotel Service(酒店服务) 不能直接调用 Hotel Attractions Service(酒店景点服务)(我们不能硬编码服务 URL)。为了提供稳定的服务,同一服务的多个实例应并行运行。实例的数量可根据负载情况增减,不健康的实例会被健康的实例取代。因此,一些基础设施组件应负责活动服务及其活动健康实例的列表。服务发现 就是用于此目的。有关运行服务实例的信息保存在服务注册中。

例如,在 Spring 体系中就是 Eureka。

# 客户端负载均衡

使用服务注册的第一个选项是客户端负载均衡

这意味着什么?我们的服务注册提供有关健康实例的信息。Hotel Service(酒店服务)*可与服务注册交互,以查找 Hotel Attractions Service(酒店景点服务) 的活动服务实例,并使用某种算法来选择要使用的实例(如:循环演算法)。

调用将被执行到选定的服务实例。

在 Spring 中 通常是 Ribbon。

# 网关

使用服务注册的第二种方法是使用网关(如文章开始的图片所示)。

在这种情况下,一个特殊组件负责将请求重定向到所需的服务实例。网关与服务注册中心合作,了解哪些服务及其实例可以使用。

例如,它可以这样工作:

  • 用户(个人或其他服务)调用,如: GET https://hotelbookingapplication.com/api/hotel-attractions?lattitude=15.45656&longitude=-18.99908 等服务。
  • hotel-attractions 路径表明服务信息。
  • 网关查找有关 hotel-attractions 服务的信息。
  • 找到正在运行的 hotel-attractions 服务实例的 URL。
  • 例如,使用循环演算法选择一个实例并发出请求。

在 Spring 中通常是 Zuul。

# 断路器

让我们回顾一下同步微服务交互的另一个问题。

在我们的示例中,将执行下一个请求序列:

  • Hotel Service(酒店服务)向酒店景点服务发出请求。
  • Hotel Attractions Service(酒店景点服务)第三方事件服务发出请求。

如您所见,该请求取决于三项服务。此外,第三方事件服务不在我们的控制范围之内。

如果其中一个服务运行缓慢,会发生什么情况?它会对整个应用程序产生什么影响?

问题在于,即使一个服务出现问题,也会对整个系统的性能产生负面影响。

试想一下第三方事件服务宕机时的情况。在这种情况下,用户甚至无法看到酒店客房信息。没错,第三方事件服务提供的酒店附近活动信息对某些人来说可能很重要。但是,这并不重要。没有这些信息,人们仍然可以预订酒店房间。

如果第三方事件服务性能不佳,情况就会更糟。

想象以下的情景:

  • Hotel Service(酒店服务)提出申请。
  • Hotel Service(酒店服务)Hotel Attractions Service(酒店景点服务) 发出请求。
  • Hotel Attractions Service(酒店景点服务)第三方事件服务发出请求。
  • 第三方事件服务在 30 秒内响应。

因此:

  • Hotel Attractions Service(酒店景点服务)阻塞线程,等待第三方事件服务的响应。
  • Hotel Service(酒店服务)阻塞线程,等待Hotel Attractions Service(酒店景点服务) 的响应。

在单个请求的情况下,并不会发生什么严重的情况:用户只需等待大约 30 秒的响应。

然而,这里有两个主要问题:

  • 不是每个用户都会等待 30 秒。
  • 有关酒店附近活动的信息并不重要。

当我们看到真实情况时,情况会变得更糟:用户提出的请求不止一个。很多用户都在寻找酒店房间。

这意味着这 30 秒的等待时间会对整个系统的性能产生负面影响:

  • Hotel Attractions Service(酒店景点服务)资源已耗尽。
  • Hotel Service(酒店服务)更加等待 Hotel Attractions Service(酒店景点服务)的响应。
  • Hotel Service(酒店服务)的资源耗尽。
  • Hotel Service(酒店服务)无法处理与既未使用Hotel Attractions Service(酒店景点服务)也未使用第三方事件服务的酒店房间列表相关的请求。

解决方案非常简单。使用断路器。

  • 为每个请求设置超时。
  • 如果一个服务在超时前未收到另一个服务的响应,则初始服务将以默认值响应。
  • 在某个配置的时间段内,不会向有问题的服务发出请求。始终使用默认响应。
  • 一段时间后,再次向相关服务发出请求。

让我们回顾一下第三方活动服务出现问题时的情况(同样适用于酒店景点服务)。

  • Hotel Service(酒店服务)Hotel Attractions Service(酒店景点服务)发出请求。
  • Hotel Service(酒店服务)向第三方活动服务发出请求,等待响应 1 秒钟。
  • 如果收到成功响应,则一切正常。用户收到所有数据。
  • 如果响应不成功,用户将不会收到有关事件的信息。后续请求(例如下一个 5 秒)将跳过对有问题的第三方事件服务的调用。

在 Spring 中,典型的是 Hystrix。

# 缓存

为了优化性能特征,在某些情况下可以使用缓存。

如果可能,可以出于以下原因使用缓存:

  • 跳过对目标服务的调用。
  • 在目标服务出现问题时,可以向用户返回一些默认数据(可能是过时或不准确的数据)。

不过,缓存失效可能是一个真正的挑战。

# 总结

本文介绍了同步微服务交互面临的各种挑战。这种方法在某些情况下是适用的。不过,还有另一种微服务通信方式。下一篇文章将讨论异步微服务交互。