如何设计一个秒杀程序及避免超卖问题

很多的电商平台,在节假日如双十一,618等都会有商品描述的活动,今天和大家讨论一下,如何设计一个秒杀系统。

什么是秒杀
在一定的时间内几秒或者几分钟,对一定数量的库存进行出卖。

场景分析:

1,在秒杀之前,比如上午十点开始秒杀,很多用户可能在九点五十左右就开始访问自己心仪的秒杀商品,这样就会出现在秒杀之前用很多的并发量,所以在秒杀之前的半个小时之前,是否可以将参加秒杀活动商品的一些信息缓存起来,这样就可以减小因为高并发访问,给数据库带来的压力。

2,有的用户为了抢到商品可能利用第三插件,去频繁的访问接口,这样给接口会带来很大的压力,为了避免这种刻意的刷单问题,可以在后台对同一个用户的访问频率做限制,可以预测接口的实际访问频率,然后对不同的接口,同一个用户做频率限制,如某个抢购入口,可以设置一个用户一分钟不能访问超过60次,10秒内不能超过20次等不同时段不同访问频率策略,这个可以通过redis等缓存框架做到,列入以某个接口+用户唯一性标准为key,可以选择value为string类型,如果缓存更多数据,value可以选择hash类型,value的值是int类型,这样用户每次访问接口,可以先判断该值有没有达到预设的访问频率限制的值,如果达到了,就告诉用户,你的访问太过频繁,请多长时间后再试,或者要求用户输入验证码(图片验证码或者短信验证码)。如果没有达到预设值,就给int值技术加一,在第一次访问的时候,即查询的前exist判断,如果不存在,插入第一条数据,并且设置过气时间,这个时间就是访问频率的时间限制例如一分钟只能访问60次,那么这里的过期时间就是60秒,预设值就是60次。这样就可以从一定程度上避免刷单问题。

3,一个用户可能当初注册了很多的账号,平时不用,专门用来参加秒杀活动,这样其实也会造成系统压力。解决方式和上述差不多,只是对ip做限制,但是怎样做可能伤到真实的用户,因为有的场合如网吧,里面人购物其实是同一个ip出口,这个时候,其实也可以对用户的等级做限制,只有多高的等级才可以参加秒杀活动。

秒杀场景的特点

  • 同一时间同时进行抢购,网站瞬时访问流量激增。
  • 访问请求数量远远大于库存数量,但是只有少部分用户能够秒杀成功。
  • 秒杀业务流程比较简单,一般就是下订单减库存

秒杀设计思路

前端页面静态化,禁止重复提交

  • 页面静态化:将前端可以静态的资源静态化。

  • 禁止重复提交:秒杀开始之后,可以对用户点击后响应前按钮置灰。

后端可拓展,缓存,限流,削峰,异步处理

  • 可拓展:服务的可扩展,可以水平添加机器将用户请求分担到不同的机器上去。数据库可扩展,支持分库分表,对于用户的请求,映射到不同的数据库,减少单台数据库的压力。

  • 内存缓存:参加秒杀系统的商品是事先可知的,可以将参加秒杀的商品信息事先缓存到redis等缓存系统中,这样可以大大的提高系统的吞吐量,减少关系型数据库的读写压力。

  • 限流: 一单秒杀开始,实际秒杀成功的用户只是库存的数量,在库存没有之后,将前端的秒杀入口关闭。

  • 削峰:数据库削峰。对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。对于关系型数据库而言,这个是致命的,是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。

  • 异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

数据库层特点

数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧。

案例:利用redis+mysql实现简单的秒杀系统

Redis是一个分布式key-value缓存系统,value支持多种数据结构,这里value可以选择两种类型,String(或者hash):主要用于记录商品的库存,对商品减库存。Set集合(这里不要用list集合,list集合是可重复的,set是不可重复的,可以保证一个用户只卖一次,如果一个用户可以买多次那么可以使用list集合):用于存储用户的id获取其他唯一确定一个用户的值。

在秒杀开始的前:可以使用批处理,将参加秒杀的产品信息缓存到redis中。这里将产品的业务唯一字段作为key,库存作为value。这里的key要和前端缓存的key一致。
在秒杀开始时::用户大量提交。根据用户提交的产品信息,获取到redis中需要的key值,查询缓存(为了保证缓存有效,如果第一次没有查询到,可以到数据库查询,然后在缓存一下,不过一般不会出现),得到库存量,判断当前库存是否大于零,如果大于零,判断当前的set集合中是否用该用户ID,如果没有,减库存并且将用户的ID放入集合中,并对库存减一,如果库存为0,提示用户,商品已售完等文案信息,如果集合中已经存在该用户id,则不做任何处理,直接处理下一个请求。直到最后库存售完,上面的过程可以利用redis事务和watch功能完成对数据一致性的控制即超卖问题
库存售完后:程序开始启动一个有个后台线程,可以阻塞等待商品库存售完的通知,在上面一步,库存一旦售完,后台进程获取set集合中的用户信息,异步处理需要操作的购买等后续操作。

这只是一个简单的秒杀系统,实际的秒杀系统,还要考虑到很多的真实场景需要很多完善的地方,希望大家一起讨论如何设计一个秒杀系统。

你可能感兴趣的:(如何设计一个秒杀程序及避免超卖问题)