【前言】
一月一度的企业测算如期而至,遇到一个非常棘手的并发问题,解决的过程挺有意思,在此与大家共享。
【有趣的过程】
一、前提:
1、测算是指每个月某些企业需要进行,点击测算后,系统会有一套计算逻辑(存储过程)将该企业的本月数据进行相应的规整,写入数据库中,供总部核对。
2、时间安排:每个月的24日和25日两日进行;企业数量:24家;每家企业进行分配时间:半个小时。
3、测算相应的逻辑计算量:共30个存储过程,每个存储过程300行代码,平均每十行代码操作一个表(大部分是插入和更新操作,少部分是查询操作)。
4、目前耗时分两种情况:
(1)如果是单家企业执行没有企业并发的话,平均耗时在十分钟左右;
(2)如果有两家以上的企业同时执行的话,会出现并发的问题,这种情况下好的情况会大概半个小时执行完毕,坏的情况下直接执行失败。
5、执行流程:点下测算功能后会触发这三十个存储过程顺序执行,每执行完一个存储过程中后会往JobLog表中写一条执行该存储过程信息的一条信息记录,单家企业执行该如下图所示:
二、现在的解决办法:
1、思路:发现问题后,有很多的解决思路,考虑到企业已经开始测算,如果大的改动时间上不允许,所以采用最快解决问题的思路—控制其走单线程:即一个企业执行完再让另外一个企业执行。
2、实现这种思路想到了两种实现方式:
(1)Redis校验法:每个企业在进行测算的时候先往缓存中(目前系统采用的是Redis,系统中应用可以参考《Redis系列》)更新一下记录是否有企业在进行测算的标识为开始,结束的时候再将该标识更新为已完成,每次企业进行测算前去缓存中读取该值,以此来确定是否可以开始本次测算。
(2)数据库校验法:每次企业进行测算前先去JobLog表中去进行校验,查看当前是否有企业正在进行测算;
3、两种实现方式优缺点对比:
(1)Redis校验法:
①优点:校验速度快;
②缺点:提供不了存储过程执行的详细信息(比如:如果当前有企业在执行,该企业是哪个企业以及企业什么时候开始执行的,执行的什么程度等等);另外增加Redis多了一个故障点;
(2)数据库校验法:优缺点正好和采用缓存的相反;
(3)根据当前业务特点,权衡利弊后最终采用数据库校验法来实现。
4、实现效果:
(1)若没有企业进行测算时:
(2)若有企业进行测算时:
5、遇到问题:
(1)企业点击测算按钮时仍出现并发问题;
①分析问题:通过JobLog表记录,发现出现这种情况为同一个企业,而且测算的开始时间相差10秒左右;
②导致问题的原因:总存储过程执行的流程(见一中5图)—先执行存储过程1执行完后再向JobLog表中写入记录;判断当前是否有企业再执行的逻辑是每1秒通过Ajax,去查询JobLog表中最新的记录是否为最后一个存储过程,若是则说明当前没有企业在执行,若不是则有企业在执行;执行第一个存储过程也需要时间,当企业第一次单击时校验JobLog表是记录上一家企业最后一条记录,开始执行测算,但是当它第一个存储过程还未执行完(也就是说还未向JobLog表中写入存储过程一相关信息时)又一次点击了测算,此时出现了并发问题。
③解决方法:修改总存储过程,在执行第一个存储过程前就向JobLog表中写入一条开始记录;如下图:
(2)同一个企业连续快速点击时出现并发问题:
①分析问题:通过JobLog表监控到同一个企业在相差1s时间内同时执行测算(该测算模式相当于秒杀,所以企业都很疯狂在点击测算);
②分析原因:检查代码发现在执行测算的页面,点击执行时调用Ajax是先执行测算然后再将测算按钮变灰的,当中有时间差,导致第二次仍能触发测算按钮;
③解决方法:将按钮变灰放在Ajax校验之前:
原来实现代码:
修改后的实现:
三、扩展解决办法:
1、这三十个存储过程是有问题的,根据业务梳理存储过程,这是最根本的解决办法;
2、采用队列和多线程相结合,这样企业就不用等待;
3、期间请教一些大牛,他们给出的其中一些比较好用的技术:
(1)Ignite(学习资料:https://www.zybuluo.com/liyuj/note/230739)
(2)Hangfire(参考:http://www.cnblogs.com/chenxizhang/p/4740921.html)
(3)Quartz(参考:http://www.quartz-scheduler.org/)
【总结】
1、在此次解决问题过程中,控制企业一个个来进行测算,实现简单迅速,既保证了测算的正常进行,同时也为后续继续优化留出的赢得了宝贵的时间。
2、快速的找到问题的所在,根据问题找寻诸多解决方案;
3、分析各个方案的利弊,根据当前所处的情况,找到一个最适合的。