高并发接口设计思路

2018/12/25 14:26
阅读数 8.4K
  • 并发队列的选择

Java的并发包提供了三个常用的并发队列实现,分别是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue  。

ArrayBlockingQueue是**初始容量固定**的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。

ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列入队的速度很快,出队进行了加锁,性能稍慢。

LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。

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

1. 请求接口的合理设计

一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容,另一个就是参与秒杀的Web后台请求接口。

通常静态HTML等内容,是通过CDN的部署,一般压力不大,核心瓶颈实际上在后台请求接口上。这个后端接口,必须能够支持高并发请求,同时,非常重要的一点,必须尽可能“快”,在最短的时间里返回用户的请求结果。为了实现尽可能快这一点,接口的后端存储使用内存级别的操作会更好一点。仍然直接面向MySQL之类的存储是不合适的,如果有这种复杂业务的需求,都建议采用异步写入。

当然,也有一些秒杀和抢购采用“滞后反馈”,就是说秒杀当下不知道结果,一段时间后才可以从页面中看到用户是否秒杀成功。但是,这种属于“偷懒”行为,同时给用户的体验也不好,容易被用户认为是“暗箱操作”。

高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

1. 超发的原因

假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

2. 悲观锁思路

解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

3. FIFO队列思路

那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

4. 乐观锁思路

这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

展开阅读全文
打赏
15
205 收藏
分享
加载中

引用来自“名称终被备注代替”的评论

呵呵,乐观锁是真乐观。分分钟被举报暗箱操作,内幕,为什么我比别人早的下单没成功他成功了?这种抢购啊,其实最好的方案还是雷总耍猴的定时到了直接显示余量=0售罄,只是代码啊要注意下别被人拔出来举报了。
雷军一到店,所有的人便都看着他笑,有的叫道,“雷总,听说你要出新手机了!”雷军不回答,对柜台说,“把这个月的业绩报告拿出来给我看看。”。他们又故意的高声嚷道,“你一定又耍猴了!”雷军睁大眼睛说,“你怎么这样凭空污人清白……”“什么清白?新闻都报道了在英国卖手机被抓到在网站后台做手脚!”雷军便涨红了脸,额上的青筋条条绽出,争辩道,“抢购不能叫耍,,,抢购,你们自己要来抢的事,能算耍吗。”接连便是难懂的话,什么“国产良心”,什么“黑科技”之类,引得众人都哄笑起来,店内外充满了快活的空气。
2019/01/16 14:22
回复
举报
没啦?
2018/12/28 17:44
回复
举报

引用来自“名称终被备注代替”的评论

呵呵,乐观锁是真乐观。分分钟被举报暗箱操作,内幕,为什么我比别人早的下单没成功他成功了?这种抢购啊,其实最好的方案还是雷总耍猴的定时到了直接显示余量=0售罄,只是代码啊要注意下别被人拔出来举报了。
对应雷总耍猴被抓这种啊,太低级的失误了,后端耍猴就行了,前端还是发请求,后端直接返回失败,这样就抓不到了么。为了偷懒,被抓了吧。
2018/12/28 17:16
回复
举报
呵呵,乐观锁是真乐观。分分钟被举报暗箱操作,内幕,为什么我比别人早的下单没成功他成功了?这种抢购啊,其实最好的方案还是雷总耍猴的定时到了直接显示余量=0售罄,只是代码啊要注意下别被人拔出来举报了。
2018/12/28 17:08
回复
举报
刘文肃博主

引用来自“momisabuilder”的评论

作者澄清,不是原创,复制而来,作者澄清,不是原创,复制而来,作者澄清,不是原创,复制而来,

引用来自“酸奶瓶盖儿”的评论

可以重新编辑一下,把原创标签去掉。
已修改,我非常尊重别人的成果😃
2018/12/28 11:30
回复
举报
刘文肃博主

引用来自“momisabuilder”的评论

作者澄清,不是原创,复制而来,作者澄清,不是原创,复制而来,作者澄清,不是原创,复制而来,

引用来自“慢慢成长”的评论

你可以编辑文章,在文章开头明确标识啊
已修改,我非常尊重别人的成果😃
2018/12/28 11:29
回复
举报

引用来自“momisabuilder”的评论

作者澄清,不是原创,复制而来,作者澄清,不是原创,复制而来,作者澄清,不是原创,复制而来,
可以重新编辑一下,把原创标签去掉。
2018/12/28 11:03
回复
举报

引用来自“_永无止境”的评论

不敢苟同,这种高并发的情况用乐观锁不是同一时间段大量返回失败?然后后面一波的可能还能抢到
从使用体验来说,小米貌似就是用的乐观锁,这种方案,并没有什么不妥。谁规定抢购业务只有前面进入的用户才有资格抢购,后面的就不能抢购?所谓的前端限制请求量只是在技术上做了妥协而已。
2018/12/28 11:03
回复
举报
刘文肃博主

引用来自“开源中国首席技术官”的评论

既然都声明了非原创,那还打上原创标签干嘛。。
绝对不是故意的,那我删了吧,重新来一次。
2018/12/28 10:16
回复
举报
既然都声明了非原创,那还打上原创标签干嘛。。
2018/12/28 09:59
回复
举报
更多评论
打赏
20 评论
205 收藏
15
分享
返回顶部
顶部