灵魂拷问



  • 秒杀这种大并发的写场景,直接分库分表开干?

  • 应对秒杀活动的流量高峰很难吗?

  • 不要拿淘宝级别的秒杀忽悠我



秒杀活动特点



我敢说凡是做过电商的同学,都会遇到运营展开的秒杀,限时购等“高并发”的活动。市面上也有不少针对秒杀的解决方案,什么分库分表,缓存,消息队列呀,但凡能想到的技术“靓点”都基本会写上一段。我觉得应对秒杀这样的带有流量峰值的业务,还是要仔细分析业务的特性,以及根据自己系统的业务量来确定需要采用哪些技术“靓点”,假如:一个日活10万的系统,采用了分库分表,缓存,消息队列,限流,降级等等技术手段,虽然功能上达到了预期,但是其实资源上可能会有些浪费,技术上也许只需要一个限流手段就足够了。说这些不是想表达什么,我只是想说,那些上来就分库分表等“大手笔”的“优化”手段一定要根据实际业务去考察是否需要实施。



言归正传,秒杀这种业务场景其实特点很明显:



  • 带有短期流量峰值特性,即:短时间内会有大量的请求涌入

  • 请求的数据带有热点性,即:大量的请求同一数据

  • 请求的成功有效率低,即:大量的请求中可能只有少量请求会成功处理业务

  • 请求的流量峰值发生在下单之前,即:付款阶段很少存在流量峰值



静态资源



静态资源是指商品的图片,视频,音频,html页面等几乎不会变化的资源,这些资源的处理方式和缓存类似,尽量放在离用户最近的地方,比如:浏览器的本地缓存,当缓存过期的时候,优先推荐从CDN中获取,CDN是应对静态资源访问高峰的最简单粗暴,也是最有效的解决方案,如果没有CDN怎么办?那最少要把请求这些静态资源的服务器和后台业务服务器物理上分离,避免因为静态资源而影响正常的业务。比如:很早之前,我就喜欢每个项目单独一个存放图片,css,js的网站,这个网站的优势是无状态,可以做到傻瓜式横向扩展。



至于静态资源的缓存更新,我想你可以百度一下会有很多答案。



业务让步



如果负责秒杀活动的产品经理是一个优秀的产品经理的话,就不会设计出:用户点击秒杀马上给予是否下单成功,这样的系统。熟悉分布式的同学肯定会想到,想保证这样的数据一致性,在可用性上必然会有所牺牲。尤其是秒杀这样的业务,我觉得可用性要比一致性优先级要高,所以几乎所有的秒杀系统都会采用BASE理论来设计系统,一致性上采用最终一致性。在用户看来,点击秒杀按钮之后会弹出一个等待的提示,在技术上我们称之为:异步处理。异步处理对于用户最明显的感知就是不会马上得到结果,而是要等待一段时间。其实这样的设计也是在技术和业务之间的一个权衡,算是业务作出的让步。



至于秒杀之前需要输入验证码或者某些题的答案等手段,其实也可以算是业务上作出的一些让步。为什么说是让步呢?对于用户来说,最理想的秒杀场景是:一点秒杀按钮,马上给予结果,但是技术上难度太大了,所以嘛,互相让一步,大家都好过,对不对?

技术第一招:限流



对于秒杀出现的流量峰值,限流是最直接的削峰手段,被限制的请求可以直接返回,客户端提示请求中提示。可想而知,当10000/S的请求量被削成100/S的量,估计系统稍微优化一下就能抗住,至于限流的策略根据业务会有很多不同的方式,比如:



  • 针对同一个用户的请求次数限流,例如:每个用户每10秒只允许请求一次

  • 针对同一个IP的请求次数限流,例如:每个IP每10秒只允许请求一次



至于限流的算法,之前写过一篇文章来介绍,而且性能还不错哦



高并发优雅的做限流

第二招:消息队列



说到消息队列,每个程序员都不陌生,它相当于一个快速的数据容器,可以作为一个缓冲层来应对流量高峰。如果从它的使用场景上来看,它可以算是低速设备和高速设备之间的平衡者,使用消息队列来进行削峰是一个很明显的异步流程。



应用到秒杀的场景下,大量的请求会先进入消息队列,它不仅削平了流量的峰值,而且把秒杀下单的这个流程异步化,只要把请求都暂存入队列,消费端慢慢消费即可,但是这里要注意,如果消费的速度远远慢于消息的投递速度,可能会影响整个系统性能。



除了削峰之外,我始终认为消息队列的最大作用是系统解耦,它把下单和支付解耦,下单和支付业务可以随着自身系统的承载量来单独扩容。

第三招:缓存



为什么要加入缓存这个选项呢?别忘了,除了大量的用户下单这个写操作之外,还有更大量的用户请求下单结果这个读操作。当用户点击秒杀按钮之后,系统会弹出等待的提示框,很多系统是不停的去轮训用户的下单结果,我之前也写过缓存的文章,曾经提到过缓存最大的作用是提供读操作的快速响应。整个秒杀系统可以这样做:



  • 用户点击下单按钮,请求经过限流组件,如果成功,则进入下单环节(这里可以进入消息队列,异步下单)

  • 服务端无论是采用redis缓存,还是其他缓存组件,存放着下单成功的用户信息(也可以包括订单信息)

  • 客户端采用轮训的方式去查询缓存,如果查询到信息说明下单成功,进入支付环节,未查询到则说明下单还未成功

  • 服务端下单成功,往缓存中写入数据,当用户下次再次查询的时候会提示下单成功。



虽然过程很简单,但是其实整个过程中有很多细节需要注意,比如:缓存的过期时间怎么设置?能否引入下单中的状态?怎么保证缓存数据和数据库数据的一致性?



谈了千百遍的缓存数据的一致性问题



除了以上的信息数据缓存,商品的信息数据也可以放在缓存中,由于读的请求量比较大,可以考虑采用缓存副本的方式来提高整体的吞吐量。

写在最后



其实很多系统应用上消息队列+限流之后,针对秒杀业务已经足够了,其余的分库分表等方案可以根据自己的业务量来确定。每个系统在满足功能性的需求下,也在满足非功能性需求的前提下越简单越好,不是每个系统都需要淘宝的架构。