记MySQL事务+消息队列引起的问题

问题描述:

先说一下流程:后端保存前端提交的图表信息,然后发送异步消息到消息队列,由下游服务去处理图表信息。

部署项目到服务器,验证项目功能的时候,出现了以下错误:数据库存在数据。下游服务查不到数据库的数据

// service代码
@Override
@Retryable
@Transactional
public ChartVo genChartByAiAsyncMq(Long uid, MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest) {
	// ...省略
	Chart chart = Chart.builder().name(name).goal(goal)
                    .chartData(data).chartType(chartType)
                    .uid(uid).status("wait").build();
     boolean save = this.save(chart);
     if(!save){
         log.info("保存表单失败");
         throw new RuntimeException("保存表单失败");
     }
     List<Chart> list = this.list();
     log.info("chart长度:{}", list.size());
     // 发送消息,触发异步处理
     biMessageProducer.sendMessage(String.valueOf(chart.getId()));
     log.info("发送消息成功");
     // 省略
}
        

下游服务处理代码

@SneakyThrows
@RabbitListener(queues = {MQConstants.BI_QUEUE_NAME}, ackMode = "MANUAL")
public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag){
    log.info("receive message: {}", message);

    // 检查消息是否为空
    if(StringUtils.isBlank(message)){
        log.info("消息不能为空");
        channel.basicNack(deliveryTag, false, false);
        throw new RuntimeException("消息不能为空");
    }
    List<Chart> list = biService.list();
    log.info("图表列表长度:{}", list.size());
    // 尝试将消息解析为图表ID,并查询图表信息
    Long chartId = Long.parseLong(message);
    Chart chart = biService.getById(chartId);
    log.info("图表信息:{}", chart);
    // 图表不存在时的处理
    if(chart == null){
        log.info("图表不存在");
        channel.basicNack(deliveryTag, false, false);
        throw new RuntimeException("图表不存在");
    }
    // 省略
}

日志输出如下:
在这里插入图片描述
数据库信息:
在这里插入图片描述

解决过程

首先说明一下,这个错误之前没有出现过,下午出错,再次测试的时候,也会出现正常的情况,只不过错误占比有点高(10次有8次获取不到数据库消息,用jemeter测试了一下)。

分析的过程:

步骤1、首先在上游服务和下游服务打印日志,查看数据库有多少条数据,上游服务显示有2条数据,下游服务显示有1条数据
在这里插入图片描述

步骤2、找错的时候,看见方法加了事务注解@Transactional,这个时候想到可能是事务影响(最近在看mysql相关的知识,比较敏感),然后取掉注解,重新验证,发现没有出错
在这里插入图片描述

原因分析

MySQL事务

我们都知道MySQL(8.x版本)的事务的隔离级别默认是可重复读(RR),那么一个事务在操作完成之前,对其他事务是看不见的,所以就说,方法中先保存图表信息到数据库,然后发送消息到消息队列,再执行方法的后续过程。发送消息到队列之后,可能数据库事务还没有提交,但是消息发送成功了,就立刻被消费者端消费,此时,消费者端查询数据库中的图表信息,当然查不到,因为生产者端的事务还没有提交。

之前没有出错,这次验证出错分析

为了写代码方便,使用的是服务器上的数据库,出现这个bug问题的时候,使用本地数据库,之后专门验证了一下,使用服务器上的数据库确实没有出现这个问题,我怀疑是本地数据库提交事务时间过长。但是反过来想,本地执行事务操作不受网络等因素影响,应该执行时间很短才是。为什么我的执行结果却是相反的,这使我很疑惑。最后我觉得只能从硬件出发,在实验室电脑上,运行项目的前后端,以及打开其他程序,内存的使用率就很高,那么在本地执行mysql事务的时候,估计时间也就很长啦(这是我目前分析的原因)
在这里插入图片描述
然后换了台电脑(M2芯片,24G内存),进行相同的操作,运行结果是正确的。如下:
在这里插入图片描述
从结果上来看,电脑内存占用率过高,导致本地mysq执行事务的时间大于服务器上mysql执行事务的时间。
好了,既然原因分析完了
那么再反思一下,如果一直使用服务器上的数据库,这个问题没有出现,系统是正常运行,这样好吗?答案是否定的,因为在真正的开发环境,我们的业务绝对会受系统配置和网络影响,针对这个场景,我们不能直接在方法上添加事务注解。

解决方法

1、手动提交事务,不使用注解,可以更细的控制事务开始和结束。
2、设置延迟队列,但是这个延迟的时间具体是多少,我们无法确定,所以最后采用第一种方法解决此问题。

@Slf4j
@Service
public class BiServiceImpl extends ServiceImpl<ChartMapper, Chart> implements BiService {

	@Resource
	private PlatformTransactionManager transactionManager;
	
	 @Override
	 @Retryable
	 public ChartVo genChartByAiAsyncMq(Long uid, MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest) {
	     TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
	     try{
	        // 省略...
	        Chart chart = Chart.builder().name(name).goal(goal)
                    .chartData(data).chartType(chartType)
                    .uid(uid).status("wait").build();
	         boolean save = this.save(chart);
	         if(!save){
	             log.info("保存表单失败");
	             throw new RuntimeException("保存表单失败");
	         }
	         transactionManager.commit(transactionStatus);
	         List<Chart> list = this.list();
	         log.info("chart长度:{}", list.size());
	         // 发送消息,触发异步处理
	         biMessageProducer.sendMessage(String.valueOf(chart.getId()));
	         log.info("发送消息成功");
	         // 省略...
	     }catch (Exception e){
	         log.error("AI 异步调用失败", e);
	         transactionManager.rollback(transactionStatus);
	         throw new RuntimeException("AI 异步调用失败");
	     }
	 }
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值