描述一个高性能高可靠的网站架构——如何设计一个秒杀系统
一、秒杀的应用场景
电商网站的抢购活动、12306网站的抢票、抢红包。
二、秒杀的特点
1、秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。
2、数据库的并发读写冲突以及资源的锁请求冲突非常严重。
3、秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。
三、秒杀架构的原则
1、将请求拦截在系统上游,降低下游压力:秒杀系统特点是并发量极大,请求都压倒了后端数据层,但实际秒杀成功的请求数量却很少。所以如果不在前端拦截很可能造成数据库读写锁冲突严重,并发高响应慢,甚至导致死锁,最终请求超时。流量虽大,下单成功的有效流量甚小。
2、利用缓存:利用缓存可极大提高系统读写速度。
3、消息队列:消息队列可以削峰,将拦截大量并发请求,后台业务根据自己的处理能力从消息队列中主动拉取请求消息进行业务处理。
四、秒杀的系统架构
浏览器端(秒杀页面)→ 站点层(网关)→ 服务层 → 数据库层
五、秒杀架构的设计
秒杀系统基于如下原则进行数据分层校验:
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、使用消息队列,拦截大量并发请求,后台异步处理,减轻服务器压力。