java面试(2)关于并发、超卖处理的思路
背景:
做电商网站,经常会有各种秒杀和热门商品,所以高并发的处理一直是电商最重要的事情。这里记录下当初自己是如何处理的!!!
设置条件:
1、本文设计到的并发处理均是针对纵向,不针对横向扩展,即只设计从PHP层面到数据库层面的处理,不涉及多台服务器,集群、大带宽等的横向设计。 2、本文中涉及到的高并发并不是淘宝京东等几百万几千万等的高并发,仅仅只是普通最多上万的并发处理 3、本文不对悲观锁乐观锁做设计
问题:普通电商中的秒杀中的并发问题,超卖问题?
实例:商品数量为100,秒杀人数为10000,整点开始秒杀
秒杀大概流程: ①商品详情点击购买(秒杀)--》②输入信息提交订单--》③进行支付
解决思路: 1、人数阀门设计 2、会员排队设计 3、问答问题设计 4、库存缓存设计 5、页面静态设计
思路理解: 一、人数阀门设计:进行用户人群过滤。 商品数量只有100份,秒杀人数有10000人,那么我们就设计1道阀门(根据情况,可以设计3道或者2道都可以的)。 在整点的时候,我们对点击了“购买”按钮后,我们只运行500人进入信息填写页面,信息填写完成后提交订单。效果如下: ①商品详情点击购买(秒杀)--》②输入信息提交订单--》③进行支付
10000人 500人 (这里也可以设计阀门,只允许多少人进入支付) 其他未进入的如何处理乃?显示已抢完或者排队等待(这就是后面要提到的排队系统设计)。
二、会员排队设计:对用户进行排队,排在前面的先购买
这相当于是消息队列模式了,如果秒杀是立即知道结果,排队可能会有点鸡肋。 在第二步②输入信息提交订单后进行排队,排在前面的先购买,排在后面的后购买
三、问答问题设计:过滤掉一些反应慢的用户 在第一步①点击购买后跳转到问题页面,用户必须回答正确问题后,方可进入后面的流程
四、库存缓存设计:缓存库存,判断用户购买的商品是否还有,不读取数据库,速度快,也不会增加数据库负担,
经过前面的过滤,超卖的可能性比较低了提前将商品库存缓存起来,到下单购买的时候,用户购买了就减1,每次都通过库存缓存判断一下,如果为0就显示已抢完。
五、页面静态设计:尽量静态缓存化【CDN那些这里不做考虑】 第一步①商品详情页面,尽量进行缓存,减轻大批量用户在访问商品页面的时候,大量查询数据库。 问答问题页面:全静态,加载快,无数据库负担。 排队等待页面:全静态,加载快,无数据库负担。 排队结束页面:全静态,加载快,无数据库负担。
小试牛刀: 上面说了那么多废话,总归在一起,流程大概就成了下面这样: ①商品详情点击购买(秒杀) --》 ②进入问题回答页面 --》③排队等待 --》 ④输入信息提交订单 --》 ⑤进行支付 页面缓存 问题过滤 阀门过滤 缓存库存减少 页面缓存 页面缓存
附件: 一、参考资料 ①、淘宝阀门设计 ②、各大电商网站秒杀流程 ③、网络上面的各种文献资料
小结:
1、在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的 必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功。 当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。
2、这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。 把你要卖出的商品比如10个商品放到缓存中;然后在redis里设置一个计数器来记录请求数,这个请求书你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。
3、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。 这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。 乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。 悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。 除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。
4、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。
5、实际应用中,并不是让mysql去直面大并发读写,会借助“外力”,比如缓存、利用主从库实现读写分离、分表、使用队列写入等方法来降低并发读写。
申明:
本人对并发处理并不深入,所知道的知识都是来源于网络资料和各种网站参考。虽然我这样的设计已经用于系统中,并且基本上解决了普通的这些问题,但是这样的设计可能存在一定的问题或者不完善,或者根本就是错误的。