大家好,又见面了,我是你们的朋友全栈君。
众所周知现在连市场卖菜的大妈都快知道高并发了,哈哈,那么我们生活中是否接触过高并发呢。当然了哈哈,比如你给你女朋友抢秒杀的化妆品什么的了。秒杀最棘手的问题就是解决并发带来的问题。下面我们一起聊聊喽。
首先我们来说下问题:秒杀高并发带来的最大问题,就是库存超卖。(如果你没看过我的文档,导致你写公司秒杀业务时库存超卖了,公司损失了,将你开除了,你会多么不开心,哈哈我来给你写稻草救救你)
嘻嘻嘻嘻嘻嘻嘻
解决方案:解决高并发导致库存超卖问题的核心思想就是,库存扣减要保证串行化操作。
1.数据库层面解决方案:
这种方式实现起来最简单,就是利用乐观锁。
update product set num=num-1 where num-1>0 and pid=#{pid}
使用此方法一定要注意,set num 时一定不要使用,set num=#{num},这样做的话不能保证原子化操作,并发还是会有问题(覆盖更新)。
缺点:数据库存在瓶颈,首先是连接数,更重要是多个请求更新一条数据,会互相检查是否死锁(笛卡尔积)。而秒杀问题就是请求量大并发高。那怎么办?继续看小老弟说咯。
2.使用缓存(Redis等)解决方案:
使用缓存确实可以解决数据库瓶颈的问题,例如Redis天然的串行化操作(Redis单线程),并且在数据内存中做操作很快。
使用Redis的自增incr或自减decr操作库存,判断返回结果是否为0,如果为0表示秒杀失败。这样不就保证了库存的不超卖。
此时你可能说,啊我明白了不就是用Redis的缓存来搞吗,简单简单。本大侠哈哈一笑,要是这样的话本大侠不也去BAT了。
=================下面才是关键=========================================
说说我们目前满意的Redis方案的问题吧。
先说一个优化的问题:
1.每次我们通过Redis查询库存都是通过远程连接的方式,虽然很快,但是并发大的时候,这里还是要优化一下的。
怎么优化?简单的说就是,当发现Redis库存为0时,我们在程序中设置一个标识位,秒杀逻辑中每次进来先判断标志位。这样库存为0时就直接返回,而不用再远程连接查询Redis了。
你可能会说这样就行了吧?对于一般的秒杀来说应该可以了,但我还要和大家说说更多的优化和优化时的问题,希望对大家的工作有好的提示或者帮助,那样我会很高兴的。哈哈
2.每次当我们秒杀成功后,会创建订单、通知库房、通知快递等一系列操作,如果我们把这些操作也放在秒杀时来处理,那么我们程序处理起来可想而知,会很慢的。那么这时我们就要优化,怎么优化?
实现异步处理,我们可以通过MQ(RocketMQ等消息队列)来实现异步处理,当用户秒杀成功后,我们发送消息给其他服务,然后返回给用户秒杀结果,这样是不是就很快了呢。对是快了。
那么问题来了:用户秒杀成功后需要付款,但是此时是异步操作,队列可能并没有处理完消息,怎么办怎么办?哈哈,这时我们需要在前端加一个轮询,轮询什么?轮询查询秒杀的结果(象征的意思意思用户,排队中什么什么的了)。下面代码
简单讲讲这段代码:首先判断登录就不说了,后面通过用户名查询下当前用户是否秒杀成功了,然后然后问题来了,哈哈为什么我下面要使用了数据库查询?而不是查询缓存中的商品数量是否为0,因为还是之前的问题,队列没有消化完,用户秒杀成功的记录还没有生成,如果查询数据库商品没有了,那就代表队列已经处理完了,代表你秒杀失败了GG。如果还有库存,那么返回success(0)告诉用户排队中。直到数据库中库存没有了,那么代表队列处理完了,这时候你在查询Redis应该有订单信息的,如果没有那么你真的是秒杀失败了。讲到这里可能大家理会的差不多了,但是这段代码中还是存在问题?什么问题呢,那就是当他查询Redis时队列消息还是没处理她的消息,当他查询数据库之前,队列处理完了,这样你查数据库发现库存没有了,你就会返回秒杀失败,但其实你是秒杀成功的。这样还是会影响用户体验的。你可能会说不能吧,那么巧。哈哈我就是要和你说高并发下这种情况出现很正常。所以呢,我们要防止,怎么防止呢?
终极解决方案
我们在查数据库判断库存为0时(标记:数据库库存为0,消息肯定处理完了,也就是redis肯定有订单了,因为有妹子问了,我必须更新一下哈哈),我们再次查询Redis里是否已经有生成的订单了,这样就避免了问题咯。这也就是江湖中传闻的江湖秘诀,双重校验锁,哈哈,其实关于秒杀啊高并发的问题还有很多,这类问题每一行代码都是要考虑很多情况的,希望我再这里能给大家一个抛砖引玉的作用。小老弟要睡觉了,如果哪里说得不对别喷啊,都同行哈哈哈。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/138411.html原文链接:https://javaforall.cn