Java基础之《分布式事务(6)—支付的分布式事务》

一、支付场景介绍

1、支付场景
Java基础之《分布式事务(6)—支付的分布式事务》_第1张图片

甚至手机收到一个短信,或者生成一个财务凭证
问题:
支付成功后,如果不用刚性事务或TCC,采用MQ,如何100%把积分送出去,并入库?并修改订单的支付状态?

二、三种不可靠的MQ消息通讯

1、第一种常见的不可靠分布式MQ消息—先处理本地业务,后发消息
Java基础之《分布式事务(6)—支付的分布式事务》_第2张图片

2、为什么该消息不可靠
如果先支付成功后处理本地业务(更改支付订单状态),再发送积分的消息,会出现以下几种数据不一致的情况:
(1)异常情况1
本地业务成功,但是发送消息异常(例如网络异常超时)
Java基础之《分布式事务(6)—支付的分布式事务》_第3张图片

(2)异常情况2
本地业务成功,发送消息也成功,但是消费异常失败(例如积分服务业务异常)
Java基础之《分布式事务(6)—支付的分布式事务》_第4张图片

这种设计导致下游不可控,一旦下游出现异常就会出现脏数据,造成数据不一致
public void testPay() {
    //1.先支付
    payService.proess();
    //2.发送消息送积分
    mqMsg.send();
}

3、第二种常见的不可靠分布式MQ消息—先发消息,后处理本地业务

4、为什么该消息不可靠
如果先发送积分消息,再处理本地业务(更改支付订单状态),会出现以下几种数据不一致的情况:
(1)异常情况1
消息发送异常(网络响应超时)。消息实际上已经发送成功,而且消息消费也成功了,最后导致没执行本地业务
Java基础之《分布式事务(6)—支付的分布式事务》_第5张图片

(2)异常情况2
消息发送成功,本地业务也成功,但是消费失败(例如积分服务业务异常)
Java基础之《分布式事务(6)—支付的分布式事务》_第6张图片

(3)异常情况3
消息发送成功,消费也成功,但是本地业务失败(例如db网络异常)
Java基础之《分布式事务(6)—支付的分布式事务》_第7张图片

以上3种情况都将出现脏数据
public void testPay() {
    //1.发送消息送积分
    mqMsg.send();
    //2.先支付
    payService.proess();
}

5、第三种常见的不可靠分布式MQ消息—放在同一个事务里
将业务代码和消息发送,放在同一个本地事务中进行处理,如果业务处理失败,就不会发送消息;如果消息发送失败,业务代码也跟着回滚
@Transactional
public void testPay() {
    //1.先支付
    payService.proess();
    //2.发送消息送积分
    mqMsg.send();
}

6、为什么该消息不可靠
看似解决了问题,但是有很多弊端
(1)异常情况1(感觉这个情况一般不会有)
业务处理成功了,积分消息也发送成功,但是在提交事务的时候,支付服务和数据库网络断开了,这时会出现事务回滚,导致数据不一致(积分消息已经发送出去了)
Java基础之《分布式事务(6)—支付的分布式事务》_第8张图片

(2)异常情况2
消息发送异常,网络响应超时,消息实际上已经发送成功了,而且消息消费也成功,事务回滚导致没执行本地业务
Java基础之《分布式事务(6)—支付的分布式事务》_第9张图片

(3)异常情况3
业务处理成功了,积分消费也发送成功了,并且事务也提交成功了,消息消费失败了。积分服务出现异常,这时同样出现了数据不一致
Java基础之《分布式事务(6)—支付的分布式事务》_第10张图片

三、本地消息表分布式服务

1、谁发明了本地消息表
本地消息表的分布式事务,最初是由eBay于2008年提出来的,核心思路是将分布式事务拆分成本地事务进行处理

2、什么是本地消息表分布式事务

基于本地消息表的最终一致性的核心做法就是在执行业务的操作的时候,记录一条消息数据到本地DB,并且消息数据的记录与业务数据的记录必须在同一个事务内完成,这是该方案的前提核心保证

3、谁来记录DB
该方案有2种角色组成
(1)事务主动者
负责新建一张事务消息表,然后干了3件事:
1.处理了自己的业务逻辑
2.记录事务消息,在本地事务中完成,并且在本地同一个事务内完成
3.发送事务消息给消息中间件MQ
(2)事务被动者
负责从消息中间件MQ,消费事务消息表中的事务

问题:
1.如何保证事务消息100%发送成功?
2.如何保证事务消息100%消费成功?

四、如何保证事务消息100%发送成功

1、主动者通知被动者处理事务
Java基础之《分布式事务(6)—支付的分布式事务》_第11张图片
第一步:写业务表数据,支付成功后,将本地的支付订单改为已支付
第二步:写消息数据表,存储该支付订单对应的消息数据,并且订单消息状态为0=未发送
Java基础之《分布式事务(6)—支付的分布式事务》_第12张图片

注意:消息数据和订单库必须为同一个数据库,这样才能保证步骤一和步骤二为同一个本地事务,要么同时成功,要么同时失败
第三步:
3.1异步发送MQ消息:这里采用多线程,异步往消息中间件发送消息
3.2改消息状态:消息发送成功后,收到ACK后,把消息修改为1=发送成功
第四步:事务被动者,负责消费消息中间件MQ消息
第五步:事务被动者,将消费的数据落地到DB
public void testPay() {
    //1.写业务表数据
    payService.proess();
    //2.写消息表数据
    msgService.proess();
    //3.异步发送消息改积分
    mqMsg.send();
}

2、问题:如果异步消息发送失败,将导致状态为0=未发送,该如何处理?

五、如何保证消息100%被消费

1、被动者告诉主动者处理事务
Java基础之《分布式事务(6)—支付的分布式事务》_第13张图片
第六步:通知支付服务,积分已赠送完毕
第七步:支付服务收到积分服务的消息后,更改消息表,把消息状态改为“2=消费成功”,或直接删除该记录即可
第八步:支付服务更改消息表状态

2、问题:步骤6、7、8任意一步骤如果抛出异常,导致消费失败怎么办?

六、本地消息表分布式事务如何容错
容错的前提是支付服务和积分服务都必须保证幂等性

1、如果异步消息发送失败,将导致状态为0=未发送,该如何处理

Java基础之《分布式事务(6)—支付的分布式事务》_第14张图片

在保证幂等性的情况下,用定时器重新发送,规则:定时器如果发现0=未发送的过期数据,例如这条数据超过2分钟还没变为1=发送成功,则轮询重新发送

2、步骤6、7、8任意一步骤如果抛出异常,导致消费失败怎么办
同样在保证幂等性的情况下,采用定时器重新发送,定时器如果发现1=发送成功的过期数据,例如这条数据超过2分钟还没变为2=消费成功,则轮询重试发送
Java基础之《分布式事务(6)—支付的分布式事务》_第15张图片

3、如果图中步骤1、2抛出异常,事务回滚
因为支付消息和消息表都在同一个数据库中,是本地事务,相当于没发生什么事

4、如果图中步骤3.2抛出异常
由于步骤3.1是异步执行的,故不会影响到步骤1、2的事务。故可以用定时器,将0=未发送的数据重新发送

5、如果图中步骤4、5抛出异常
和步骤3.1一样,在保证幂等性的情况下,采用定时器重新轮询发送一遍

七、本地消息表分布式事务的优缺点

1、该方案的核心就是一张本地消息表,技术实现容易,轻量级

2、优点
技术简单,不复杂,容易实现,其核心就是一张本地消息表,整个过程都围绕这张表进行增删改操作

3、缺点
为了达到本地事务的效果,消息数据和业务数据必须同一个数据库,这种问题比较大,耦合度大,不可重复利用
 

你可能感兴趣的:(JAVA基础,java)