你知道微服务架构中的“发件箱模式”吗?双写是指您的应用程序需要在两个不同的系统中更改数据,例如需要将数据存储在数据库中并将事件发送到消息队列。您需要保证这两个操作都会成功。如果这两个操作之一失败,您的系统可能会变得不一致。那么对于这种情况有什么好的方法或者设计保证吗?本文将为大家分享一种“发件箱模式”,可以很好的避免此类问题。欢迎关注个人公众号『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(){List
