在开发过程中,遇到一个bug,产生bug的原因是spring事务提交晚于消息队列的生产消息,导致消息队列消费消息时获取到的数据不正确。这篇文章介绍问题的产生和一步步的解决过程。

一.问题的产生:

场景还原:接口中的一个方法,首先修改订单状态,然后向消息队列中生产消息,消息队列的消费者获取到消息检测订单状态,发现订单状态未更改。

代码:

@Service(orderApi)
public class OrderApiImpl implements OrderApi {
@Resource MqService mqService;
@OrderDao orderDao; public void push(String orderId) {
// 更新订单状态,之前的状态是1
updateStatus(orderId, 3);
// 产生消息
mqService.produce(orderId);
}
public viod updateStatus(String orderId, Integer status) {
orderDao.updateStatus(orderId, status);
}
}

问题产生原因:orderApi中的所有方法都有事务,事务类型PROPAGATION_REQUIRED,所以push方法对数据的操作会在push代码全部执行之后提交,而在事务提交之前消息队列的消息已经产生所以消息队列中消费到的订单从数据库查询出的状态可能还为1。为了让bug现象更明显,可以在push方法最后添加:

try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

这样就会发现消费消息时,订单状态一定是未修改的。

二.问题的解决:

解决方案:在更新数据时,新建一个事物,保证更新代码执行完成后,更新数据库的事务已被提交。(确保消息产生前数据库操作已提交)

按照上述方案,我首先想到的是直接修改updateStatus方法的事务类型;我将此方法的事务类型改为PROPAGATION_REQUIRES_NEW(新建事务,如果当前存在事务,把当前事务挂起)。

但是这么做有两点不合适:

  1.强制修改了updateStaus的事务类型,可能影响其他流程。

  2.未起到作用,updateStaus方法中没有新建事务。

关于第二点的解释:spring添加事务是通过BeanNameAutoProxyCreator实现的动态代理,只是给bean对象添加了事务,现在在类内部调用方法,是不会触发新事物的创建的。

所以在经过以上尝试后,我创建了一个新的类:

@Service("orderExtApi")
public class OrderExtApiImpl {
@Resource OrderApi orderApi; public void updateStatusNewPropagation(String orderId) {
orderApi.updateStatus(orderId);
}
}

并为updateStatusNewPropagation方法添加事务PROPAGATION_REQUIRES_NEW

这个类就只是为了给orderApi中的updateStaus方法新起一个事务。

ok,到此为止bug已经解决了。

但是代码中还是存在问题:对数据库的操作已经提交,如果生产消息出现异常对业务逻辑来说还是错误的。所以需要检测消息的产生是否完成。

最终orderApi中的代码如下:

@Service(orderApi)
public class OrderApiImpl implements OrderApi {
@Resource MqService mqService;
@Resource OrderDao orderDao;
@Resource OrderExtApiImpl orderExtApi; public void push(String orderId) {
// 更新订单状态,之前的状态是1
orderExtApi.updateStatusNewPropagation(orderId, 3);
// 产生消息--produce会检测是否出现异常 当返回1时表示生产消息成功
Response response = mqService.produce(orderId);
if (response.getCode() != 1) {
log.info("消息队列生产消息异常:" + response.getErrorMsg())
// 生产消息异常,重置状态 等待下次重新执行
orderExtApi.updateStatusNewPropagation(orderId, 1);
} }
public viod updateStatus(String orderId, Integer status) {
orderDao.updateStatus(orderId, status);
}
}

  

spring事务与消息队列的更多相关文章

  1. Spring整合ActiveMq消息队列

    ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线.ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久 ...

  2. spring mvc redis消息队列

    通常情况下,为了提高系统开发的灵活性和可维护度,我们会采用消息队列队系统进行解耦.下面是一个采用spring redis实现的消息队列实例,但此实例会由于网络延迟和阻塞等情况导致消息处理的延时,因而不 ...

  3. [转帖]微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务

    微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务 http://skaka.me/blog/2016/04/21/springcloud1/ APR 21ST,  ...

  4. 微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务

    http://skaka.me/blog/2016/04/21/springcloud1/ 不同于单一架构应用(Monolith), 分布式环境下, 进行事务操作将变得困难, 因为分布式环境通常会有多 ...

  5. 使用kafka消息队列解决分布式事务(可靠消息最终一致性方案-本地消息服务)

    微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务 本文转自:http://skaka.me/blog/2016/04/21/springcloud1/ 不同于单一 ...

  6. NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...

  7. Redis作为消息队列服务场景应用案例

    NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例   一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

  8. 【转】NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...

  9. 使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 "消息"是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,"消息队列&qu ...

随机推荐

  1. 构造函数模式自己定义js对象

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. latextools \cite 自动补全

    最近在用latex写毕业论文,编辑环境用的是Sublime Text 2 加 latextools 插件,在使用latextools的\cite命令来引用参考文献时,我们希望输入\cite{ 后自动弹 ...

  3. Scala 并行和并发编程-Futures 和 Promises【翻译】

    官网地址 本文内容 简介 Futures 阻塞 异常 Promises 工具 最近看了<七周七语言:理解多种编程泛型>,介绍了七种语言(四种编程范型)的主要特性:基本语法,集合,并行/并发 ...

  4. zxing扫码--镭射线

    同步发表于http://avenwu.net/2015/09/15/zxing_view_finder_laser 在很多应用中都有二维码扫描的需求,比如微信,通过扫描电脑二维码,实现用户登录授权: ...

  5. 用户管理 之 Linux 用户管理工具介绍

    Linux是一个多用户的操作系统,她有完美的用户管理工具,这些工具包括用户的查询.添加.修改,以及用户之间相互切换的工具等:通过这些工具,我们能安全.轻松的完成用户管理: 在这里我们要引入用户控制工具 ...

  6. 我也要学C语言-第十九章:命令行参数

    C语言的语法规定main函数是带连个参数的,因为当初是考虑是在控制台下写程序.于是用户可以给参数微控程序.其实现在的WINDOWS程序也可以带参数.一般正规军写的应该程序一般都带命令行参数,帮助文档, ...

  7. C#使用ICSharpCode.SharpZipLib.dll压缩文件夹和文件

    大家可以到http://www.icsharpcode.net/opensource/sharpziplib/ 下载SharpZiplib的最新版本,本文使用的版本为0.86.0.518,支持Zip, ...

  8. 常用SQL语句备忘录

    1.---表中有重复记录用SQL语句查询出来 select * from Recharge where RechargeSerial in (select RechargeSerial from Re ...

  9. Mac终端常用命令收集

    删除非空目录 rm -rf 目录名字 -r 就是向下递归,不管有多少级目录,一并删除-f 就是直接强行删除,不作任何提示的意思 终端修改hosts文件 sudo vi /etc/hosts 切换到su ...

  10. UNIX环境高级编程笔记之文件I/O

    一.总结 在写之前,先唠几句,<UNIX环境高级编程>,简称APUE,这本书简直是本神书,像我这种小白,基本上每看完一章都是“哇”这种很吃惊的表情.其实大概三年前,那会大三,我就买了这本书 ...