1、先做数据的动静分离;
2、将90%的数据缓存在客户端浏览器;
3、将动态请求的读数据Cache在Web端;
4、对读数据不做强一致性校验;
5、对写数据进行基于时间的合理分片;
6、对写请求做限流保护;
7、对写数据进行强一致性校验。
也就是:
1、把大量静态不需要检验的数据放在离用户最近的地方;
2、在前端读系统中检验一些基本信息,如用户是否具有秒杀资格、商品状态是否正常秒杀是否已经结束等;
3、在写数据系统中再校验一些如是否是非法请求,营销等价物是否充足(淘金币等),写的数据一致性如检查库存是否还有等;
4、最后在数据库层保证数据最终准确性,如库存不能减为负数。
六、优化细节
1、秒杀系统独立部署
将秒杀系统独立部署为集群模式,可以避免短时间内的大访问量对现有网站业务造成的冲击,如果需要还可以使用独立域名,使其与网站完全隔离。这样即使秒杀系统崩溃了,也不会对网站造成影响。
2、数据预处理
1)活动页面,有很多视频和图片资源,这些静态资源提前部署在CDN上。
2)将商品本身的属性信息(商品描述、参数、秒杀规则等)这些数据事先存储到数据库中,在容器加载服务启动时,直接加载到本地缓存中当作只读数据,这样可以加速用户访问速度。
3)增加带宽。
3、前端
1)浏览器端
① 禁止重复提交:用户提交之后按钮置灰,禁止重复提交;
② 页面静态化:将活动页面上的所有可以静态的元素(商品描述、参数、详情等)全部写到一个静态页面,不用进行程序的逻辑处理,不需要访问数据库,并尽量减少动态元素。
③ 减少HTTP请求数:主要是合并CSS、JavaScript、图片等。
④ 用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流。
⑤ 动态生成随机下单页面的URL,避免直接下单:秒杀的游戏规则是到了秒杀才能开始对商品下单购买,在此时间点之前,只能浏览信息不可下单。而下单页面也是一个普通的URL,如果得到这个URL,不用等到秒杀开始就可以下单了。为了避免用户直接访问下单URL,需要将URL动态化,用随机数作为参数,只能秒杀开始的时候才生成。
2)CDN加速
3)反向代理
4、后端
1)站点层(网关层)
针对同一个访问uid,限制访问频率,做页面缓存,几秒内到达站点层的请求,均返回同一页面。
2)服务层
利用缓存应对读、写请求:
大部分请求是查询请求,可以利用缓存分担数据库压力。
对于写请求,可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把redis中的用户秒杀请求同步到数据库中。可以采用Redis的list数据结构,把每个商品作为key,把用户id作为value,队列的长度就是库存数量。对于每个用户的秒杀,使用 RPUSH key value插入秒杀请求, 当插入的秒杀请求数达到上限时,停止所有后续插入。然后根据先进先出,使用 LPOP key zhuge读取秒杀成功者的用户id,再操作数据库做最终的下订单减库存操作。
② 异步操作,使用消息队列:把请求写到消息队列中,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
③ 使用集群:在网站高并发访问的情况下,使用负载均衡技术为一个应用构建一个由多台服务器组成的集群,将并发访问请求分发到多台服务器上处理。
3)数据库层
mysql批量入库提高insert效率。
七、关于超卖
秒杀带来的问题一个是高并发,另一个就是超卖,售出数量多于库存数量。解决超卖问题的方案有两种:
1、采用乐观锁,也就是采用带版本号(Version)更新。
实现的流程为:这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。
2、尝试扣减库存,扣减库存成功才会进行下单逻辑。
UPDATE table_name SET n=n-1 WHERE n>1;
扣减库存后进行检查,保证减完不能等于负数。
八、总结
1、将秒杀系统独立部署为集群模式,包括服务器、数据库、缓存等。
2、页面内容静态化,把一些不需要变化的内容写到静态页面,这样不用请求服务器、访问数据库,减轻服务器、数据库的压力。
3、将一些数据事先存储到数据库中,在容器加载服务启动时,直接加载到本地缓存中当作只读数据,加速用户访问速度。
4、增加带宽,将所有静态资源部署在CDN上,减轻网站服务器的压力。
5、使用redis,提高读写速度,分担数据库压力,缓解网站压力。
6、使用消息队列,拦截大量并发请求,后台异步处理,减轻服务器压力。
[参考资料]