电商项目part09 分布式事务Seata&MQ可靠消息

分布式事务

在微服务架构中,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。

电商项目中会结合下单的业务重点讲解两种分布式事务解决方案:

  • 2PC的方案: 基于Seata AT实现
  • mq可靠消息的方案:基于Rocketmq事务消息实现

Seata架构

在 Seata 的架构中,一共有三个角色:

  • TC (Transaction Coordinator) - 事务协调者
    维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。

在 Seata 中,一个分布式事务的生命周期如下:

  1. TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。
  2. RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。
  3. TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
  4. TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。
    (branch表,lock表,log表)
    在这里插入图片描述

分布式事务结合分库分表

ShardingSphere整合Seata

引入依赖

<!‐‐shardingsphere整合seata依赖‐‐>
<dependency>
  <groupId>org.apache.shardingsphere</groupId>
  <artifactId>sharding‐transaction‐base‐seata‐at</artifactId>
  <version>4.1.1</version>
</dependency>

配置seata.conf
包含 Seata 柔性事务的应用启动时,用户配置的数据源会根据 seata.conf 的配置,适配为
Seata 事务所需的 DataSourceProxy,并且注册至 RM 中。

client {
    
    
  application.id = tulingmall‐order‐curr
  transaction.service.group = tuling‐order‐group
}

开启全局事务配置
核心类SeataATShardingTransactionManager

//全局事务交给SeataATShardingTransactionManager管理
@ShardingTransactionType(TransactionType.BASE)
@Transactional
public CommonResult generateOrder(OrderParam orderParam, Long memberId) {
    
    
...
}

在这里插入图片描述

注意:GlobalTransactional和ShardingTransactionType不能同时出现,此处不能使用@GlobalTransactional。同时需要关闭数据源自动代理

 seata:
  enable‐auto‐data‐source‐proxy: false #关闭数据源自动代理,交给sharding‐jdbc那边
  

可靠消息最终一致性方案实现

可靠消息最终一致性方案是指当事务发起执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。

RocketMQ保证消息不丢

生产端:同步消息(ACK机制)

存储端:同步刷盘

消费端:手动offset

Rocketmq事务消息实现

RocketMQ事务消息设计则主要是为了解决Producer端的消息发送与本地事务执行的原子性问题,RocketMQ的设计中broker与producer端的双向通信能力,使得broker天生可以作为一个事务协调者存在;而RocketMQ本身提供的存储机制为事务消息提供了持久化能力;RocketMQ的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。

在RocketMQ 4.3后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决Producer端的消息发送与本地事务执行的原子性问题。
在这里插入图片描述

执行流程如下 :

1)Producer发送事务消息
Producer(MQ发送方)发送事务消息至MQ Server,MQ Server将消息状态标记为Prepared(预览状态),注意此时这条消息消费者(MQ订阅方)是无法消费到的。
2)MQ Server回应消息发送成功
MQ Server接收到Producer发送给的消息则回应发送成功表示MQ已接收到消息。
3)Producer执行本地事务
Producer端执行业务代码逻辑,通过本地数据库事务控制。
4)消息投递
若Producer本地事务执行成功则自动向MQ Server发送commit消息,MQ Server接收到commit消息后将消费消息,消费成功则向MQ回应ack,否则将重复接收消息。这里ack默认自动回应,即程序执行正常则自动回应ack

以上主干流程已由RocketMQ实现,只需要分别实现本地事务执行以及本地事务回查方法,因此只需关注本地事务的执行状态即可。

RocketMQ提供RocketMQLocalTransactionListener接口 :

public interface RocketMQLocalTransactionListener {
    
    
/**
  * 发送prepare消息成功此方法被回调,该方法用于执行本地事务
  * @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id
  * @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
  *@return 返回事务状态,COMMIT :提交 ROLLBACK :回滚 UNKNOW :回调
  */
	RocketMQLocalTransactionState executeLocalTransaction(Message msg,Object arg);
/**
 * @param msg 通过获取transactionId来判断这条消息的本地事务执行状态
 * @return 返回事务状态,COMMIT :提交 ROLLBACK :回滚 UNKNOW :回调
 */
 	RocketMQLocalTransactionState checkLocalTransaction(Message msg);
 }

案例

	/**
     * 使用事务消息机制发送订单
     * @return
     */
    public boolean sendCreateOrderMsg(Long orderId, Long memberId){
    
    
//        SendResult result = rocketMQTemplate.syncSend(asyncOrderTopic+":"+ORDERTAG,message);
        String destination = asyncOrderTopic+":"+ORDERTAG;
        Message<String> message = MessageBuilder.withPayload(orderId+":"+memberId)
                .build();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(destination,message,orderId);
        return SendStatus.SEND_OK == sendResult.getSendStatus();
    }

    /**
     * 发送延时同步库存消息,60s后同步库存
     * @param productId
     * @param promotionId
     * @return
     */
    public boolean sendStockSyncMessage(Long productId,Long promotionId){
    
    
        Message message = MessageBuilder.withPayload(productId+":"+promotionId).build();
        SendResult result = rocketMQTemplate.syncSend("stock-sync",message,5000,5);
        return SendStatus.SEND_OK == result.getSendStatus();
    }

猜你喜欢

转载自blog.csdn.net/Forbidden_City/article/details/132623564