之前被面试官问到如何设计一个秒杀系统。当时答的不好,后来遇到了一个小伙伴之前做过秒杀系统。经过和大佬的探讨得出了以下的一些设计技巧:
一、只用mysql的秒杀系统?
这种实现的秒杀系统就是通过查询数据库来防止超卖的发生。update t_goods set stock = stock - 1 where good_id = xxx and stock > 0。
前端页面采用java的themlef模板(不用行不行?我就用前后端分离的项目,我就要用vue可不可以,可以,这里我们为了体现后面的优化,所以才说成这个模板做的)。
几千的并发量就能够干死任何版本的单机mysql了。这就是瓶颈。
我们接下来要说的优化,就是为了解决这个瓶颈。
二、优化秒杀系统
我们一般采取两部份优化方式:(1)页面方面(2)接口方面
1、页面方面
页面缓存:
将整个页面以String类型方式存入redis里面。设置过期时间10s,如果有客户端来请求,直接返回缓存中数据,如果缓存失效,再查询数据库,填充页面生成String。
旁边的同学可能板砖要举起来了,你咋把页面变成String类型?themlef有一个resolver了解一下,是可以做到的。
页面静态化:
说白了就是前后端分离项目,什么是前后端分离项目?themlef这种模板引擎肯定不是,因为他是动态的渲染数据。
前后端分离的项目是没有其他的语言规则的,就是html页面通过js的ajax请求然后渲染数据。这个有啥好处?
对于动态渲染的页面,每一次客户端请求都需要完整的从服务端拿取数据,因为里面含动态数据不能缓存到浏览器。
静态页面可以放在浏览器的缓存中,我们不需要请求服务端去拿html页面,在浏览器缓存中直接找到对应的缓存取出,然后通过js去获取数据,进行渲染。这样的方式要比动态页面快的多。因为有些数据是不需要每次都走网络带宽的。例如那些动态页面的静态数据。如果每次都是返回完整html,那么网速会很慢,特别是在秒杀高并发的情境下,更加明显。
压缩css、js:
将css和js压缩,去除中间空白符。尽量将所有的js放到一个js文件,同理css。这样可以一次请求就获取了所有资源,不用多次请求。
2、接口方面
(1)首先前端点击下单的时候,需要填写验证码,这个可以防刷。同时用户下单之后,按钮变灰。
(2)中间可以采用hystrix或者sentinel等进行限流降级操作,不让大批量的请求进入业务程序。
(3)redis会在秒杀之前预热商品的数据,比如商品的基本信息,还会用一个字符串key,value表示一个商品的剩余数。主从+哨兵部署。
(4)当用户下单的时候去查询库存还有没有了,如果有没有,直接返回(卖完,你还买个啥)。如果有,redis的里面商品数量自减(原子操作)。然后返回给订单正排队中,添加到消息队列。
(5)异步执行到mysql刷新商品的库存。update t_goods set stock = stock - 1 where good_id = xxx and stock > 0。同时添加一个订单的信息,其中user_id的字段为唯一索引。那么当用户下单两次的时候,就会发生异常然后回滚。
注意:在这里redis中的库存量是有可能为负数的。我们只需要保证mysql中的永远不会为负数就可以了。因为即使在redis超卖,在mysql也是能够根据stock > 0避免超卖。