在开发过程中,遇到一个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. Android版-支付宝APP支付

    此项目已开源 赶快来围观 Start支持下吧 [客户端开源地址-JPay][服务端端开源地址-在com.javen.alipay 包名下] 上一篇详细介绍了微信APP支付 点击这里 此篇文章来详细介绍 ...

  2. QQ互联开发-01-开发者注册和网站应用申请

    首先需要申请网站应用 要想使用QQ互联,首先需要先注册一个开发者账号,使用QQ号码可以注册,注册后,就可以创建网站应用了: 通过下面地址:https://connect.qq.com/,我们去登录QQ ...

  3. c10k问题及其解决方案

    本文主要讲述高并发http应用中的c10k瓶颈问题:在很多服务器初始状态下,无法服务1w左右的并发连接.这与每次服务的资源消耗.服务器的硬件配置固然有关,但很多时候是被linux的默认配置以及软件st ...

  4. Selenium实战脚本集(3)--抓取infoq里的测试新闻

    描述 打开infoq页面,抓取最新的一些测试文章 需要抓取文章的标题和内容 如果你有个人blog的话,可以将这些文章转载到自己的blog 要求 不要在新窗口打开文章 自行了解最新的测试思潮与实践

  5. 为YAESU FT-817ND 增加频谱功能

    > ft817可谓是QRP神机啊,可能是一开始就接触SDR的缘故,没有频谱显示,总觉得很难受,好在经典的817从不缺乏魔改.终于找到一个解决方案: http://item.taobao.com/ ...

  6. MyEclipse中拷贝J2EE项目,发布到tomcat中名字一样的解决办法

    修改Eclipse工作空间下新拷贝项目下.settings文件夹中org.eclipse.wst.common.component的两个属性值. 为新项目名字: <?xml version=&q ...

  7. OceanBase RPC机制简要说明

    http://note.youdao.com/share/?id=d2163a7ba8ec1ec58e64683a961c5121&type=note RPC是OB一个比较重要的各个组件间进行 ...

  8. 如何在Xcode6中添加空模板

    在Xcode中模板位置: Macintosh HD ▸ 应用程序 ▸ Xcode(低于版本6的).app ▸ Contents ▸ Developer ▸ Platforms ▸ iPhoneOS.p ...

  9. SQL server 数据库连接方式分析

    SQL server 数据库连接方式图示: ODBC和OLEDB连接的区别 ODBC(开放数据库互连):是Microsoft引进的一种早期数据库接口技术.它实际上是ADO的前身.早期的数据库连接是非常 ...

  10. notepad++批量头尾追加

    每行首添加 在“查找目标”里输入上尖号“^”,上尖号代表每行的开头 每行尾追加