您的位置:首页 > 编程语言 > Java开发

一次订单业务问题的排查

2016-04-12 10:39 441 查看
前些天遇到一个切换数据源的问题,分析了下给大家分享下

l 【问题】

1. 问题背景

提单数据是分库存储的,分库意味着数据需要根据特定的路由规则路由到不同的库,

切换数据源不可避免;

2. 问题描述

优化版本单品单结,用户订单不能按照路由规则,存储到特定的数据库,而是存储在默认的数据库中,持久层代码如图



l 【分析】

1. 提单采用的路由规则很简单,路由key为用户pin,根据用户pin哈希取模决定数据存储到特定的数据库,若不能取得路由key,则存储到默认的数据库中。从结果看数据存储到了默认的数据库,说明路由策略中没有取到对应的路由key。

2. 为了Service层与Dao层的分离解耦,路由key,是通过TheadLocal的方式存储到线程中,实现两个层的数据传递,线程内部为串行执行,只需要在Dao层调用之前确定路由key,Dao层便可以根据路由规则,将数据存储到特定的库。代码也确实是这么处理的,将用户pin设置到了线程变量,并且之后调用了Dao层的方法,如下图标识。



3. 问题变的难以琢磨,一切貌似都很正常,但只有优化版本的单品单结业务出现了这样的问题,商超,外卖,秒杀等平行业务没有这样的问题,使用的路由规则和Dao层方法也是一样一样的,在测试环境下,加入打印日志验证

通过日志对比看出在路由策略执行的时,单品单结没有取到路由key,其他业务可以取到路由key,进一步将问题锁定在单品单结业务的代码上,但然而仍然没有什么头绪……

4. 再次对比代码,寻找差异(很反复的过程),抱着宁可错杀一千也不放过一个的态度,最终发现设置路由key的位置,有所不同,其他业务设置路由key为拿到用户pin就立即设置,而单品单结是在调用Dao之前设置路由key,将单品单结的设置路由key的代码,尽可能的前置,部署测试,惊奇的发现正确运行,订单数据可以顺利路由到用户pin对应的数据库了,改动设置路由key的代码位置生效了,距离问题的真正原因又近了一步;继续变换该段代码的位置,不断测试,最终问题慢慢凸显出来,在事务内部设置路由key就会有问题,在事务外部设置路由key则正确运行。事务注解@Transactional都做了些什么呢?



5. 进一步的分析,我们知道无论是注解式的事务,还是声明式的事务,最终都是事务管理器处理事务,我们这里使用的是spring默认的DataSourceTransactionManager,

Spring注解事务的传播级别为默认的PROPAGATION_REQUIRED,如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,所以一旦开启就会在同一事务中。

在事务开启时DataSourceTransactionManager会调用doBegin方法,这个方法会获得该事务使用的数据库连接,结合场景,由于是第一个事务标签所以是新建的事务,所以会通过图中的this.dataSource.getConnection()来获取数据库连接。



我们使用的数据源为下图动态路由数据源,该数据源依赖自定义路由策略

dataSource.getConnection(),分为两步,第一步确定数据源,第二步获取已定数据源的数据库连接;第一步确定数据源的过程依赖我们自定义的路由策略。如下图



我们自定义的数据源实现了父类的方法,并依赖自定义的路由策略,如图



大致流程如下图



也就是说在事务开启阶段,我们已经使用路由规则,选定了数据源并且获取了数据库连接,并且将该连接绑定在线程中,而后续的数据库操作都是使用的是线程绑定的数据库连接(myibatis事务也是spring管理,也会直接使用该连接)。在开启事务之前设置路由key是有效的,而在事务内部设置路由key已经为时已晚,因为后续的操作会一直使用在事务开启阶段绑定的数据库连接。

【总结】

1. 数据源的切换需要在获取数据库连接之前,获取数据库连接以后,切换数据源不能起作用。

2. 开启数据库事务会提前锁定数据库连接,对于确定数据源的路由key等数据,应该在事务开始之前设置。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java