一次项目并发问题的解决方案

零 业务背景

船公司用户在每天同一时间打印单据,进行抢单,出现并发问题

一 问题

打单的时候,如费用规则设置了可以打印200单,实际却打印了不到200个单(通过打印日志查看到)

二 原因

由于扣减费用规则箱数量和记录打印日志不在同一个事务,导致客户打单时,出现并发操作,在打印日志记录时触发乐观锁,最终记录打印日志方法回滚了,而扣减费用规则箱数量未回滚。从而出现少打现象

三 解决方案

1 梳理流程

 流程图url
 
 https://www.processon.com/view/link/607e273df346fb647a5f5958
 
 
复制代码

2 方案选取

1 将费用规则中箱的扣减放到打单后面,并将扣减和打单放到同一个事务中

打单依赖于费用规则,也就是说费用规则一定会跑,如果将对费用规则中箱的更新移动到打印之后,那就就是 费用规则->打印->费用规则箱更新。
这种方式的优点是,将费用规则的扣减独立开,缺点是,修改的代码较多,由于业务敏感,需要更长时间的测试。

2 使用redis分布式锁

考虑到打单时,遍历每一个单,如果对每一个单都上锁,那么性能可能有印象。

3 直接将费用规则中箱的扣减和打单日志记录放到同一个事务

这种处理方案的优点是,改动很小,并且能达到数据一致性的实现,相应的存在的问题是,扩大了事务,可能会出现更多的乐观锁。

3 方案执行

比对以上几个方案,方案3更适合,实际执行中对一些方法进行整理,考虑到打印日志的记录可能出现回滚,于是发送报文的方式进行了改动,之前的报文放松方式为记录一次打印日志发送一次报文,现在如果整个打单完成,再一次性发送所有要发送的报文

4 方案执行过程

1 修改打单规则和单状态大事务后出现锁表
 三个用户每人打大批量相同单,同时修改打单规则和单状态导致锁表
 
复制代码
2 缩小事务范围
 由于事务太大产生锁表,所以对代码进行梳理,将大事务拆分成更小的事务,最终将多个单的事务修改成打个单的事务控制
 
复制代码
3 出现打单规则扣减少扣现象
 打单规则扣减156,实际打单200,梳理发现,打单规则扣减使用saveOrUpdate方法,没有实现乐观锁控制,事务拉长之后,由于扣件后没有及时提交,导致出现少扣现象。
 
复制代码
4 减少乐观锁发生
 将修改打单规则和单状态的方法加上锁,减少出现乐观锁的发生,保证程序更正常的执行
 
复制代码
5 针对多节点的并发控制
 java锁可以保证单节点但是不是保证多节点,多节点使用乐观锁控制
 
复制代码
6 乐观锁报错后,增加对用户的提示,该单已打印
 之前的提示并不友好,对乐观锁报错进行catch处理,提示用户该单已打印
 
复制代码
7 相关问题
1 事务未生效,在同一个类中由无事务的方法调用有事务的方法,有事务的方法事务不会生效
   因为Spring事务是由动态代理控制,而动态代理其实是在调用类与被调用类之间生成一个代理类,在同一个类中两个方法调用不会生成代理类,所以事务是不会生效的。
复制代码
2 synchronized锁加上后,没有锁住事务
@Transactional(rollbackFor = Exception.class)
public synchronized List<PrintLog> getExistCntPlacePrintLogs(Map<EdiBooking, PrintingRule.Result> resultMap, Set<String> messages,
                                                                                             String identify, String remark, EdiBooking booking) {
    Container container = countContainerNum(booking);
  ...
复制代码

就像第一点问题的产生原因一样,事务是在锁加载的方法的先后执行的,所以事务的操作不在锁内,就锁不到事务了。

3 乐观锁未触发
1 方法使用saveOrUpdate是不会触发乐观锁的
2 出现问题,version明明过期了,但是还是没有触发乐观锁,思考乐观锁的执行。
复制代码

5 最终方案

通过分布式锁+事务控制完成整体修改。
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享