写点什么

发件箱模式:打造微服务可靠消息传输

作者:俞凡
  • 2025-04-29
    上海
  • 本文字数:2022 字

    阅读完需:约 7 分钟

本文介绍了在微服务系统中实现可靠消息传递以及分布式事务的发件箱模式,该设计模式实现了消息生产者和消费者的解耦,了解这一模式可以帮助我们设计出容错性、可靠性更高的系统。原文:Outbox Pattern For Reliable Microservices Messaging



开发微服务以及其他分布式系统都不容易,任何问题都有可能发生,甚至还有关于这方面的研究论文


作为工程师,减少出错的可能性也应该是你的目标之一,本文将尝试使用发件箱模式(Outbox pattern) 来实现这一点。


如何在分布式系统中实现组件之间的可靠通信?


发件箱模式是此类问题的一种优雅解决方案,该方案让我们能够实现事务性保证,并至少向外部系统传递一次消息。


让我们看看发件箱模式如何解决这个问题,以及如何实现。

发件箱模式解决了什么问题?

当然,要理解发件箱模式解决了什么问题,我们先给出一个问题。


下面是一个用户注册流程的示例,有几件事正在发生:


  • User 保存到数据库

  • User 发生欢迎邮件

  • 向消息总线发布 UserRegisteredEvent


public async Task RegisterUserAsync(User user, CancellationToken token){    _userRepository.Insert(user);
await _unitOfWork.SaveChangesAsync(token);
await _emailService.SendWelcomeEmailAsync(user, token);
await _eventBus.PublishAsync(new UserRegisteredEvent(user.Id), token);}
复制代码


所有操作都在常规路径中按序完成,没有任何问题,一切都很好。


但如果其中任何一个操作失败了怎么办?


  • 数据库不可用,保存 User 失败

  • 邮件服务中断,无法发送邮件

  • 向服务总线发布事件没有成功


另外,想象一下这种情况:你已经将 User 保存到数据库中,并向他发送了欢迎邮件,但未能成功发布 UserRegisteredEvent 来通知其他服务。怎么才能从这种情况中恢复过来?


发件箱模式可以帮你自动更新数据库并将消息发送到消息总线。

实现发件箱模式

首先在数据库中引入一个表示发件箱(Outbox) 的新表,可以将这个表称为 OutboxMessages,用于存储需要传递的所有消息。现在,我们不再直接向外部服务发出请求,而是简单的将消息作为新行存储在发件箱表中,消息通常以 JSON 格式存储。


然后引入后台进程,定期轮询 OutboxMessages 表。如果发现有未处理的消息,就发布该消息并标记为已发送。如果由于某种原因造成消息发布失败,就在下一次执行时重试


注意,通过重试,现在实现了至少一次消息传递(at-least-once message delivery)。对于常规路径,消息只发布一次,而在重试的情况下,则会发布多次。


我们现在可以基于发件箱模式重写上面的 RegisterUserAsync 方法:


public async Task RegisterUserAsync(User user, CancellationToken token){    _userRepository.Insert(user);
_outbox.Insert(new UserRegisteredEvent(user.Id));
await _unitOfWork.SaveChangesAsync(token);}
复制代码


发件箱与工作单元在同一个事务中,因此可以将 User 自动保存到数据库中,并持久化 OutboxMessage。如果保存到数据库失败,则回滚整个事务,并且不会向消息总线发送任何消息。


由于现在将 UserRegisteredEvent 的发布转移到了工作进程,因此需要添加一个处理程序,以便向用户发送欢迎邮件。下面是 SendWelcomeEmailHandler 类的一个例子:


public class SendWelcomeEmailHandler : IHandle<UserRegisteredEvent>{    private readonly IUserRepository _userRepository;    private readonly IEmailService _emailService;
public SendWelcomeEmailHandler( IUserRepository userRepository, IEmailService emailService) { _userRepository = userRepository; _emailService = emailService; }
public async Task Handle(UserRegisteredEvent message) { var user = await _userRepository.GetByIdAsync(message.UserId);
await _emailService.SendWelcomeEmailAsync(user); }}
复制代码

发件箱模式架构图

下面是引入发件箱后的系统架构图,可以在数据库中看到 Outbox 表,因此可以将消息与相关实体一起通过同一事物存储到 Outbox 表中。


延伸阅读

通过本文,你应该对发件箱模式以及它解决的问题有了很好的理解。如果需要在分布式系统中实现可靠消息传递,那么发件箱模式是一个很好的解决方案。


如果需要了解发件箱模式的更多实现细节,可以观看以下油管视频:





你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

发布于: 2025-04-29阅读数: 2
用户头像

俞凡

关注

公众号:DeepNoMind 2017-10-18 加入

俞凡,Mavenir Systems研发总监,关注高可用架构、高性能服务、5G、人工智能、区块链、DevOps、Agile等。公众号:DeepNoMind

评论

发布
暂无评论
发件箱模式:打造微服务可靠消息传输_架构_俞凡_InfoQ写作社区