开发现代应用程序意味着为云进行开发,将正常运行时间、可扩展性、地理分布和低延迟放在关注的前沿。这导致了基于事件驱动微服务的应用程序架构的广泛采用。将应用程序的元素分解为微服务允许我们(例如)独立扩展不同的服务。这只是为云构建应用程序的最有效方式。
然而,采用事件驱动的微服务也带来了一些挑战。有这么多不同的服务同时运行,它们之间的通信可能会成为一个挑战。
例如,如果一个微服务需要向另一个服务发送一些数据或请求,但另一个服务很忙怎么办?如果这两个服务必须相互等待,我们就会失去一些微服务架构的效率。
这个问题的一种解决方案是消息队列。
什么是消息队列?
消息队列本质上是一个中间存储队列,允许微服务之间进行异步通信。消息队列允许一个服务向另一个服务发送“消息”,即使另一个服务还没有准备好接收它。
例如,在下图中,服务 1 可能有消息需要发送到服务 2。使用消息队列,它可以根据需要发送它们并继续操作,而不管服务 2 是否准备好接收它们。然后将这些消息存储在队列中,直到服务 2 检索它们。

这使得整个系统更高效且更易于扩展。通过解耦服务 1 和 2,我们可以使它们彼此运行而无需等待对方,同时仍然允许它们通过中间消息队列进行异步通信。
这也有助于我们降低级联故障的风险。如果我们的服务必须同步通信,而一个服务失败了,其他尝试与该服务通信的服务也可能失败,那么与这些服务通信的服务也会失败,以此类推。
但是,当我们想要将相同的数据存储在两个地方(例如消息队列和数据库)时,使用消息队列可能会导致一些偷偷摸摸的问题。
什么是双写问题?
有时,我们需要一项服务将同一条数据发送到两个存储位置,同时确保它们之间的一致性。例如,当发生特定事件时,我们可能希望使用相同的信息更新数据库和消息队列(或消息队列系统,如 Apache Kafka)。这称为双重写入——我们将相同的数据写入两个不同的地方。
但是,如果这两个更新中的一个成功而另一个失败,会发生什么?这是双写问题;如果我们试图在分布式系统中更新两个单独的存储解决方案,而没有采取任何额外措施来确保它们之间的一致性,最终我们将得到一个不一致的状态。

双重写入问题很容易被我们忽视,因为只要我们的数据库和消息队列(例如)都正常运行,就不会出现不一致的情况。当出现不一致时,我们可能并不总是注意到它们。
然而,从长远来看,忽视双写问题是不可持续的。最终,故障、错误或中断将导致不一致,从而对您的应用程序产生负面影响——而且很可能对您的业务产生负面影响。
我们还询问了 Twitter 如何简洁地定义双重写入问题,并得到了一些很好的答案,包括:


事务发件箱模式
解决此问题的一种方法是一种称为事务发件箱模式的设计方法。这种方法需要一个事务数据库,例如 CockroachDB。
它是这样工作的:我们不是将数据发送到两个单独的位置,而是发送一个事务,它将在数据库中存储两个单独的数据副本。一个副本存储在相关的数据库表中,另一个副本存储在一个发件箱表中,我们随后将从该表中更新另一个存储位置。例如,我们可以将发件箱连接到 Kafka 或其他一些消息队列系统。
换句话说,这是一个两步过程:首先,我们使用单个事务更新数据库的两个部分(相关表和我们的事务发件箱),这使我们能够保证两个更新都提交或都不提交。
然后,我们将更新从事务发件箱推送到消息队列。如果发件箱中的消息未能进入消息队列,则不会丢失任何数据或一致性。因为数据已经安全地存储在数据库中,我们可以简单地重试。
但是,这种方法确实需要额外的服务或工作:我们需要将事件从数据库发件箱移动到消息队列。那么我们该怎么做呢?
我们刚刚为 Cockroach University 添加了一门新的免费课程,涵盖了您需要了解的所有内容。它被称为Java 开发人员的事件驱动架构。本课程涵盖如何将事务发件箱模式构建到您自己的应用程序中。
文章来源:https://dzone.com/articles/message-queuing-and-the-database-solving-the-dual




