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某一个小事务方法时,外层事务最后提交时,依然会抛出异常表现为乐观锁失败
图1.1
图1.2
图1.3
图1.4
2.try···catch···抓住异常并打印时为何显示出错位置为userRewardInvestReturnBonusManager.listRewardBonus(investing.getUserId())这样一行查询方法处?
Answer: 1.首先来看Hibernate官方文档来了解下Flush Hibernate Session Cache相关的时机(图2.1),可以发现在执行某些查询操作的时候会将Hibernate缓存刷新到数据库缓存;
图2.1
2.出错代码位置的查询符合这样的一类查询(图2.2),即所查询的表是否在Hibernate缓存所要进行DML操作的表中,在则刷出Hibernate缓存;
图2.2
3.刷出缓存后,check刷新结果时,预期结果和实际结果不一致,所以抛出异常。
相关链接:
1. https://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html#objectstate-flushing
最后,欢迎大家参与讨论,希望能共同进步。