前置知识
- 事务: 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任意一个执行失败,将导致整个事务的回滚。简单来说就是提供一种”要么什么都不做,要不全都做“的机制。
- 本地事务: 当事务是由资源管理器本地管理时被称为本地事务。本地事务的有点是支持严格的 ACID 特性,高效,可靠,状态可以只在资源管理器中维护,而且应用编程模型简单。但是本地事务不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器。MySql 的 InnoDB 通过日志(Redo 和 Undo)和锁来保证事务。
- 全局事务: 当事务由全局事务管理器进行全局管理时成为全局事务,事务管理器负责管理全局事务状态和参与的资源,协同资源的一直提交回滚。
刚性事务和柔性事务
- 刚性事务:遵循 ACID 原则,强一致性,典型的例子就是数据库事务。
- 柔性事务:尊循 BASE 理论,最终一致性,允许在一定时间内,不同节点的数据不一致,但要求最终一致性。
分布式知识
CAP
分布式系统在设计时只能在一致性,可用性和分区容错性中满足两种,无法兼顾三种。
C: 在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。在一致性的要求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。
A: 可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
P: 分区容错性约束了一个分布式系统需要具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
对于共享数据系统,最多只能同事用于 CAP 其中的两个,任意两个都有其适应的场景。分布式系统中最重要的是满足业务需求,而不是追求高度抽象,绝对的系统特性。
- 说明 放弃一致性(C) 这里所说的放弃C,并不是完全不需要数据一致性,如果真这样,那么系统的数据也没意义,系统也就没价值。放弃C指放弃数据的强一致性,而保留数据的最终一致性。这样的系统无法保证数据保持实时的一致性,但是能够承诺的是,数据最终会达到一个一致的状态。这就引入了一个时间窗口的概念,具体多久能够达到数据一致取决于系统的设计,主要包括数据副本在不同节点之间的复制时间长短。 放弃可用性(A) 相对于放弃P来说,放弃A正好相反,其做法是一旦系统遇到网络分区或其他故障时,那么受到影响的服务需要等待一定的时间,因此在等待期间系统无法对外提供正常的服务,即不可用。 放弃分区容错性(P) 如果希望能够避免系统出现分区容错性问题,一种较为简单的做法是将所有的数据(或者仅仅是与事务相关的数据)都放在一个分布式节点上。这样的做法虽然无法100%地保证系统不会出错,但至少不会碰到由于网络分区带来的负面影响。但同时需要注意的是,放弃P的同时也就意味着放弃了系统的可拓展性。 BASE
BA 指的是基本业务可用性,支持分区失败,S 表示柔性状态,也就是允许短时间内不同步,E 表示最终一致性。原子性和持久性必须从根本上保障,为了可用性,性能和服务降级的需要,只有降低一致性和隔离性的要求。
刚性事务解决方案
即分布式事务的两阶段提交,对应技术上的 XA,JTA。
2PC
两阶段提交将一个事务分为两个阶段:
第一阶段:事务管理器要求每个涉及到事务的数据库预提交操作,并反应是否可以提交。
第二阶段:事务管理器综合结果,要求每个数据库提交数据或者回滚数据。
两阶段提交防范应用非常广泛,几乎所有的商业 OLTP 数据库都支持,但是两阶段提交方案锁定资源时间长,对性能影响大,基本不适合解决为服务的问题。
缺陷:
- 同步阻塞。
- 单点故障,一旦协调者发生故障,所有参与者会一直阻塞下去。
- 数据不一致,在二阶段提交的第二阶段中,如果局部网络异常或者发送提交请求过程中协调者发生了故障,这就导致只有一部分参与者收到了提交请求,导致数据不一致。
柔性事务解决方案架构
柔性事务有两个特性,基本可用和柔性状态。所谓基本可用是指分布式系统出现故障的时候允许损失一部分的可用行。柔性状态是指允许系统存在中间状态,这个中间状态不会影响系统整体的可用性,比如数据读写分离的主从同步延迟等。柔性事务的一致性指的是最终一致性。
基于可靠消息(消息中间件)的最终一致性解决方案
- 实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。
- 消息:业务处理服务在业务事务回滚后,向实时消息服务取消发送。消息发送状态确认系统定期找到未确认发送或者回滚发送的消息,向业务处理服务询问消息状态,业务处理服务根据消息ID或者消息内容确认该消息是否有效。被动方的处理结果不会影响主动方的处理结果,被动方的消息处理操作是幂等操作。
- 成本:可靠的消息系统建设成本,一次消息发送需要两次请求,业务处理服务需要实现消息状态回查接口。
- 优点:消息数据独立存储,独立伸缩,降低业务系统和消息系统之间的耦合。对最终一致性时间敏感度较高,降低业务被动方的实现成本。兼容所有实现JMS标准的MQ中间件,确保业务数据可靠的前提下,实现业务的最终一致性,理想状态下是准实时的一致性。
消息发送的一致性是指产生消息的业务动作与消息发送一致,也就是说如果业务操作成功,那么由这个业务操作所产生的消息一定要发送出去,否则就丢失。
public void completeOrderService() { // 处理订单 order.process(); // 发送会计原始凭证消息 pipe.sendAccountingVouchetMessage(); }
在上面的情况中,如果业务操作成功,执行消息发送之前应用发生故障,消息发送不出去,导致消息丢失。如果消息系统或者网络异常,也会导致消息发送不出去,最终结果是数据不一致。
public void completeOrderService() { // 发送会计原始凭证消息 pipe.sendAccountingVouchetMessage(); // 处理订单 order.process(); }
将两个操作调换了一下顺序,情况更加不可控了,消息发出去了业务可能失败,也会造成数据不一致的情况。
保证消息一致性的变通做法:
- 发送消息:主动方先将应用消息发送给消息中间件,消息状态标记为待确认状态。
- 消息中间件收到消息后,会将消息持久化。
- 消息中间件返回持久化结果给主动方:
- 成功:继续执行业务操作处理。
- 失败:放弃执行业务操作处理,结束整个事务,抛出异常。
- 业务操作完毕,返回操作结果给中间件。
- 消息中间件收到业务操作结果,根据结果进行处理
- 成功:更新消息状态为待发送,执行消息投递。
- 失败:删除消息存储中的消息,结束。
- 向应用方投递消息。
- 应用方返回消息处理结果。
- 成功:标记消息状态为已成功。
- 失败:人工兜底。
消息重复发送问题和业务接口的幂等性设计
对与没有确认的消息,采取按规则重新投递的方式进行处理。对于以上流程,消息的重复发送会导致业务接口出现重复调用的问题。消息消费过程中消息重复发送的主要原因就是消费者成功接收处理完消息后,消息中间件没有及时更新投递状态导致的。
独立消息服务方案:
- 预发送消息:主动方应用系统预发送消息,由消息服务子系统存储消息,如果存储失败,那么也就无法进行业务操作。如果存储成功,然后执行业务操作。
- 执行业务操作:执行业务操作成功的时候,将业务操作执行成功的状态发送到消息服务子系统,消息服务子系统修改消息的标识为”可发送“状态。
- 发送消息到实时消息服务:当消息的状态发生改变的时候,立刻将消息发送到实时消息服务中。接下来,消息将会被消息业务的消费端监听到,然后被消费。
- 消息状态子系统:相当与定时任务系统,在消息服务子系统中定时查找确认超市的消息,在主动方应用系统中也去定时查找没有处理成功的任务,进行相关的处理。
- 消息消费: 当消息被消费的时候,向实时消息服务发送ACK,然后实时消息服务删除消息。同时调用消息服务子系统修改消息为“被消费”状态。
- 消息恢复子系统:当消费方返回消息的时候,由于网络中断等其他原因导致消息没有及时确认,那么需要消息恢复子系统定时查找出在消息服务子系统中没有确认的消息。将没有被确认的消息放到实时消息服务中,进行重做,因为被动方应用系统的接口是幂等的。
优点:
- 消息服务独立部署,独立维护,独立伸缩。
- 消息存储可以按需选择不同的数据库来集成实现。
- 消息服务可以被相同的的使用场景使用,降低重复建设服务的成本。
- 从分布式服务应用设计开发角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了对MQ中间件特性的依赖。
- 降低了业务系统与消息系统之间的耦合,有利于系统的扩展维护。
缺点:
- 一次消息发送需要两次请求。
- 主动方应用系统需要实现业务操作状态的校验与查询接口。
名称 数据类型 允许空 默认值 属性 释义 uuid varchar(50) No — unique UUID version int(11) No 0 — 版本号 editer varchar(100) Yes NULL — 修改者 creater varchar(100) Yes NULL — 创建者 edit_time datetime Yes 0000-00-00 00:00:00 — 最后修改时间 create_time datetime No 0000-00-00 00:00:00 — 创建时间 msg_id varchar(50) No — — 消息ID msg_body longtext No — — 消息内容 msg_date_type varchar(50) Yes — — 消息数据类型 consumer_queue varchar(100) No — — 消费队列 send_times int(6) No 0 — 消息重发次数 is_dead varchar(20) No — — 是否死亡 status varchar(20) No — — 状态 remark varchar(200) Yes — — 备注 field0 varchar(200) Yes — — 扩展字段0 field1 varchar(200) Yes — — 扩展字段1 field2 varchar(200) Yes — — 扩展字段2 TCC 事务补偿型方案
实现:一个完整的业务活动由一个主业服务于若干的从业服务组成。主业服务负责发起并完成整个业务活动。从业务服务提供 TCC 型业务操作。业务活动管理器控制业务活动的一致性,它登记业务活动的操作,并且业务活动提交时确认所有 TCC 型操作的 Confirm 操作,在业务活动取消时调用所有 TCC 型操作的 Cancel 操作。
成本:实现 TCC 操作的成本较高,业务活动结束的时候 Confirm 和 Cancel 操作的执行成本,业务活动的日志成本。
使用范围:强隔离性,严格一致性要求的业务活动,适用于执行时间较短的业务,比如出账户或者收费。
特点:不与具体的服务框架耦合,位于业务服务层,而不是资源层,可以灵活的选择业务资源的锁定粒度。TCC里对每个服务资源操作的是本地事务,数据被锁住的时间短,可扩展性好,可以说是为独立部署的SOA服务而设计的。
尽最大努力通知型
实现: 业务活动的主动方在完成处理之后向业务活动的被动方发送消息,允许消息丢失。业务活动的被动方根据定时策略,向业务活动的主动方查询,恢复丢失的业务消息。
约束: 被动方的处理结果不影响主动方的处理结果。
成本: 业务查询与校对系统的建设成本。
使用范围:最业务最终一致性的时间敏感度低,跨企业的业务活动。
特点:业务活动的主动方在完成业务处理之后,向业务活动的被动方发送通知消息。主动方可以设置时间阶梯通知规则,在通知失败后按规则重复通知,知道通知N次后不再通知。主动方提供校对查询接口给被动方按需校对查询,用户恢复丢失的业务消息。
适用范围: 银行通知,商户通知。
下一张节我们会使用消息队列来实现一个分布式事务框架。
来源:https://www.cnblogs.com/paulwang92115/p/12163452.html