个人的生命都是一只小船,理想是小船的风帆。——张海迪
前言 |
要搞定一个 “难题” 最重要的是什么?当然是要搞明白问题的核心或者说是本质。下面让我们一起揭开秒杀华丽的外表,一探它内在的本质。
1. 秒杀系统的本质
首先,回忆一下那些电商平台的秒杀:xx 日 xx 时一块钱秒杀一部手机;xx 日 xx 时两块钱秒杀一部电脑等等。不管秒杀什么,其超低的价格都会引来大量用户抢购(这就是秒杀华丽外表下的真正目的,利用人性的贪婪,达到宣传推广的目的。哈哈,但这不是我们要讨论的重点)。所以,秒杀场景具有以下特点:
需求远大于资源
瞬间并发量极大
因此,我们需要解决两个问题:
防止超卖
抗住高并发
知己知彼,方能百战不殆。了解完秒杀,我们接下来分析一下我们自身。其实每个人都是天生的秒杀高手,不记得了?这也不能怪你,当时你年纪还太小,以至于你对那次激烈程度不亚于双十一的 “秒杀” 比赛忘得一干二净。当年是如此的英勇才跑赢其他兄弟姐妹,赢得了这场世纪大秒杀,然后来到了这个世界。这下想起来了吧(如果还没有,就去翻一下中学的生物课本吧,我救不了你了)。
2. 合格的秒杀系统
接下来我们看一下,作为一个合格的秒杀系统,我们需要解决哪些问题:
2.1 核心问题
高并发
高并发现象就像是写在秒杀的基因里一样,远低于正常情况下的价格,势必会让人趋之若鹜。这是人性所致,我们无法避免,只能正面解决。如果系统没有应对高并发的能力,那么系统挂掉,会带来无法估量的损失。所以应对高并发的能力是秒杀系统的头等大事,因为系统挂了,啥也没有了。
超卖
超卖问题同样非常严重,如果秒杀的是纸尿裤,超卖个几百条倒还顶得住。但如果是手机、电脑或者更贵的物品,超卖的后果很严重。要么直接把店卖了然后给用户发货,要么耍赖让有关部门查封你。不管怎样反正你的店是保不住了。
2.2 安全问题
恶意请求
在利益的驱使下,人的 “聪明才智” 会被激发出来。有人会用程序去刷你的秒杀接口,这样会导致真正的用户基本上没有秒杀成功的可能了,人的手速再快也赶不上机器,根本不是一个数量级。黄牛的力量不能小觑,这可是一个职业化的群体呀。专业的程序 + 顶级的硬件 + 顶级的带宽,就没有他们抢不到的东西(好像明白了为什么通过 12306 买不到票,而通过黄牛却能买到)。
提前秒杀
秒杀接口事先泄露,导致商品被提前秒杀。不对呀,我在程序中设置了时间限制,怎么可能有人能提前秒杀呢?很简单,假如我知道了秒杀的地址,那么我就可以写个程序不断的请求秒杀接口,秒杀时间一到,请求会马上成功。等真正人肉秒杀的用户反应过来秒杀已经开始的时候,商品早已经是我的囊中之物了。我只需要保证可以在所有用户前面将商品秒杀即可。
2.3 独善其身
作为一个秒杀系统,一定不能因为自己而影响了主业务。秒杀毕竟是用来带人气,为主业务拉流量的。不能因为秒杀系统出现故障导致主业务无法进行。这是作为一个秒杀系统最起码的职业操守。
3. 怎么做?
我们详细分析了秒杀系统所面临的问题,接下来我们就需要对症下药,将这些问题一一化解。
3.1 应对高并发
高并发下性能瓶颈主要来自两方面:
WEB 服务
数据库
一个 WEB 容器节点的并发能力一般都在几百或几千的数量级,而一般秒杀活动的请求量都是十万级起步的。秒杀前商家都会进行大量宣传,各种营销渠道一通狂轰乱炸之后,前来捡便宜的人一定不少。甚至有些秒杀活动,根本就不需要任何宣传,都会有大量的用户一窝蜂地涌进来 —— 比如春运期间的 12306。各种抢票软件,专业抢票天团(黄牛),再加上 12306 本身的流量,高峰期达到千万级也是很轻松的。这也是直接导致前些年 12306 屡屡瘫痪的一个重要原因。
不过好在 WEB 服务扩展起来相对容易,就像孙悟空一样拔几根猴毛一吹,变出一群分身。在云服务如此完善的时代,临时增加服务节点变得异常简单。扩展出足够多的秒杀系统集群,再使用 NGINX 做负载均衡,可以搞定 WEB 服务并发的问题。
至于数据库方面,就需要我们的 Redis 出场啦。Cluster + 主从同步 + 读写分离可以解数据库之忧。这些工作做完以后,还可以进一步优化,将页面静态化,并将静态资源尽量一一放到 CDN 上缓存起来,进一步减少对服务器的请求。
3.2 解决超卖
解决超卖问题有多种方案,可以利用 Redis 的 decr 命令的原子性来做减库存操作。也可以使用分布式锁(我们前面已经学过了)来保证减库存操作的原子性。还可以单线程队列来解决。
decr 命令是对 key 值进行减一的操作,它具有原子性,执行 decr 返回减一后的值,那么我们就可以这样来判断:if (decr (productId)<0) 如果条件成立,那么就代表商品售罄,秒杀失败。而关于分布式锁的方式,可以翻阅之前的小节内容。单线程队列就更不用说了,都单线程了,超卖的问题肯定就不存在了。
3.3 消除安全隐患
上面我们提到了恶意请求和提前秒杀的问题,无非就是因为秒杀地址被提前泄露。要解决这个问题并不难。写一个简单的随机方法,将秒杀请求的地址动态化,即使系统的开发者也无法事先预知真正的秒杀地址是什么。
3.4 消除对现有业务的影响
解决方案也很简单,就是不要把秒杀系统和现有业务系统放到同一个 “篮子里”。将秒杀系统单独部署,从前端、后端、缓存、数据库,统统独立部署。这样就算秒杀系统再怎么崩溃、瘫痪,也绝对不会对现有系统造成影响。
3.5 再加固
按照上面的方案,应对一般的秒杀已经没问题了。如果你还不放心,可以继续加固 —— 限流、削峰保驾护航。限流可以挡住不能承受的那部分流量,就像早高峰地铁站的限流一样,地铁的运输能力就那么大,发车频率也已经到了极限,如果不限流,把乘客都放进站,分分钟给你填满整个候车室。而削峰有点像我们错峰上班,互联网公司一般都十点上班,为的就是错开八九点的早高峰。程序中通常采用消息队列(MQ)来达到削峰的目的。
4. 总结一下
OK,上面说了很多,接下来我们整理一下思路,先看图:
静态化 + CDN,减少服务器负担
NGINX + 服务集群,提升处理能力
Redis 缓冲,消除数据库压力
MySQL 持久化,保证数据的可靠与稳定
说明:此篇文章转载至我的老师-(刘水镜)发布的Spring Boot趣味私房课,如需转载,请标明出处,尊重原创,感谢配合。
END