天天看点

20191113部分标的无法满标的问题总结及延伸思考

问题发现:

2019年11月13日,部分标的无法自动满标。

问题分析:

①通过admin系统管理-接口列表-项目接口-标的其他接口-查看出借订单功能发现有一笔出借订单状态和懒猫并不一致,后续调用同步出借订单功能进行同步,返回失败;

②查看日志,发现底层抛出org.hibernate.StaleObjectStateException,上层表现为org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException;

③结合代码和异常堆栈信息发现,是多条投资勾选同一张现金券时,首先回来的投资notify会将现金券的使用状态计入数据库,而后续回来的其他投资notify则会存表失败抛出乐观锁。 

解决方案:

完整解决方案将分为如下两步进行:

①首先将现金券的使用逻辑和投资逻辑通过事件-监听器方式解耦,保证现金券的使用的成功与否不影响投资的结果,该步骤目前已上线;

②将用户投资勾选现金券的行为由“缓存+日志”改为“数据库+缓存+日志”方式来进行;特别地,将在投资notify返回后,增加对于现金券bonus_id和investing_trans_id之间1对多的关系处理,而不仅是之前investing_trans_id和bonus_id之间的1对1,好处是后续投资可以更快地得到现金券的使用结果(减少连表查询操作)。

问题总结:

1. 方案设计时,依赖try catch来保证现金券的逻辑独立于投资逻辑,这种思路看似万无一失,其实存在重大问题,关于这点将在延伸思考里进行说明,正确的方案应该是抛事件,异步进行处理;

2. 过渡依赖乐观锁来解决并发问题,update操作本身为耗时操作,且发生乐观锁时回滚也是极其耗时的操作,执行期间还会占用锁,导致其他操作排队等情况的发生,正确的思路是先进行查询操作,无问题则保存这样可以避免相当一部分update类操作。

延伸思考:

1. 为什么在事务方法中try catch了,且将异常生吞,外部依然会乐观锁失败?

    Answer: 首先系统内的事务传播机制均设置为PROPAGATION_REQUIRED,即当事务嵌套事务且其中的某个内层事务发生异常时,会将sessionHolder设置为rollbackOnly = true

如图1.1、图1.2 ;而当最外层事务提交时,会去检查该标志位是否为false,如图1.3、图1.4,如果该标志位为false(最外层事务newTransaction为true),抛出异常,外层会将该方法封装为乐观锁异常,故在事务方法中try catch某一个小事务方法时,外层事务最后提交时,依然会抛出异常表现为乐观锁失败 

20191113部分标的无法满标的问题总结及延伸思考
                                                                                           图1.1
20191113部分标的无法满标的问题总结及延伸思考
                                                                                             图1.2
20191113部分标的无法满标的问题总结及延伸思考
                                                                                            图1.3
20191113部分标的无法满标的问题总结及延伸思考
                                                                                           图1.4

2.try···catch···抓住异常并打印时为何显示出错位置为userRewardInvestReturnBonusManager.listRewardBonus(investing.getUserId())这样一行查询方法处?

Answer:  1.首先来看Hibernate官方文档来了解下Flush Hibernate Session Cache相关的时机(图2.1),可以发现在执行某些查询操作的时候会将Hibernate缓存刷新到数据库缓存;
20191113部分标的无法满标的问题总结及延伸思考

                                                                                              图2.1

2.出错代码位置的查询符合这样的一类查询(图2.2),即所查询的表是否在Hibernate缓存所要进行DML操作的表中,在则刷出Hibernate缓存;

20191113部分标的无法满标的问题总结及延伸思考

                                                                                              图2.2

3.刷出缓存后,check刷新结果时,预期结果和实际结果不一致,所以抛出异常。

20191113部分标的无法满标的问题总结及延伸思考

相关链接:

1. https://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html#objectstate-flushing

2. https://docs.spring.io/spring/docs/4.0.5.RELEASE/spring-framework-reference/html/transaction.html#tx-propagation-required

3.https://stackoverflow.com/questions/19302196/transaction-marked-as-rollback-only-how-do-i-find-the-cause

最后,欢迎大家参与讨论,希望能共同进步。

继续阅读