1 秒杀业务分析
-
正常电子商务流程
(1)查询商品;(2)创建订单;(3)扣减库存;(4)更新订单;(5)付款;(6)卖家发货
-
秒杀业务的特性
(1)低廉价格;(2)大幅推广;(3)瞬时售空;(4)一般是定时上架;(5)时间短、瞬时并发量高
2 秒杀架构原则
-
尽量将请求拦截在系统上游
传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小【一趟火车其实只有2000张票,200w个人来买,基本没有人能买成功,请求有效率为0】。
-
读多写少的常用多使用缓存
这是一个典型的
读多写少
的应用场景【一趟火车其实只有2000张票,200w个人来买,最多2000个人下单成功,其他人都是查询库存,写比例只有0.1%,读比例占99.9%】,非常适合使用缓存
。
3 秒杀架构设计
秒杀系统为秒杀而设计,不同于一般的网购行为,参与秒杀活动的用户更关心的是如何能快速刷新商品页面,在秒杀开始的时候抢先进入下单页面,而不是商品详情等用户体验细节,因此秒杀系统的页面设计应尽可能简单。
商品页面中的购买按钮只有在秒杀活动开始的时候才变亮,在此之前及秒杀商品卖出后,该按钮都是灰色的,不可以点击。
下单表单也尽可能简单,购买数量只能是一个且不可以修改,送货地址和付款方式都使用用户默认设置,没有默认也可以不填,允许等订单提交后修改;只有第一个提交的订单发送给网站的订单子系统,其余用户提交订单后只能看到秒杀结束页面。
要做一个这样的秒杀系统,业务会分为两个阶段,第一个阶段是秒杀开始前某个时间到秒杀开始
, 这个阶段可以称之为准备阶段
,用户在准备阶段等待秒杀; 第二个阶段就是秒杀开始到所有参与秒杀的用户获得秒杀结果
, 这个就称为秒杀阶段
吧。
3.1 前端层设计
首先要有一个展示秒杀商品的页面, 在这个页面上做一个秒杀活动开始的倒计时, 在准备阶段内用户会陆续打开这个秒杀的页面, 并且可能不停的刷新页面
。这里需要考虑两个问题:
-
第一个是秒杀页面的展示
我们知道一个html页面还是比较大的,
即使做了压缩,http头和内容的大小也可能高达数十K,加上其他的css, js,图片等资源
,如果同时有几千万人参与一个商品的抢购,一般机房带宽也就只有1G~10G,网络带宽就极有可能成为瓶颈
,所以这个页面上各类静态资源首先应分开存放,然后放到cdn节点上分散压力
,由于CDN节点遍布全国各地,能缓冲掉绝大部分的压力,而且还比机房带宽便宜~ -
第二个是倒计时
出于性能原因这个一般由js调用客户端本地时间,就有可能出现客户端时钟与服务器时钟不一致,另外服务器之间也是有可能出现时钟不一致。
客户端与服务器时钟不一致可以采用客户端定时和服务器同步时间
,这里考虑一下性能问题,用于同步时间的接口由于不涉及到后端逻辑,只需要将当前web服务器的时间发送给客户端就可以了,因此速度很快
,就我以前测试的结果来看,一台标准的web服务器2W+QPS不会有问题,如果100W人同时刷,100W QPS也只需要50台web,一台硬件LB就可以了~,并且web服务器群是可以很容易的横向扩展的(LB+DNS轮询),这个接口可以只返回一小段json格式的数据,而且可以优化一下减少不必要cookie和其他http头的信息,所以数据量不会很大,一般来说网络不会成为瓶颈,即使成为瓶颈也可以考虑多机房专线连通,加智能DNS的解决方案
;web服务器之间时间不同步可以采用统一时间服务器的方式,比如每隔1分钟所有参与秒杀活动的web服务器就与时间服务器做一次时间同步
。 -
浏览器层请求拦截
(1)产品层面,用户点击“查询”或者“购票”后,按钮置灰,禁止用户重复提交请求;
(2)JS层面,限制用户在x秒之内只能提交一次请求;
3.2 站点层设计
前端层的请求拦截,只能拦住小白用户(不过这是99%的用户哟),高端的程序员根本不吃这一套,写个for循环,直接调用你后端的http请求,怎么整?
(1)同一个uid,限制访问频度
,做页面缓存,x秒内到达站点层的请求,均返回同一页面
(2)同一个item的查询,例如手机车次
,做页面缓存,x秒内到达站点层的请求,均返回同一页面
如此限流,又有99%的流量会被拦截在站点层。
3.3 服务层设计
站点层的请求拦截,只能拦住普通程序员,高级黑客,假设他控制了10w台肉鸡(并且假设买票不需要实名认证),这下uid的限制不行了吧?怎么整?
(1)大哥,我是服务层,我清楚的知道小米只有1万部手机,我清楚的知道一列火车只有2000张车票,我透10w个请求去数据库有什么意义呢?对于写请求,做请求队列,每次只透过有限的写请求去数据层,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”
;
(2)对于读请求,还用说么?cache来抗
,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的;
4 大并发带来的挑战
4.1 请求接口的合理设计
一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容
,另一个就是参与秒杀的Web后台请求接口
。
通常静态HTML等内容,是通过CDN的部署,一般压力不大,核心瓶颈实际上在后台请求接口上
。这个后端接口,必须能够支持高并发请求,同时,非常重要的一点,必须尽可能“快”,在最短的时间里返回用户的请求结果。为了实现尽可能快这一点,接口的后端存储使用内存级别的操作会更好一点
。仍然直接面向MySQL之类的存储是不合适的,如果有这种复杂业务的需求,都建议采用异步写入
。当然,也有一些秒杀和抢购采用“滞后反馈”
,就是说秒杀当下不知道结果,一段时间后才可以从页面中看到用户是否秒杀成功。但是,这种属于“偷懒”行为,同时给用户的体验也不好,容易被用户认为是“暗箱操作”。
4.2 重启与过载保护
如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启
。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间
。
秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施
。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回
。
现在我们知道,电商系统所要面对的高并发压力,远高于人们的想象。特别是双十一零点的抢购,绝对是比春节微信红包更严峻的考验。红包本身更倾向于娱乐,金额普遍也不大,并且瞬时洪峰比较分散;但电商面对的都是实际的交易,容错率同样很低,而且会在零点面临极高的流量峰值。电商系统见招拆招,发展到今天,也有了充足的技术储备及实战经验。
电商的发展,直接催生了第三方支付的出现及繁荣,也就间接着孕育了互联网金融特别是P2P网贷。随着近两年来互联网金融行业的发展和调整,一些全国范围的大型平台也越来越感受到高并发所带来的压力。虽然互联网金融的业务量还不像电商那么夸张,但如何在有限的软硬件基础上提供最流畅的用户体验以及最可靠的交易服务,也是需要行业内关注的新课题。特别是在行业监管逐步落地的今天,对互联网金融平台的稳定性和可靠性,也提出了越来越高的要求。