简历上面写了秒杀项目,面试怎么办?
背景
很多同学简历上面可能都有秒杀项目,但是有的同学对秒杀项目的一些细节,可能了解不是很全面,下面我就来说一下我想到的秒杀常见问题及解决方法,让大家面试能轻松应对秒杀项目的问题。
问题
1. 超卖
分析秒杀的业务场景,最重要的有一点就是超卖问题,但凡是个秒杀,都怕超卖,比如商家备货只有100个MacBook Pro,商家的预算经费卖100个可以赚点还可以造势,但是因为超卖问题卖出去200个,你不发货用户投诉你,平台封你店,你发货就血亏,你怎么办?因此解决商品的超卖问题是特别重要的。
2. 高并发
大家都知道如果真的营销到位,价格诱人,几十万的流量我觉得完全不是问题,秒杀具有时间短、并发量大的特点,秒杀持续时间只有几分钟。短时间内会有大量请求涌进来,我们需要解决缓存雪崩,缓存击穿,缓存穿透,防止由于流量过高打挂数据库。
3. 恶意请求
有很多黄牛党会有针对秒杀对应的软件,知道你什么时候抢,弄几十台机器加上脚本,可以模拟十几万左右的请求,那这样成功率就会很高了。怎样防止这类软件的重复无效请求也是需要我们解决的。
4. 链接暴露
对于小白用户来说,看到的只是一个比较简单的秒杀页面,在未达到规定时间,秒杀按钮是灰色的,一旦到达规定时间,灰色按钮变成可点击状态。但对于稍微有点电脑功底的用户,都可以打开谷歌的开发者模式,然后看看你的网页代码,有的就有URL,通过特定软件去请求也可以实现秒杀。怎样防止链接暴露也是需要我们解决的。
5. 数据库设计
秒杀每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单是做秒杀的还涉及其他的业务,如果没做降级、限流、熔断,别的业务也可能会挂。
解决
前端
1. 秒杀URL的设计
为了避免有程序访问经验的人通过下单页面url直接访问后台接口来秒杀货品,我们需要将秒杀的url实现动态化,即使是开发整个系统的人都无法在秒杀开始前知道秒杀的url。具体的做法就是通过md5加密一串随机字符作为秒杀的url,然后前端访问后台获取具体的url,后台校验通过之后才可以继续秒杀。
2. 秒杀页面静态化
秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,减少真正秒杀时候服务器的压力。
3. 前端限流
用在秒杀按钮点击以后发起请求,那么在接下来的5秒是无法点击,这样可以减少流量。
在点击秒杀按钮之后要求用户输入验证码,做一个缓冲。
后端
1. 数据库设计
针对秒杀的数据库问题,应该单独设计一个秒杀数据库,防止因为秒杀活动的高并发访问拖垮整个网站,这里主要需要两张表,一张是秒杀订单表,一张是秒杀商品表。
2. Redis集群做缓存
秒杀是一个读多写少的场景,可以使用Redis做缓存,但是单体Redis可能顶不住,这时候就需要搭建Redis集群,提升redis的性能和可用性,我们开始秒杀前通过定时任务提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀结束了,再异步的去修改库存就好了,但是要加事务,Redis本身是支持事务的,而且他有很多原子命令的,也可以用LUA脚本。
3. 解决超卖
数据库加唯一索引:防止用户重复购买;SQL加库存数量判断:防止库存变成负数:update miaosha_goods set stock=stock-1 where goos_id={#goods_id} and version=#{version} and sock>0; 这样的话,就可以保证库存不会超卖并且一次更新库存,还有注意一点这里使用了版本号的乐观锁,相比较悲观锁,它的性能较好。
4. 使用nginx
nginx是一个高性能web服务器,它的并发能力可以达到几万,而tomcat只有几百。通过nginx映射客户端请求,再分发到后台tomcat服务器集群中可以大大提升并发能力。
5. 后端限流
同一个用户x秒内重复请求直接拒绝:具体多少秒需要根据实际业务和秒杀的人数而定,一般限定为10秒。
令牌桶算法限流:令牌桶算法的基本思路是每个请求尝试获取一个令牌,后端只处理持有令牌的请求,生产令牌的速度和效率我们都可以自己限定。
6. 异步下单
为了提升下单的效率,并且防止下单服务的失败,需要将下单这一操作进行异步处理。可以采用RocketMQ消息队列,在后台经过了限流、库存校验之后,走到这一步骤的就是有效请求。然后发送到队列里,队列接受消息,进行异步下单,下完单,入库没有问题提示用户秒杀成功。如果失败的话,可以采用补偿机制,利用RocketMQ的事务性消息进行重试。
7. 服务降级
假如在秒杀过程中出现了某个服务器宕机,或者服务不可用,应该做好应对工作。可以通过Hystrix或者sentinel进行服务熔断和降级,假如服务器真的宕机了,直接给用户一个友好的提示返回,而不是直接卡死,服务器错误等生硬的反馈。
整体流程
1.登录进入商品列表页面
2.点击进入商品详情页面,静态资源缓存,Ajax获取验证码等动态信息
3.点击秒杀按钮, 将验证码结果和商品ID传给后端,如果结果正确。动态生成随机串UUID作为随机path,结合用户ID和商品ID(url)存入redis,并将path传给前端。前端获取path后,再根据path地址调用秒杀服务
4.服务端获取请求的path参数,去查缓存是否在。
5.如果存在,并且Redis还有库存,预减redis库存,看是否已经生成订单,没有的话就将请求入消息队列
6.从消息队列的接收端获取消息:包括商品ID和用户ID,判断数据库库存以及是否重复秒杀,然后下单。下单过程是:减库存,下订单,生成订单。
8.生成订单成功或者失败后,都将秒杀结果放到redis中;前端采用ajax轮询方式查询redis获取最终秒杀结果,返回给用户提示。