数据库写入事务和MQ消息发送的一致性问题

2,082 阅读2分钟
  • 问题产生的背景:

        项目A和项目B共用一个Mysql数据库,项目A在对数据做一些处理之后,将处理完成的数据批量写入数据库中,然后发送一条MQ消息(消息体内只有主键ID)给项目B。项目B拿到该条消息之后用主键ID去Mysql数据库中查询出全量数据并写入Redis缓存中

  • 问题产生原因:

话不多说,上项目A中的代码:

        结合如上代码可以看出,如果数据库事务提交慢的情况下,就会产生MQ消息已经发送成功,但是数据库事物还没有提交成功。项目B(消费端)消费的时候,带着主键ID去Mysql数据库查询没有结果导致消费失败。因为MQ消费是异步的,所以不能保证数据库事务提交和MQ消费的顺序。所以有时候可以正常消费,有时候消费会失败

  • 解决方案

        方案一 

         将发送MQ的代码和插入数据库的代码分开,保证插入数据库事务成功之后再发送MQ消息

        方案二

          事务消息(因为我们项目使用的消息队列是RocketMQ,是支持事务的消息队列)

           1)首先项目A在消息队列上开启一个事务

           2)项目A给消息服务器发送一个"半消息",这个半消息是说内容是完整的消息内    容,但是在事务提交之前,对于消费者来说,这个事务是不可见的

           3)半消息发送成功之后,项目A就可以执行本地事务了,在Mysql数据库中批量插入处理完成的数据,并提交数据库事务

           4)根据本地数据库事务的执行结果决定提交或者回滚事务消息。如果项目A数据库插入成功,那就提交事务消息,项目B就可以正常消费这条消息并继续后续流程,如果项目A插入失败,那就回滚事务消息,项目B就不会收到这条消息

          如果第4步提交或者回滚事务消息的时候发生网络异常等情况导致RocketMQ的Broker没有收到提交或者回滚的请求,Broker会定期去Producer上反查这个事务对应的本地事务的状态,然后根据反查结果决定提交或者回滚这个消息事务。为了支撑这个事务反查机制,我们的业务代码需要实现一个反查本地事务状态的接口,告知RocketMQ本地事务是成功还是失败