`

使用kafka消息队列解决分布式事务(可靠消息最终一致性方案-本地消息服务)(转)

 
阅读更多

微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务

不同于单一架构应用(Monolith), 分布式环境下, 进行事务操作将变得困难, 因为分布式环境通常会有多个数据源, 只用本地数据库事务难以保证多个数据源数据的一致性. 这种情况下, 可以使用两阶段或者三阶段提交协议来完成分布式事务.但是使用这种方式一般来说性能较差, 因为事务管理器需要在多个数据源之间进行多次等待. 有一种方法同样可以解决分布式事务问题, 并且性能较好, 这就是我这篇文章要介绍的使用事件,本地事务以及消息队列来实现分布式事务.

我们从一个简单的实例入手. 基本所有互联网应用都会有用户注册的功能. 在这个例子中, 我们对于用户注册有两步操作: 
1. 注册成功, 保存用户信息.
2. 需要给用户发放一张代金券, 目的是鼓励用户进行消费.
如果是一个单一架构应用, 实现这个功能非常简单: 在一个本地事务里, 往用户表插一条记录, 并且在代金券表里插一条记录, 提交事务就完成了. 但是如果我们的应用是用微服务实现的, 可能用户和代金券是两个独立的服务, 他们有各自的应用和数据库, 那么就没有办法简单的使用本地事务来保证操作的原子性了. 现在来看看如何使用事件机制和消息队列来实现这个需求.(我在这里使用的消息队列是kafka, 原理同样适用于ActiveMQ/RabbitMQ等其他队列)

我们会为用户注册这个操作创建一个事件, 该事件就叫做用户创建事件(USER_CREATED). 用户服务成功保存用户记录后, 会发送用户创建事件到消息队列, 代金券服务会监听用户创建事件, 一旦接收到该事件, 代金券服务就会在自己的数据库中为该用户创建一张代金券. 好了, 这些步骤看起来都相当的简单直观, 但是怎么保证事务的原子性呢? 考虑下面这两个场景:
1. 用户服务在保存用户记录, 还没来得及向消息队列发送消息之前就宕机了. 怎么保证用户创建事件一定发送到消息队列了?
2. 代金券服务接收到用户创建事件, 还没来得及处理事件就宕机了. 重新启动之后如何消费之前的用户创建事件?
这两个问题的本质是: 如何让操作数据库和操作消息队列这两个操作成为一个原子操作. 不考虑2PC, 这里我们可以通过事件表来解决这个问题. 下面是类图. 

EventPublish是记录待发布事件的表. 其中:
id: 每个事件在创建的时候都会生成一个全局唯一ID, 例如UUID.
status: 事件状态, 枚举类型. 现在只有两个状态: 待发布(NEW), 已发布(PUBLISHED).
payload: 事件内容. 这里我们会将事件内容转成json存到这个字段里.
eventType: 事件类型, 枚举类型. 每个事件都会有一个类型, 比如我们之前提到的创建用户USER_CREATED就是一个事件类型.
EventProcess是用来记录待处理的事件. 字段与EventPublish基本相同.

我们首先看看事件的发布过程. 下面是用户服务发布用户创建事件的顺序图. 
1. 用户服务在接收到用户请求后开启事务, 在用户表创建一条用户记录, 并且在EventPublish表创建一条status为NEW的记录, payload记录的是事件内容, 提交事务.
2. 用户服务中的定时器首先开启事务, 然后查询EventPublish是否有status为NEW的记录, 查询到记录之后, 拿到payload信息, 将消息发布到kafka中对应的topic.
发送成功之后, 修改数据库中EventPublish的status为PUBLISHED, 提交事务.

下面是代金券服务处理用户创建事件的顺序图. 
1. 代金券服务接收到kafka传来的用户创建事件(实际上是代金券服务主动拉取的消息, 先忽略消息队列的实现), 在EventProcess表创建一条status为NEW的记录, payload记录的是事件内容, 如果保存成功, 向kafka返回接收成功的消息.
2. 代金券服务中的定时器首先开启事务, 然后查询EventProcess是否有status为NEW的记录, 查询到记录之后, 拿到payload信息, 交给事件回调处理器处理, 这里是直接创建代金券记录. 处理成功之后修改数据库中EventProcess的status为PROCESSED, 最后提交事务.

回过头来看我们之前提出的两个问题:
1. 用户服务在保存用户记录, 还没来得及向消息队列发送消息之前就宕机了. 怎么保证用户创建事件一定发送到消息队列了?
根据事件发布的顺序图, 我们把创建事件和发布事件分成了两步操作. 如果事件创建成功, 但是在发布的时候宕机了. 启动之后定时器会重新对之前没有发布成功的事件进行发布. 如果事件在创建的时候就宕机了, 因为事件创建和业务操作在一个数据库事务里, 所以对应的业务操作也失败了, 数据库状态的一致性得到了保证.
2. 代金券服务接收到用户创建事件, 还没来得及处理事件就宕机了. 重新启动之后如何消费之前的用户创建事件?
根据事件处理的顺序图, 我们把接收事件和处理事件分成了两步操作. 如果事件接收成功, 但是在处理的时候宕机了. 启动之后定时器会重新对之前没有处理成功的事件进行处理. 如果事件在接收的时候就宕机了, kafka会重新将事件发送给对应服务.

通过这种方式, 我们不用2PC, 也保证了多个数据源之间状态的最终一致性.
和2PC/3PC这种同步事务处理的方式相比, 这种异步事务处理方式具有异步系统通常都有的优点:
1. 事务吞吐量大. 因为不需要等待其他数据源响应.
2. 容错性好. A服务在发布事件的时候, B服务甚至可以不在线.
缺点:
1. 编程与调试较复杂.
2. 容易出现较多的中间状态. 比如上面的例子, 在用户服务已经保存了用户并发布了事件, 但是代金券服务还没来得及处理之前, 用户如果登录系统, 会发现自己是没有代金券的. 这种情况可能在有些业务中是能够容忍的, 但是有些业务却不行. 所以开发之前要考虑好.

另外, 上面的流程在实现的过程中还有一些可以改进的地方:
1. 定时器在更新EventPublish状态为PUBLISHED的时候, 可以一次批量更新多个EventProcess的状态.
2. 定时器查询EventProcess并交给事件回调处理器处理的时候, 可以使用线程池异步处理, 加快EventProcess处理周期.
3. 在保存EventPublish和EventProcess的时候同时保存到Redis, 之后的操作可以对Redis中的数据进行, 但是要小心处理缓存和数据库可能状态不一致问题.
4. 针对Kafka, 因为Kafka的特点是可能重发消息, 所以在接收事件并且保存到EventProcess的时候可能报主键冲突的错误(因为重复消息id是相同的), 这个时候可以直接丢弃该消息.

分享到:
评论

相关推荐

    分布式事务-可靠消息的服务的设计与实现(消息服务子系统)

    本文将深入探讨“分布式事务-可靠消息的服务的设计与实现”这一主题,主要围绕消息服务子系统,结合提供的资料,包括“微服务架构的分布式事务解决方案.pdf”、“rc_pay_dubbo_message.sql”数据库脚本、“龙果学院-...

    分布式事务最终一致性常用方案.docx

    【分布式事务最终一致性常用方案】 在当今的信息化时代,无论是企业级应用还是互联网应用,保持数据的最终一致性已经成为系统设计的重要挑战。随着分布式系统的广泛采用,数据一致性问题变得更为复杂,因为没有一种...

    【进阶之路】可靠消息最终一致性解决方案.docx

    可靠消息最终一致性解决方案 可靠消息最终一致性解决方案是指在分布式系统中,如何确保消息的可靠传输和一致性处理。本解决方案涵盖了消息队列、事务处理、幂等性设计、 compensation 机制等多方面的技术细节。 一...

    基于本地消息表的分布式事务处理-tcc.zip

    在本压缩包"基于本地消息表的分布式事务处理-tcc.zip"中,包含的项目"tcc-master"可能是一个关于如何结合这两种方法实现高效、可靠的分布式事务解决方案的示例。 本地消息表是一种在分布式环境中保证事务一致性的...

    Java-25-通用设计-分布式事务-消息队列.rar

    分布式事务和消息队列在Java开发中是两个非常重要的概念,尤其在构建大规模、高并发、高可用的系统时更是不可或缺。本资料包"Java-25-通用设计-分布式事务-消息队列.rar"主要涵盖了这两个主题,下面将详细阐述它们的...

    使用KafkaStreams和SpringBoot实现微服务Saga分布式事务-Piotr.pdf

    通过这种方式,Kafka Streams和Spring Boot的组合为微服务架构提供了灵活且可靠的分布式事务解决方案,允许在保持服务独立性的同时保证数据一致性。然而,这种模式也需要注意并发控制、错误处理和回滚策略的复杂性,...

    Distributed-transaction:基于消息队列最终一致性实现的分布式事务demo

    总之,"Distributed-transaction:基于消息队列最终一致性实现的分布式事务demo"项目展示了如何在Java环境中利用消息队列解决分布式一致性问题。理解并掌握这种实现方式,对于提升大型分布式系统的可靠性和性能具有...

    分布式事务演示-distributed-transaction-demo.zip

    在这个"分布式事务演示-distributed-transaction-demo.zip"中,我们很显然会看到一个用于演示如何在分布式环境中实现事务一致性的项目。这个项目名为"distributed-transaction-demo-master",可能是用Git管理的一个...

    【笔记】分布式消息通信之Kafka的实现原理1

    *弱一致性事务模型:Kafka可以用于弱一致性事务模型场景,例如数据的最终一致性等等。 分布式消息队列是解决分布式系统之间消息传递问题的关键技术之一。Kafka是Apache开源的分布式消息队列系统,具有高吞吐量、高...

    分布式事务的四种解决方案,值得参考.docx

    通常配合消息队列使用,以实现最终一致性。 **工作流程:** 1. **写入本地消息表**:在完成业务操作后,将消息写入本地消息表。 2. **消息转发**:将本地消息表中的消息转发到消息队列,如Kafka。 3. **消息处理**...

    11 支付订单库存消息Mq交互(分布式事务)1

    本文将探讨一种常见的解决方案——使用消息队列,特别是针对支付订单库存消息交互的分布式事务处理。首先,我们来看一下消息队列在分布式事务中的作用。 分布式事务是指跨越多个独立的数据库或服务的事务操作,其...

    JAVA面试题(Zookeeper、消息队列、分布式等最新的也有)

    在Java面试中,候选人需要展示对分布式系统设计原则的理解,如CAP定理(一致性、可用性和分区容错性之间的权衡),以及BASE理论(基本可用、软状态和最终一致性)。还要熟悉分布式服务发现、负载均衡、分布式事务...

    基于dubbo的分布式事务实现demo源码.zip

    【分布式事务】是大型分布式系统中的重要组成部分,它确保在多个操作之间的一致性和完整性,即使这些操作分布在不同的网络节点上。在微服务架构中,由于服务间的相互调用变得频繁,分布式事务处理就显得尤为关键。...

    分布式事务消息技术介绍.pptx

    在我们的实践中,根据业务场景和需求,我们可能需要定制适合的事务消息解决方案,结合现有的成熟产品,如RabbitMQ、Kafka等消息中间件,以及分布式事务管理框架如Seata、Atomikos等,来构建高效且可靠的分布式事务...

    kafka队列下载

    标题中的“kafka队列下载”指的是Apache Kafka的下载过程,Kafka是一个分布式流处理平台,常用于构建实时数据管道和流应用。描述中的“kafka_2.12-0.11.0.0”是Kafka的一个特定版本,它表明是在使用Scala 2.12编译的...

    微服务架构下的分布式事务处理

    8. **事件驱动架构**:利用事件来协调不同服务之间的状态变更,通过发布订阅模式实现事务处理,比如使用消息队列(RabbitMQ、Kafka等)作为中间件,服务间通过事件通信,达到异步解耦和最终一致性。 9. **分布式...

    katcat:基于消息队列的数据最终一致性

    总的来说,"katcat"项目展示了如何通过Java和消息队列来解决分布式环境中的数据一致性问题,实现了高效、可靠的最终一致性。这种设计模式在现代互联网应用中具有广泛的应用,尤其是在大数据处理、实时分析和微服务...

    微服务数据一致性常见问题及解决方案共42页.pdf.zip

    微服务架构下的数据一致性是确保系统整体正确性的关键,而这也是本资料——“微服务数据一致性常见问题及解决方案共42页.pdf”所关注的核心议题。 1. 数据一致性挑战 在微服务环境中,每个服务都有自己的数据库,...

Global site tag (gtag.js) - Google Analytics