rocketmq之分布式事务实现

  1. 分布式事务概述
    1.1概念术语
    事务:
    由多个计算任务构成的一组具有明确边界的工作集合。事务当中可能包括接口访问、网络通信、数据获取和处理。严格的事务实现应该具备具有原子性、一致性、隔离性、持久性四个特性

原子性(Atomicity):一个事务中的任务要么全部完成,要么全部失败。没有中间状态。
隔离性(Isolation):不同事务之间的操作互不影响,并发的事务其中间状态对其他事务不可见。
持久性(Durability):事务一旦完成,则状态永久有效。
一致性(Consistency):事务涉及的资源或者数据在事务前后遵循某种约束,事务的完成或失败不会影响此状态。

分布式事务:
在分布式系统中,事务的访问涉及的资源、参与计算的节点都部署在不同的节点上,这种情况下涉及到的事务称为分布式事务。

从系统整体的架构角度看,分布式事务涉及的场景可以分为两类。第一类是,事务本身只涉及单个应用,但是涉及多个数据存储,一笔交易需要访问多个数据存储才能最终完成。第二类,是事务本身涉及多个应用,同时每个应用可能连接着一个或者多个数据存储,一笔交易需要协同多个独立的应用访问多个数据存储最终才能完成。
1.2流程分析
在这里插入图片描述
1.2.1 生产者发送半消息给MQ Server(可以理解为暂存待确认的消息),发送成功后返回响应给生产者
1.2.2 生产者收到MQ Server 响应之后执行本地事务,根据本地事务执行的结果(COMMIT/ROLLBACK)反馈到MQ Server ,MQ Server 根据返回的结果确定值执行消息投递(消息最终确认)还是消息回滚(消息删除)
1.2.3 在上述执行的过程中有可能出现本地事务执行完成,但是还MQ Server 还未收到反馈的情况,生产者程序断网或者断电的情况,这种情况下,生产者程序重启后会自动去检查本地事务的状态,将本地事务执行状态返回给MQ Server ,MQ Server根据返回的结果判定最终是执行COMMIT还是ROLLBACK
1.3事务消息状态
COMMIT:提交事务消息,消费者可以消费此消息
ROLLBACK:回滚事务消息,broker会删除该消息,消费者不能消费该消息
UNKNOWN:broker需要回查确认该消息的状态
3. 分布式事务编程实现
通过内容微服务和用户微服务来讲解rocketmq分布式事务,内容微服务作为消息的生产者、用户中心作为消息的消费者
3.1场景描述:
内容中心有个分享文章的审核接口,审核通过后调用用户中心给对应的用户添加积分
3.2准备工作:
开启nacos注册中心服务
3.2内容中心实现
3.2.1 pom文件添加依赖

<dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>

3.2.2配置

rocketmq:
  name-server: 192.168.8.9:9876
  producer:
    # 小坑,必须指定group
    group: test-group

3.2.3添加service接口

Share auditById(Integer id, ShareAuditDTO auditDTO);

3.2.4添加service接口实现类

    @Override
    public Share auditById(Integer id, ShareAuditDTO auditDTO) {
        //1.查询share是否存在,不存在或者当前的状态 !=NOT_YET,那么抛出异常
        Share share = shareMapper.selectByPrimaryKey(id);
        if(null==share){
            throw new IllegalArgumentException("参数非法,该分享不存在");
        }

        if(!Objects.equals("NOT_YET",share.getAuditStatus())){
            throw new IllegalArgumentException("参数非法,该分享审核通过/不通过");
        }

        //2.如果是PASS 则向rocketmq发布半消息
        if(AuditStatusEnum.PASS.equals(auditDTO.getAuditStatusEnum())){
            //发送半消息
            String transactionId = UUID.randomUUID().toString();
            rocketMQTemplate.sendMessageInTransaction(
                    "tx-add-bonus-group",
                    "add-bonus",
                    MessageBuilder.withPayload(
                            UserAddBonusMsgDTO.builder()
                                    .userId(share.getUserId())
                                    .bonus(5)
                                    .build()
                    )
                            //有妙用。。。
                    .setHeader(RocketMQHeaders.TRANSACTION_ID,transactionId)
                    .setHeader("share_id",id)
                    .build(),
                    //arg 有大用处
                    auditDTO
            );
        }else {
            auditByIdInDb(id,auditDTO);
        }
        return share;
    }

发送消息代码为2中的逻辑判断,通过rocketMQTemplate.sendMessageInTransaction()方法向rocketmq进行消息发送
3.2.5添加事务监听类:

package com.yf.cn.contentcenter.rocketmq;

import com.alibaba.fastjson.JSON;
import com.yf.cn.contentcenter.dao.messaging.RocketmqTransactionLogMapper;
import com.yf.cn.contentcenter.domain.dto.content.ShareAuditDTO;
import com.yf.cn.contentcenter.domain.entity.messaging.RocketmqTransactionLog;
import com.yf.cn.contentcenter.service.share.ShareService;
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusTransactionListener implements RocketMQLocalTransactionListener {
    private final ShareService shareService;
    private final RocketmqTransactionLogMapper rocketmqTransactionLogMapper;

    /**
     * 执行本地事务,并返回对应的结果
     * @param msg  消息体
     * @param arg  额外参数
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        MessageHeaders headers = msg.getHeaders();

        String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
        Integer shareId = Integer.valueOf((String) headers.get("share_id"));

        String dtoString = (String) headers.get("dto");
        ShareAuditDTO auditDTO = JSON.parseObject(dtoString, ShareAuditDTO.class);

        try {
            //执行本地事务
            this.shareService.auditByIdWithRocketMqLog(shareId, (ShareAuditDTO) arg, transactionId);
            //返回本地事务正常结果
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            //返回本地事务异常结果
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     * rocketmq 容错机制,防止本地事务刚执行完但是未返回结果的瞬间断网或者断点的情况,
     * 当服务重新启动后自动检查本地事务执行的结果以便做出对应的处理
     * @param msg
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        MessageHeaders headers = msg.getHeaders();
        String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
        // select * from xxx where transaction_id = xxx
        RocketmqTransactionLog transactionLog = this.rocketmqTransactionLogMapper.selectOne(
                RocketmqTransactionLog.builder()
                        .transactionId(transactionId)
                        .build()
        );
        if (transactionLog != null) {
            return RocketMQLocalTransactionState.COMMIT;
        }
        return RocketMQLocalTransactionState.ROLLBACK;
    }
}

3.2.6添加对应的controller方法进行分享审核调用

package com.yf.cn.contentcenter.controller.content;

import com.yf.cn.contentcenter.domain.dto.content.ShareAuditDTO;
import com.yf.cn.contentcenter.domain.entity.content.Share;
import com.yf.cn.contentcenter.service.share.ShareService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/admin/shares")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ShareAdminController {
    private final ShareService shareService;
    @PutMapping("/audit/{id}")
    public Share auditById(@PathVariable Integer id, @RequestBody ShareAuditDTO auditDTO) {
        return this.shareService.auditById(id, auditDTO);
    }

}

启动程序,调用controller接口,即可向rocketmq发送消息
在这里插入图片描述
如上控制台,通过topic和消息生成的时间即可查看消息列表,点击MESSAGE DETAIL 即可查看消息明细
在这里插入图片描述
3.3用户中心消息消费代码实现
3.3.1添加依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
        </dependency>

3.3.2添加配置

rocketmq:
  name-server: 192.168.8.9:9876

3.3.3添加消息消费类

package com.yf.cn.usercenter.rocketmq;

import com.yf.cn.usercenter.dao.bonuseventlog.BonusEventLogMapper;
import com.yf.cn.usercenter.dao.user.UserMapper;
import com.yf.cn.usercenter.domain.dto.messaging.UserAddBonusMsgDTO;
import com.yf.cn.usercenter.domain.entity.bonuseventlog.BonusEventLog;
import com.yf.cn.usercenter.domain.entity.user.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@RocketMQMessageListener(consumerGroup = "consumer-group",topic = "add-bonus")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class AddBonusListener implements RocketMQListener<UserAddBonusMsgDTO> {

    private final UserMapper userMapper;

    private final BonusEventLogMapper bonusEventLogMapper;

    @Override
    public void onMessage(UserAddBonusMsgDTO message) {
        //当收到消息的时候执行的业务

        //1.为用户加积分
        Integer userId = message.getUserId();
        Integer bonus = message.getBonus();
        User user = userMapper.selectByPrimaryKey(userId);
        user.setBonus(user.getBonus()+bonus);
        userMapper.updateByPrimaryKey(user);
        //2.记录日志到bonus_event_log表
        bonusEventLogMapper.insert(
                BonusEventLog.builder()
                        .userId(userId)
                        .value(bonus)
                        .event("CONTRIBUTE")
                        .createTime(new Date())
                        .description("投稿")
                        .build()
        );
        log.info("积分添加完毕。。。");


    }
}

至此便完成了分布式事务代码的编写
rocketmq分布式事务测试方式(四)

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值