当前位置:   article > 正文

秒杀系统

秒杀系统

Java并发包提供三个常用的并发队列实现

分别是ArrayBlockingQueue、ConcurrentLinkedQueue和 LinkedBlockingQueue 。在请求预处理阶段,由于我们的系统入队需求要远大于出队需求,一般不会出现队空的情况,所以我们可以选择ConcurrentLinkedQueue来作为我们的请求队列实现。

1、并发队列的选择
Java的并发包提供了三个常用的并发队列实现,分别是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。
ArrayBlockingQueue是初始容量固定的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。
ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。
LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。
在请求预处理阶段,由于我们的系统入队需求要远大于出队需求,一般不会出现队空的情况,所以我们可以选择ConcurrentLinkedQueue来作为我们的请求队列实现。

2、请求接口的合理设计

一个秒杀或者抢购,需要前端和后端2部分。
通常前端静态HTML等内容,可以通过CDN的部署,一般压力不大,核心瓶颈实际上在后台接口。这个后端接口,必须能够支持高并发,并快速处理请求。
注:前端优化处理,防止多次请求一个接口,在网络抖动或者卡顿的时候,防止多次请求一个。
注:如果redis 缓存或者jvm内存的缓存数据在ms 响应,复杂的接口进过拆分,异步也要快速响应,时刻观察qps,tps。短信验证码可以采用redis 消亡方式做短信验证。
为了实现尽可能快这一点,接口的后端存储使用内存级别的操作会更好一点。仍然直接面向MySQL之类的存储是不合适的,如果有这种复杂业务的需求,都建议采用异步写入。
在这里插入图片描述
当然,也有一些秒杀和抢购采用“滞后响应”,就是说秒杀当下不知道结果,一段时间后才可以从页面中看到用户是否秒杀成功。
优惠券这种延迟发送。比如京东都是通过jmq发送。
但是,这种属于“偷懒”行为,同时给用户的体验也不好,容易被用户认为是“暗箱操作”,能做的好话,还是通过实时响应通知,就需要对业务进行拆分和快速响应。

3、高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。
如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。可以采用提前压人奖品的方式,或者严格控制数量。
超发的原因
假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。
这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景
在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

悲观锁思路
解决线程安全的思路很多,加锁来处理请求,但是会体验不好,并容易出现死锁。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。
同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

FIFO队列思路
将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。队列可以保证不会造成额外的超额奖品数量。
然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。
或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。
也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。
使用redis 快速响应,和存储商品数量。

乐观锁思路
这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。
实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。
这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。
同样可以将奖品提前放到mysql 数据库,采用行锁。谁抢到并修改sql 成功就是抢购成功。
有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/641592
推荐阅读
相关标签
  

闽ICP备14008679号