`

使用消息队列解决数据最终一致性

 
阅读更多

http://www.cnblogs.com/LBSer/p/4715395.html

  前阵子从支付宝转账1万块钱到余额宝,这是日常生活的一件普通小事,但作为互联网研发人员的职业病,我就思考支付宝扣除1万之后,如果系统挂掉怎么办,这时余额宝账户并没有增加1万,数据就会出现不一致状况了。

  上述场景在各个类型的系统中都能找到相似影子,比如在电商系统中,当有用户下单后,除了在订单表插入一条记录外,对应商品表的这个商品数量必须减1吧,怎么保证?!在搜索广告系统中,当用户点击某广告后,除了在点击事件表中增加一条记录外,还得去商家账户表中找到这个商家并扣除广告费吧,怎么保证?!等等,相信大家或多或多少都能碰到相似情景。

  这些问题本质上都可以抽象为:当一个表数据更新后,怎么保证另一个表的数据也必须要更新成功。

1 本地事务

  还是以支付宝转账余额宝为例,假设有

  支付宝账户表:A(id,userId,amount)  

  余额宝账户表:B(id,userId,amount)

  用户的userId=1;

  从支付宝转账1万块钱到余额宝的动作分为两步:

  1)支付宝表扣除1万:update A set amount=amount-10000 where userId=1;

  2)余额宝表增加1万:update B set amount=amount+10000 where userId=1;

  如何确保支付宝余额宝收支平衡呢?有人说这个很简单嘛,可以用事务解决。

1
2
3
4
5
Begin transaction
         update set amount=amount-10000 where userId=1;
         update set amount=amount+10000 where userId=1;
End transaction
commit;

  非常正确!如果你使用spring的话一个注解就能搞定上述事务功能。

1
2
3
4
5
@Transactional(rollbackFor=Exception.class)
    public void update() {
        updateATable(); //更新A表
        updateBTable(); //更新B表
    }

  如果系统规模较小,数据表都在一个数据库实例上,上述本地事务方式可以很好地运行,但是如果系统规模较大,比如支付宝账户表和余额宝账户表显然不会在同一个数据库实例上,他们往往分布在不同的物理节点上,这时本地事务已经失去用武之地。

  既然本地事务失效,分布式事务自然就登上舞台。

2 分布式事务—两阶段提交协议

  两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。一般分为协调器C和若干事务执行者Si两种角色,这里的事务执行者就是具体的数据库,协调器可以和事务执行器在一台机器上。

  1) 我们的应用程序(client)发起一个开始请求到TC;

  2) TC先将<prepare>消息写到本地日志,之后向所有的Si发起<prepare>消息。以支付宝转账到余额宝为例,TC给A的prepare消息是通知支付宝数据库相应账目扣款1万,TC给B的prepare消息是通知余额宝数据库相应账目增加1w。为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证 的效果,如果没有本地日志(凭证),容易死无对证;

  3) Si收到<prepare>消息后,执行具体本机事务,但不会进行commit,如果成功返回<yes>,不成功返回<no>。同理,返回前都应把要返回的消息写到日志里,当作凭证。

  4) TC收集所有执行器返回的消息,如果所有执行器都返回yes,那么给所有执行器发生送commit消息,执行器收到commit后执行本地事务的commit操作;如果有任一个执行器返回no,那么给所有执行器发送abort消息,执行器收到abort消息后执行事务abort操作。

  注:TC或Si把发送或接收到的消息先写到日志里,主要是为了故障后恢复用。如某一Si从故障中恢复后,先检查本机的日志,如果已收到<commit >,则提交,如果<abort >则回滚。如果是<yes>,则再向TC询问一下,确定下一步。如果什么都没有,则很可能在<prepare>阶段Si就崩溃了,因此需要回滚。

  现如今实现基于两阶段提交的分布式事务也没那么困难了,如果使用java,那么可以使用开源软件atomikos(http://www.atomikos.com/)来快速实现。

  不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差,根本不适合高并发的系统。为什么?

  1)两阶段提交涉及多次节点间的网络通信,通信时间太长!

  2)事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多!

  正是由于分布式事务存在很严重的性能问题,大部分高并发服务都在避免使用,往往通过其他途径来解决数据一致性问题。

3 使用消息队列来避免分布式事务

  如果仔细观察生活的话,生活的很多场景已经给了我们提示。

  比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,往往是给你一张小票,然后让你拿着小票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强(并发量更高)。

  还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当支付宝账户扣除1万后,我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加 1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性

3.1 如何可靠保存凭证(消息)

  有两种方法:

3.1.1 业务与消息耦合的方式

  支付宝在完成扣款的同时,同时记录消息数据,这个消息数据与业务数据保存在同一数据库实例里(消息记录表表名为message);

1
2
3
4
5
Begin transaction
         update set amount=amount-10000 where userId=1;
         insert into message(userId, amount,status) values(1, 10000, 1);
End transaction
commit;

  上述事务能保证只要支付宝账户里被扣了钱,消息一定能保存下来。

  当上述事务提交成功后,我们通过实时消息服务将此消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复后删除该条消息数据。

3.1.2 业务与消息解耦方式

  上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。

  1)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;

  2)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;

  3)当支付宝扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;

  4)对于那些未确认的消息或者取消的消息,需要有一个消息状态确认系统定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。

  优点:消息数据独立存储,降低业务系统与消息系统间的耦合;

  缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。

3.2 如何解决消息重复投递的问题

  还有一个很严重的问题就是消息重复投递,以我们支付宝转账到余额宝为例,如果相同的消息被重复投递两次,那么我们余额宝账户将会增加2万而不是1万了。

  为什么相同的消息会被重复投递?比如余额宝处理完消息msg后,发送了处理成功的消息给支付宝,正常情况下支付宝应该要删除消息msg,但如果支付宝这时候悲剧的挂了,重启后一看消息msg还在,就会继续发送消息msg。

  解决方法很简单,在余额宝这边增加消息应用状态表(message_apply),通俗来说就是个账本,用于记录消息的消费情况,每次来一个消息,在真正执行之前,先去消息应用状态表中查询一遍,如果找到说明是重复消息,丢弃即可,如果没找到才执行,同时插入到消息应用状态表(同一事务)。

1
2
3
4
5
6
7
8
for each msg in queue
  Begin transaction
    select count(*) as cnt from message_apply where msg_id=msg.msg_id;
    if cnt==0 then
      update set amount=amount+10000 where userId=1;
      insert into message_apply(msg_id) values(msg.msg_id);
  End transaction
  commit;

  Ebay的研发人员早在2008年就提出了应用消息状态确认表来解决消息重复投递的问题:http://queue.acm.org/detail.cfm?id=1394128

参考文献

Dan PritchettBase: An Acid Alternative,http://queue.acm.org/detail.cfm?id=1394128

程立,大规模SOA系统中的分布式事务处理

mysql两阶段提交,http://blog.csdn.net/jesseyoung/article/details/37970271

分享到:
评论

相关推荐

    使用RabbitMQ+延迟队列实现分布式事务的最终一致性方案

    为了解决这一问题,我们可以采用“最终一致性”策略,即允许在一段时间内数据存在短暂不一致,但最终会达到一致状态。在本方案中,我们将利用RabbitMQ的延迟队列特性来实现在订单和库存系统中的分布式事务最终一致性...

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

    《katcat:基于消息队列实现数据最终一致性》 在分布式系统中,数据一致性是一个至关重要的问题,尤其是在大型互联网应用中。"katcat"项目,正如其标题所示,旨在利用消息队列技术来确保数据的最终一致性。在这个...

    基于消息队列的分布式系统数据一致性方法研究.pdf

    通过这种方式,即使在节点故障或其他异常情况下,也能通过消息队列的持久化和重新投递机制,保证数据的最终一致性。 此外,消息队列还可以提供顺序保证,这对于某些需要严格顺序的业务场景至关重要。例如,在金融...

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

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

    PB消息队列聊天源码

    4. 一致性与可靠性:为了保证消息不丢失,消息队列通常会实现事务机制、确认机制或者消息重试策略。例如,使用“发布/订阅”模型时,消息可能会被持久化存储,直到所有订阅者都确认收到。 5. 安全性:在实际应用中...

    基于rabbitmq实现的分布式事务解决方案是独立消息服务的最终一致性案例源码.zip

    在这个场景中,我们关注的是基于RabbitMQ实现的分布式事务解决方案,它是一种独立消息服务确保最终一致性的案例。RabbitMQ是广泛应用的消息中间件,用于解耦应用程序,使得它们可以通过消息进行通信,而无需直接交互...

    springboot缓存一致性解决

    常见的有三种:强一致性、最终一致性和读已写一致性(Read-Your-Writes Consistency)。在分布式系统中,强一致性很难实现,因为需要保证所有节点在同一时刻看到相同的数据,这通常会牺牲系统的可用性。因此,Spring...

    微服务架构下的数据一致性:概念及相关模式.docx

    面对这一挑战,微服务架构往往采用最终一致性模型,这是一种权衡一致性和可用性的策略。在CAP理论中,由于分区容错性是分布式系统的基本需求,因此在一致性与可用性之间,微服务通常更倾向于保证可用性。最终一致性...

    基于可靠消息的最终一致性的分布式事务解决方案

    分布式事务解决方案之基于可靠消息的最终一致性方案。RMQ本身不生产消息队列,...RMQ框架提供消息预发送、消息发送、消息确认、消息恢复、消息管理等功能,结合成熟的消息中间件,解决分布式事务,达到数据最终一致性。

    分布式消息队列设计与架构演进

    然而,在构建自己的消息队列时,往往会遇到各种各样的挑战,比如系统的复杂性增加、数据一致性难以保证等问题。此外,随着业务规模的不断扩大,原有的单体架构越来越难以满足需求,因此,对架构进行持续的优化和演进...

    彻底搞定互联网架构中海量数据一致性Release(2018.11.21).pdf

    文档可能详细探讨了几种分布式事务模型,如两阶段提交协议(2PC)、基于可用性和最终一致性的BASE理论、三阶段提交(TCC)、长事务(Saga)模式、消息队列(MQ)等,并解释这些模型如何应用于不同的同步和异步场景中...

    TUXEDO的可靠消息队列

    3. **事务一致性保障**:借助TMS_QM,/Q能够支持事务级别的消息处理,确保在一系列操作中,所有相关操作要么全部成功,要么全部失败,从而维持数据的完整性和一致性。 4. **故障恢复**:即使面对系统崩溃或网络故障...

    消息队列概述

    消息队列提供了两种处理策略:**强一致性**和**最终一致性**。 - **强一致性**通常采用两阶段提交协议来实现,但这种方式在网络延迟较大或系统较为复杂的情况下成本较高。 - **最终一致性**通过记录操作状态并在...

    使用RabbitMQ实现最终一致性的分布式事务案例.zip

    为了解决这个问题,我们可以采用分布式事务的策略,其中一种是最终一致性。本案例主要探讨如何利用消息中间件RabbitMQ来实现分布式事务的最终一致性。 首先,我们需要理解什么是最终一致性。最终一致性是一种弱一致...

    Springboot2.X基于可靠消息rabbitmq最终一致性分布式事务+分布式全局唯一ID生成器

    如果全部为消费成功,则更新业务订单表中的订单状态由 下单中 --》 待付款并删除所有临时表数据,如果非全部消费成功,则将定性为异常单并写入补单队列,并删除所有临时表数据。该逻辑有定时调度完成。

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

    - 最终一致性:为实现高可用性,系统可能会牺牲强一致性,导致数据在一段时间内处于不一致状态。 - 服务间的通信:异步通信可能导致数据同步延迟,而同步通信则可能增加延迟并降低性能。 - 事件驱动架构:事件的...

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

    3. **基于事务型消息队列**:业务处理后发送消息,消息队列负责后续处理,确保最终一致性。如RabbitMQ或Kafka。 4. **消息队列+定时补偿机制**:在消息队列基础上增加补偿任务,处理失败情况,确保一致性。 5. **...

    如何解决微服务的数据一致性分发问题.docx

    - **最终一致性**:允许短暂的数据不一致,通过最终同步达到全局一致性。 每种方法都有其适用场景和优缺点,需要根据业务需求和系统特性来选择合适的数据一致性分发解决方案。在实施时,还需要考虑监控、调试和运维...

    可靠消息的最终一致性解决方案.docx

    ### 可靠消息的最终一致性解决方案 #### 一、引言 在分布式系统中,确保消息传递的一致性和可靠性是一项核心挑战。为了提高系统的稳定性和可用性,本方案提出了一种可靠的最终一致性解决方案,旨在通过特定的技术...

Global site tag (gtag.js) - Google Analytics