消息队列(Message Queue)是一种在计算机系统或网络中用于存储和传递信息的方式。它通常用于在分布式系统或网络中的不同部分之间传递数据,允许应用程序之间进行异步通信。消息队列的实现方式有很多种,以下是其中一些常见的方法:
- 内存队列:消息队列通常在内存中实现。消息被添加到队列中,然后由另一个进程或线程从队列中取出并处理。这种方法简单且高效,但缺点是如果内存不足,可能会导致数据丢失。
- 文件系统队列:消息可以被写入到文件系统中,并按照某种顺序(如时间戳)进行排序。这种方法可以持久化存储消息,但需要额外的磁盘空间和I/O操作。
- 数据库队列:数据库通常被用来实现消息队列。消息可以被添加到数据库的特定表中,然后由另一个进程或线程从表中读取并处理。这种方法具有很高的可靠性和持久性,但可能涉及到额外的数据库查询和更新操作。
- 使用现有的消息队列服务:许多公司和研究机构提供了消息队列服务,如RabbitMQ、Apache Kafka等。这些服务通常提供了一组API和工具,以简化消息的生产、存储、消费和处理过程。
消息队列的实现方式可以根据具体需求和环境而变化。在实际应用中,可能需要根据系统规模、性能需求、可靠性要求等因素来选择最合适的实现方式。
另外,请注意,在使用消息队列时,需要注意确保可靠性和一致性,以避免数据丢失或不一致的情况。这可能需要使用一些额外的机制,如事务处理、重复确认等。
本地消息表(异步确保)
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:
基本思路就是:
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像 TCC那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
MQ 事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败
遗憾的是,RocketMQ并没有 .NET 客户端。有关 RocketMQ的更多消息,大家可以查看这篇博客优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 实现难度大,主流MQ不支持,没有.NET客户端,RocketMQ事务消息部分代码也未开源。