首先澄清一下,最近更博比较少,最近在研究新的东西的同时还有大量的任务在做,这个月会继续的更新,把rabbitmq的系列更新完成,同时把我研究的新的东西的完整的系列也整理发布出来,大家一起学习进步。
一、问题描述:
很多时候面试都会被问到并发的问题,那个时候我们总觉的遇不到这种情况,并发多线程就是一个屠龙之术,不幸的是本人负责的一个项目中,出现了这个问题。最开始体量很少用户少,看不出问题更加测不出问题,最终还是用户给我们暴漏了出来。还好这是一个模拟的贵金属交易系统(给各大银行交易大赛使用的)
下面先给一段伪代码来描述一下逻辑片段:
if( userHoldPosition >= entrust.getNum){
执行平仓程序 中间过程大概 1-3s的耗时
// 更新用户的仓位
updateUserPosition(userNo,entrustNum);
}
这段代码正常跑起来没问题吧,是的我们测试过很多遍,毕竟银行大佬们如果程序稍有问题,他们是不会用的心情真是诚惶诚恐呀。
下面给出一种在多线程下编发的问题,
- A请求A 线程通过判断正在执行平仓程序,但是此时还没有走到更新仓位的代码
- 此时B请求B线程也来到了判断用户仓位是否足够平仓,由于A线程还未更新用户持仓,所以此时B线程判断成功通过,
- A线程更新用户持仓,B线程完成平仓程序,更新用户仓位
二、分析
2.1 问题推广
- 1.在这个并发的情况下,加入用户的仓位不足,是不是会出现负仓位的情况出现。
- 2.把这种情况推广,电商中下单扣减库存的问题上、领取优惠券的问题上
2.2 解决方案:
要想解决问题,必须要找到问题的根源:这种情况出现的原因①:请求同时到达,判断与更新之间时间跨度大,例如开平仓的计算问题,以及订单的下单、支付中间都需要很长的时间跨度,所以下面针对这种情况给出解决方案。
- 针对电商下单的问题,一般是下单就立即锁库存,注意这种方法可以抵御并发量不大的情况,如果是阿里、京东这种体量依旧抵挡不住,目前题主没有太好的办法,慢慢的积累看以后有什么更好的办法吧。
- 双重检查(double check相信大家在单例的七种实现方法中看到过),这种针对我目前遇到的问题是可以解决的,就是在更新用户的仓位的时候在做一次判断,当然极端的情况依旧会有问题(大体量的用户的时候),这中情况类似于乐观锁,大家也可以使用乐观锁来实现
ps:说加锁的朋友,我就不想在说什么了,如果什么地方都可以通过加锁搞定,多线程和并发就不会那么折磨人了。
三、介绍我在网上看到的一种血案(需要通过锁来实现的)
一张优惠券引发的血案原文链接
https://juejin.im/post/5a5182986fb9a01ca5604a8d
我想大部分没有并发思维的同学都是这样写出来的,然而问题来了,当某个时间点缓存不存在,请求量比较大的时候,同时出现多个线程去查询DB并进行缓存,改进使用锁
这里是使用到了锁,但是在上文中为何我又说锁不能解决问题呢,这里是因为更新缓存的数据一定只能是串行的一定不能出现多个线程去查询数据库然后更新缓存的情况,这样的情况的话缓存就失去了意义了。然而下单程序本来就是一个多线程的高并发的应用,如果这个时候你去加锁,岂不是让下单交易程序变成了一个串行话的程序了吗,这种情况肯定是解决了不一致的问题,但是你的吞吐量会严重的下降。
这里已经加锁了,进行到这里应该就可以高枕无忧了吧,只能too young too simple,忘了上面我们遇到的同时通过判断的情况了吗,假如A线程在获取锁的时候,B线程在判断优惠券列表是否存在,A更新释放锁,此时B获取锁又会在进行一次更新,所以问题依然存在,下面看解决方案:
这就是我遇到的和网上看到的,在高并发下,看起来测试起来都没有问题的程序发生血案的故事,这里面锁和双重检查的思想一定是要有的,以及常见的解决手段乐观锁都是需要具备的,所以我们在做这种带有并发的程序的时候一定要有并发思想在里面,对于可能出现的极端的并发的情况进行提前的处理,以防引发事故。
博客首发地址,另外也有群570980002,希望大家能一起在技术上进行探讨,共同成长共同进步