就在两天前,百词斩自己开发的商城第一次加入到了双十一的狂欢之中。老王也第一次作为技术人员参加了这次活动(虽然加班到很晚,但是很开心~~)。在这次活动中,我们推出了很多好玩儿的项目,比如:抽奖、打折等等。但从技术的角度讲,这次活动里最有意思的,却是:秒杀。那这次秒杀到底遇到了什么样的问题,我们又是怎么样解决的呢,请跟老王一起来吧~
秒杀,这个变态而又好玩儿的电商营销手段,产生了很多经济的效益,但是却给技术带来了很多麻烦和压力。一到整点,成千上万的用户就跟洪水一样蜂拥而至,让码工和服务器饱受压力和摧残,真的很想把最初设计这个模式的产品经理暴打一顿。
我们这次的秒杀也是一样,从早上10点开始到晚上,每两个小时的整点,用户就可以来我们的商城用9.9元的价格抢购价值100多块的商品,名额为50个。老王在加入百词斩的时候,就想着如果哪一天百词斩也要秒杀了,我们的系统应该如何设计?这个问题在脑子里反复想了很多很多次,方案也想了几个,最终有一个认为是比较完美的方案,反复推演也没有问题。不过这次活动的时候,老王在负责另外一个业务,最终实现是由我的同事tt和yx来设计和落地的。
之前和tt讨论方案,说了我的那个方案,确实从技术上来讲是OK的,不过实现略复杂。tt说整个秒杀,我们的用户应该在千级规模,如果到时用户涌进来,直接用数据库硬抗也是没问题的(对aliyun的RDS比较信任),这样实现逻辑更简单。我当时想了想,觉得也有道理,如果实在不行,加个缓存应该也不成问题,于是就没有坚持我的那个方案。然后,就用最直接和简单的方式来实现了。
11.11上午十点,秒杀正式开始。我因为前一天晚上跟着加班弄的比较晚,早上就起晚了些。到公司的时候,第一轮秒杀已过。不过看到tt和yx的表情,就觉得似乎不妙。他们告诉我第一轮秒杀,有一定概率用户出现502错误。然后猜测是阿里云slb(类似入口的nginx服务)最大流量设置限制了,超过限制的部分被拒绝了(因为看日志,高峰后段就没有请求到我们的服务了)。于是将这个限制调高了,这个设置改了应该就没问题了。
到12点,第二轮秒杀开始。这个时候又有用户反馈502,我自己也有概率刷到502。这个时候证明之前的猜测是有问题的。于是,我把服务器日志打开,看到我们的nginx有499错误,这个说明slb连接我们的nginx有超时,把连接关掉了。再接着看我们java逻辑服务器的日志,发现后面的请求处理越来越慢,从几十毫秒到几百毫秒,再到一秒多,最后到好几秒。这就解释了十点那一场秒杀的疑问:slb请求nginx,nginx将请求转发到java服务器。由于slb设置了连接超时,而我们自己nginx的超时设置的比较长。当java服务器处理变慢以后,slb就出现连接后端超时,返回502错误。并且标记后端服务为不健康,后来的请求就直接502拒绝。而这个时候,后端java程序已经将先前的请求都处理完毕,但没有新的请求。看起来就像是slb流量超过限制从而拒绝了。
现在问题定位了,就是java这端处理慢了。然后tt细查日志和代码,发现是数据库瞬时并发压力太大,造成读写冲突严重。于是,做了两个层面的优化:
1、将数据库所有的查询做了优化,将该加的索引都加上,该强制使用force index的地方都做了强制;
2、程序代码加了缓存,超过秒杀数量的,直接逻辑拒绝,减少数据库操作。
经过临时修改,基本将问题修复。这个时候,差不多到下一个秒杀点了。
14点秒杀开始。刚刚的修改果然有效果,绝大部分请求都ok了,还有很小部分的请求偶尔会出现502。仔细分析,还是有些数据库操作很慢(大量并发+读写冲突)。于是tt将代码又做了优化,减少数据库操作,同时添加了服务器。后面基本上就没什么问题了。
这个事情过了,回过头来反思,我就跟tt和yx说:我现在很后悔,如果回到当初,我坚持之前我想的方案就好了。应该不会出现类似的问题。那老王想的方案是怎么样的呢?
秒杀最大的问题,就是瞬时压力太大,会对存储系统造成瞬时冲击,产生大量的写操作。如果是读操作,我们可以通过增加数据库从库来解决,但是大量写操作,确实很难优化。要解决他,最好的方法就是将瞬时压力平滑,分摊到一个更长的时间段,这样我们就可以轻松应对。那么怎么样才能平滑这个瞬时压力呢?
如果我们能将用户的提交变成异步的,那是不是就可以了呢?具体怎么操作呢?
1、用户提交信息的时候,我们将用户的请求进行排队,然后返回给用户,说这个提交请求是成功了,不过呢,系统正在处理,你要稍微等一下;
2、客户端(浏览器)每隔一段时间(比如500ms)去服务器查询一下,秒杀状态是否成功;
3、排队的请求被后端系统逐一执行,去更新存储系统(数据库);
4、执行完更新以后,更改用户秒杀状态。
这样,我们就将原来卡在服务器的压力,转换到客户端去等待,同时将瞬时的更新压力,平滑到更新队列里。如果数据库很慢,那这个操作就慢慢执行。只要在几秒钟内能够执行完毕,对于用户来讲,就没有太差的体验。
这里有一个前提条件,就是提交排队的操作要非常快,尽可能服务更多的请求,这样就不会卡住请求。所以,我们在这里引入Message Queue。MQ有几个好处:
1、能保证提交的顺序;
2、提交的处理最差能在1-2个毫秒内完成,每秒能接收大量的请求;
3、异步处理,不会同步卡住服务。
老王之前写了一个MQ,在线上已经运行了2年多了。之前测试的性能,单MQ大概能处理每秒1万次左右的请求。如果单MQ不够,还可以用多MQ方案。所以,一秒钟几千次提交是比较轻松的。
而查询的时候,也可以做优化。数据库用主从方案,updater更新主库,从库从主库那里同步,提供查询服务。这样,主库就不会出现大量读写冲突,而从库可以提供更快的查询服务,单库每秒几千次请求是不成问题的。而且增加从库就能服务更多请求。
通过上面的技术架构,理论上基本能推导出秒杀系统不会因为大量用户出现问题。即使用户增加,我们也可以通过增加机器和服务部署来满足和应对更多的请求。
好了,以上就是老王这次双十一的感受和想法。接下来,我们就会去实现这个秒杀系统。如果有更好的方案,欢迎跟老王交流哈~