并发队列的选择
Java的并发包提供了三种常见的并发队列实现:arrayblockingqueue、concurrentlinkedqueue和linkedblockingqueue。
ArrayBlockingQueue是一个具有固定初始容量的阻塞队列,它可以作为数据库模块(如10项)的成功投标队列,因此我们建立了一个10大小的数组队列。
Concurrentlinkedqueue是一个由CAS原语实现的无锁异步队列。进入队列的速度很快,锁定队列后性能稍慢。
LinkedBlockingQueue也是一个阻塞队列,无论是进出队列都被锁定,当团队为空时,线程将暂时阻塞。
在请求预处理阶段,由于系统对进入队列的需求远远大于离开队列的需求,因此不会出现空队列,因此可以选择并发链接队列作为请求队列的实现
1.请求接口的合理设计
seckill或snap-up页面通常分为两部分,一部分是静态HTML和其他内容,另一部分是参与seckill的web后台请求接口。
一般情况下,静态HTML等内容都是通过CDN部署的,一般压力并不大,核心瓶颈实际上是在后台请求接口上。这个后端接口必须能够支持高并发请求。同时,尽快返回用户的请求结果非常重要。为了尽可能快地实现,接口的后端存储将与存储器级操作更好。不适合直接面对MySQL等存储。如果有如此复杂的业务需求,建议使用异步编写。
当然,也有一些秒杀和闪购使用了“滞后反馈”,即秒杀目前还不知道结果,从页面上可以看出用户一段时间后秒杀是否成功。但这种行为属于“偷懒”行为,用户体验不好,很容易被认为是“暗箱操作”。
高并发下的数据安全
我们知道,当多线程写入同一文件时,有一个"螺纹安全"问题(多个线程同时运行相同的代码,如果每个运行的结果与单个线程的结果相同),结果是线程安全(预期)。如果是MySQL数据库,可以使用自己的锁定机制来解决这个问题。但是,在大规模并发场景中,不建议使用MySQL。在杀人抢夺的场面中,还有另一个问题,那就是“超调”,如果在这方面不小心,就会造成太多的送人。我们也听说一些电子商务公司从事闪购活动。买家成功拍照后,商家不承认订单有效,拒绝送货。这里的问题可能不一定是企业是奸诈的,但在系统的技术水平上存在超限风险。
1. 超发的原因
假设在抢购的情况下,我们总共只有100种产品。在最后一刻,我们已经消费了99种产品,只剩下最后一种。此时,系统发送多个并发请求,这些请求读取的货物的剩余量为99,然后全部通过该余量判断,最终导致溢出。(与文章前面提到的场景相同)
在上面的图中,并发用户B也被“抢占”,让另一个人访问该产品。在高并发的情况下,这种场景很容易出现。
2. 悲观锁思路
解决线程安全问题的思路很多,可以从悲观锁的角度进行讨论。
悲观锁,即在修改数据时,使用锁状态排除外部请求的修改。如果您遇到锁定状态,您必须等待。
尽管上述解决方案确实解决了线程安全问题,但不要忘记我们的场景是“高并发性”。换句话说,会有很多这样的修改请求,每个请求都必须等待一个"锁",一些线程可能永远不会有机会抓取"锁",这样的请求就会在那里死去。同时,会有很多这样的请求,这会在瞬间增加系统的平均响应时间。因此,可用连接数将耗尽,系统将陷入异常。
3. FIFO队列思路
好吧,让我们修改一下上面的方案,我们直接在队列中使用FIFO(先进先出),这样我们就不会引起一些请求永远得不到锁。看这里,把多线程变成一个线程不是有点强迫吗。
然后,我们现在解决了锁定问题,并且在"先进先出"队列中处理了所有请求。所以新问题来了。在高并发场景中,由于请求较多,队列内存很可能在一瞬间突发,然后系统进入异常状态。或者大内存队列的设计也是一种解决方案,但是系统请求在一个队列内的速度不能与疯狂涌流队列的数量相比较。也就是说,队列中的请求将越来越多地累积起来。最后,web系统的平均响应时间会急剧下降,系统仍然会陷入异常状态。
4. 乐观锁思路
在这一点上,我们可以讨论乐观锁的想法。乐观锁是一种比悲观锁更为宽松的锁机制,大多数悲观锁都是用版本更新的。该实现是所有对该数据的请求都有资格被修改,但是只有在版本号匹配时才会获得数据的版本号,而其他的返回将失败。这样就不需要考虑队列问题,但会增加CPU的计算成本。然而,从全面的角度来看,这是一个很好的解决办法。
有许多软件和服务支持乐观锁功能。例如,redis的手表就是其中之一。通过此次实施,我们保证了数据的安全