当前位置: 首页 > 后端技术 > Node.js

你知道微服务架构中的“发件箱模式”吗

时间:2023-04-03 20:29:25 Node.js

你知道微服务架构中的“发件箱模式”吗?双写是指您的应用程序需要在两个不同的系统中更改数据,例如需要将数据存储在数据库中并将事件发送到消息队列。您需要保证这两个操作都会成功。如果这两个操作之一失败,您的系统可能会变得不一致。那么对于这种情况有什么好的方法或者设计保证吗?本文将为大家分享一种“发件箱模式”,可以很好的避免此类问题。欢迎关注个人公众号『JAVASunrise』通信下单示例假设我们有一个OrderService类,在创建新订单时会调用该类,此时应该将订单实体保存在数据库中,将其发送到交付微服务一个事件,以便交付部门可以开始计划交付。您的代码可能如下所示:.executeWithoutResult(transactionStatus->{//保存订单orderRepository.save(id,description);});//发送消息deliveryMessageQueueService.send(message);}privateStringbuildMessage(intid,Stringdescription){//...}}复制代码可以看到我们在一个事务中保存了数据库中的订单,并且然后我们使用消息队列将事件发送到交付服务。这是双写的场景。如果这样写,会遇到什么问题?首先,如果我们保存了订单但是没有发送消息怎么办?送货服务永远不会收到消息。那么你可能会认为保存订单和发送消息可以在同一个事务中完成,即将deliveryMessageQueueService#send和orderRepository#save移到同一个事务中,如下图:transactionTemplate.executeWithoutResult(transactionStatus->{//保存订单orderRepository.save(id,description);//发送消息deliveryMessageQueueService.send(message);});实际上,在数据库事务中建立TCP连接是一种不好的做法,我们不应该这样做。有没有更好的办法?我们可以在与订单表相同的数据库中有一个表“发件箱”(在最简单的情况下,它可以有一个列“消息”和当前时间戳)。保存订单时,在同一个事务中,我们在“发件箱”表中保存一条消息。消息一发送,我们就可以使用以下代码将其从发件箱表中删除:outboxId=UUID.randomUUID();Stringmessage=buildMessage(id,description);transactionTemplate.executeWithoutResult(transactionStatus->{//保存订单orderRepository.save(id,description);//保存到outboxRepository.save(newOutboxEntity(outboxId,message));});deliveryMessageQueueService.send(消息);//deleteoutboxRepository.delete(outboxId);}privateStringbuildMessage(intid,Stringdescription){//...}}复制代码如您所见,我们在一次事务中将订单和发件箱实体保存在数据库中。然后我们发送消息,如果成功,我们删除消息。如果deliveryMessageQueueService#send失败会怎样?(例如,您的应用程序终止或消息队列或数据库不可用)。在这种情况下,outboxRepository#delete将不会运行,我们必须重试发送消息。可以使用将在后台运行的计划任务来完成,该任务将尝试发送在表发件箱中显示超过X秒(比如10秒)的消息,如下面的代码所示。@ServicepublicrecordOutboxRetryTask(IOutboxRepositoryoutboxRepository,IDeliveryMessageQueueServicedeliveryMessageQueueService){@Scheduled(fixedDelayString="10000")publicvoidretry(){ListoutboxEntities=outboxRepository.findAllBefore(Instant)conds(对于。{deliveryMessageQueueService.send(outbox.message());outboxRepository.delete(outbox.id());}}}这里可以看到我们每10秒运行一个任务,并发送一条之前没有发送过的消息。如果消息成功发送到消息队列,但发件箱实体没有从数据库中删除(例如因为数据库问题),那么下一次这个后台任务将尝试再次将这条消息发送到消息队列。但这也意味着我们消息的消费者必须是幂等的,因为他们可能会多次收到同一条消息发件箱模式通过上面的例子,我们可以抽象出“发件箱模式”在数据库中添加一个额外的发件箱表来存储事件需要发送的。将直接发送事件的步骤替换为将事件存储在数据库发件箱表中。程序启动一个作业,不断抓取发件箱表中的记录,通过推送线程完成不同的任务。业务推送最终删除发送成功的记录。提醒消费者做幂等处理。总结发件箱模式虽然听起来很简单,但在平时的开发中可能会被忽略。如果还是看不懂,我们可以类比为生活场景。发件人只需要写信,放入收件箱,然后就不用管了。寄件人会到收件箱取信,并根据信件中要寄出的地址将信件送达目的地。这样做的好处是,寄信人写完信后,不需要等收信人有空再寄信,只需要把信扔进发件箱就可以了。