日期:2014-05-16 浏览次数:20514 次
? 最近遇到一个场景:
? ?1、系统有一个功能是做入库,批量挺大,一次需要入库上万级的数据(页面一个提交完成)。
? ?2、系统使用的B/S结构。
? ?3、系统表数据已经很庞大,估计上千万级数据了。
? ?4、系统架构主要是Action--->Service 结构,也就是Action用于处理页面的跳转,Service层用于具体的业务处理,
Action和service是1对多的关系,其中一个service层方法作为一个事务。
? ?5、系统同时使用一个功能,一组数据的情况比较少。
?
? ?用户的一种操作是这样的,在页面上提交数据,但是发现一段时间没执行出来,于是重新进入系统重新保存,导致系统几乎同时出现一模一样的两个事务处理,并都执行成功,结果数据重复。
? ?另外一种是多个用户同时操作同一批设备,同时做入库。
?
? ? 我们都知道事务具有,1、一致性,2、原子性,3、隔离性,4、持久性。根据其特点,结合系统的架构我们不难分析出出现问题的原因。A事务在执行,还没提交之前,B事务继续提交,此时两个事务是相互隔离的,并且两个事务的最终提交时间也具有不确定性。
? ? ? 出现以上情况,大概总结了几种解决方案。
?
?
? ?1、对于处于同一个会话的两次提交,可以使用token机制来判断是否是同时两次提交。strtus有实现,
?
protected String generateToken(HttpServletRequest request) 创建一个令牌.
protected boolean isTokenValid(HttpServletRequest request) 检查令牌是否有效
protected boolean isTokenValid(HttpServletRequest request,Boolean reset) 检查令牌是否有效,并且重置令牌(如果reset 是true)
protected void resetToken(HttpServletRequest request) 重置令牌
protected void saveToken(HttpServletRequest request) 添加令牌
基本原理
服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,
看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给
客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次
提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。
2、对于不同会话之间的重复提交。 这边也有几种解决方案,各有利弊:
? ?(1) 数据分步提交,即执行比如1000个数据后,提交事务。这样根据两个事务具有时间差 来判断数据的重复性是可行的。 但是一旦其中一个提交发生了异常中断,那么已经建档的数据得不到有效回滚操作。导致部分数据会重复提交。目前这边主要的解决办法是判断异常,然后处理数据回滚。
? (2)数据不分步提交,数据全部一次提交,两个事务具有时间差 来判断数据的重复性是理论上也是可行的。但是如果数据量太大,事务数据量太大,最终会前台session超时500错误,用户也是等不起这个执行时间的。但是对于小批量的效果很好,也不用考虑并发提交的异常情况。
? ?(3)从数据库的表结构上进行优化,比如表中有唯一键业务字段,那么建立唯一键。但是很多场景其实都不具备这个条件,因为大部分这个情况都是新数据的录入。
?
3、其他方面做优化,让用户等得住。
? ? 对于长时间的处理的时候,如果只是单纯的滚动条,或者按钮灰掉等处理,用户肯定不耐烦,这边也提出几个解决办法供探讨。
? ?(1) 将后台执行数据在前台实时显示,同时屏蔽其他可操作入口。
? ?(2) 前台提供进度条,显示进度百分比,同时屏蔽其他可操作入口
? 等待完成后恢复可操作入口。
? 以下是实现实时展示后台执行数据的思路:
? ? 将执行某一个元操作后的