示例场景:
新用户是在一个组中创建的(我们需要确保他们的电子邮件是唯一的等)。
我们想发送一个事件UserCreated
(通过PubSub/Kafka/RabbitMQ),以便异步触发一些额外的业务逻辑:
- 发送确认电子邮件
- 通知组管理员有新用户加入了该组
我可以想象,我们可以将确认电子邮件视为即发即弃任务,因为它可以由用户再次触发。然而,对于通知组管理员来说,情况并非如此(失去这样的事件可能是不可接受的)。我们不能简单地将一个新用户保存到数据库,然后发布一个事件,因为它很容易失败(双重写入问题)。我们可以转向纯粹的事件驱动方法,但我不知道如何为它提供一个同步的REST API
问题
在应用程序中实现通知/事件系统时,人们在现实生活中如何处理双重写入问题?每个人真的在CDC中使用交易发件箱模式吗(例如Debezium)?这对我来说似乎有些过头了,但我真的想不出更好的方法来解决这个问题(除非你也能让API调用完全异步)。轮询数据库表(而不是CDC)是可以接受的解决方案吗?我们怎么能扩大规模?
如果你能分享你的经验或链接一些示例项目作为参考,那将是非常棒的!我能找到的大多数教程似乎完全忽略了这个问题。
以防万一,我主要使用Python(FastAPI),但用其他技术(如Java/NodeJS)分析项目对我来说应该不是什么大问题。
人们如何在现实生活中处理中的双写问题
- 没有分布式事务的可靠消息传递--Udi Dahan
- 没有人需要可靠的信息--Marc de Graauw
- 幂等方法——HTTP语义
如果您选择了分布式体系结构,那么您需要设计系统以考虑可用的消息传递保证。
一旦交货保证(由您选择)是不可能的/昂贵得令人望而却步。所以你可以在";最多一次";交货保证或";至少一次";交货保证。
至少一次意味着您的订阅者需要能够处理他们接收到具有相同语义的消息的两个(或多个)副本的情况(要么是因为他们可以检测到重复,要么是因为重复处理的成本是可以接受的)。
我可以想到将用户创建分为两步。
首先,执行在组中创建新用户的同步API请求,该请求立即返回某种";任务id";对于此请求。这只是意味着,好吧,我们收到了您创建新用户的请求,并将对其进行处理。任务id可用于获取有关此请求状态的信息。如果从这一点开始,在对客户端的响应中忘记了在另一端接收到创建用户的请求可能就足够了,并且任务id(或请求id)可能只具有系统内部相关性,例如用于关联、日志记录和后台的实际处理。
当您的后端收到此请求时,例如,您可以在队列上放置一个新命令(如创建用户命令),也可以将其实现为事件(如用户创建请求的事件)。注意,通过排队,我更倾向于引用排队的概念,因此这可以以不同的方式实现,例如,使用事务发件箱或某种持久消息队列解决方案。
如果你考虑在一个可靠的队列上有这个命令或事件(无论选择哪个实现),你现在可以尝试对这个"做出的反应;消息";异步,方法是在组中实际创建新用户。一旦发生这种情况,您就可以发布一些用户创建的事件。
用户创建的事件可以由单个组件订阅,如果在您的情况下,通过发送确认电子邮件和通知组管理员来做出反应是有意义的,甚至可以由单独的组件订阅。将其拆分为单独的订阅者可能会增加更多的实现工作量,但也会使您在处理具有不同性能和可靠性要求的同一事件时具有更大的灵活性。例如,正如您所提到的,在您的情况下,电子邮件确认并不像通知您的管理员那样重要。
create用户命令 (或分别为用户创建请求的事件)和用户创建的事件的实际处理然后以所需的弹性程度执行,以处理临时中断,并保证一切都在某个时刻发生,这使您具有eve-tual的特征一致性。
我已经遵循这种模式好几次了,尤其是在处理电子商务以实现订购流程时,客户(例如网络或移动前端)需要立即确认他们的请求已经同步完成,但对同一请求的实际处理和完成通知可以在稍后异步进行。
因此,您可以将创建新用户的请求视为类似于下订单,实际上创建新用户类似于处理订单,发送类似于向客户发送订单确认电子邮件的确认电子邮件,以及类似于向系统中的某个其他重要参与者通知新订单的管理员的通知。