小米抢购系统的成长史

 

大家对下面这个排队的场景应该非常熟悉,这个是小米手机抢购的用户排队交互图,大家看到这些排队的兔子时,说明也有很多用户在同一时间向小米抢购系统提交了购买请求。

【年度案例】小米抢购限流峰值系统架构历年演进历程

小米抢购系统后端服务面临巨大的压力,下图可以反映小米抢购系统面临的瞬间峰值压力。这张图截取2015年米粉节大秒服务后端其中一组LB(负载均衡层)的每分钟请求总数的情况(横轴的时间是UTC时间),如大家可以想象到的一样,峰值流量是普通情况下流量的近10倍。

 

【年度案例】小米抢购限流峰值系统架构历年演进历程

 

以上是小米抢购活动时后端服务集群面临的压力,小米抢购系统从2011年底诞生到成长为一个扛过2次米粉节和多次爆品首发的高性能、高可靠的峰值系统,经历了很多次大大小小的架构演进。
1、抢购系统的诞生

2011底,在小米手机首批30万全部发完货之后,接下来便是大家熟知的,每周二中午十二点,小米手机开放购买。在开放购买期间,海量的用户请求瞬间冲击过来,对于一个初创公司,特别是一个刚成立不到半年的电商团队来说,面临的挑战是巨大的。第一次开放购买,在小米商城主站进行,在瞬时压力下,整个主交易系统并没有逃脱挂掉的命运。开放购买活动是结束了,但是一周之后,下一轮的开放购买已经开始开放预约,开放购买会如期而至,为了保证下次开放购买顺利进行,留给小米网工程师团队的时间是有限的,只有短短的7天时间。

 

经过技术团队讨论,为了避免抢购时的峰值流量压垮整个小米网,决定设计一套独立的抢购系统。预约用户通过在这套独立的抢购系统中抢到购买资格,购买资格会异步的由资格数据同步模块同步到用户的购物车中,从用户抢到手机那一时刻起,在规定的时间内,用户小米商城完成下单和支付。

 

从此这版独立的抢购系统承接了小米的所有的抢购,经历过大大小小的优化与重构,最后又演化为整个小米网的限流峰值系统。这版最初的抢购系统内部代号:TD。

 

接下来简单描述下这版抢购系统设计的出发点和技术点
出发点

  1. 并发能力要尽量的高,能承受瞬时的峰值;
  2. 不能超卖(可容忍小量的超卖);
  3. 只有预约用户可以购买,一个用户只能买一台;
  4. 要非常可靠,购买记录不可丢失;

 

TD技术架构图

【年度案例】小米抢购限流峰值系统架构历年演进历程

 

小米第一版抢购系统使用的技术栈: LVS + nginx + PHP + node.js + redis;

  • 预约用户信息缓存在远端缓存集群中,一主多从结构,具体在放号的过程中,对预约数据只有查询操作;
  • 放号业务逻辑层使用PHP开发(当时小米网PHP开发者居多),使用远端缓存作为数据中心,存放用户的购买记录,同时,每放一个资格号,记录一条持久化的log(资格日志),保障用户购买记录可靠;
  • 使用node.js开发的资格日志收集及统计模块logagent,收集资格日志,并将资格日志发送到远端的logserver;同时,logagent统计自己所在的机器上购买记录的条数,由于事先均等的分配好每台机器的商品销售数量,当机器上购买成功的日志条数大于等于分配的数量时,则在本地生成一个.Lock的文件;
  • PHP开发的放号业务逻Middle集群中服务可能会辑中首先会检查.Lock的文件是否存在,来标志这台机器上的资格数是否被放完;同时,logserver将用户购买成功的资格同步到购物车及其他相关服务系统中。

 

总结
1) 可伸缩性强:PHP开发的放号逻辑不持有状态数据,状态数据存在远端数据中心或者由logagent控制生成本地文件锁(.Lock)标识,伸缩性强;
2) 数据的可靠性:抢购成功记录以日志的形式记录,保障数据可靠性;

该系统经过不间断的优化,满足了2012年及2013年上半年的抢购需求,应对的预约量在100万级别。

 

问题点
1) 单机处理能力有限:由于单机处理能力有限,如果要应对千万级别的预约量时,假设不暴漏伸缩性问题的话,需要的资源数量是否能承担的起?
2) logserver 处理能力: logserver的管理能力有限,随着抢购人数的增多,并发量的增加,当前逻辑下,每秒放的资格数量会增加,logserver是否能够及时的处理这些资格记录?

 

基于上述问题,接下来我们来看小米第二版 抢购系统,也可以理解为第一版的加强版。

 

2、限流服务助力抢购系统扛过百万并发

2013年7月份,小米发布了红米手机,并与QQ空间合作首发,1分钟内预约达到30万,半个小时预约量达到100万,72小时预约量突破500万,小米手机的预约量从此进入了千万级别,面对如此惊人的预约量,像之前分析的那样,如何在短时间内应对这个突发的量级变化呢?(红米手机发布前,技术团队并不清楚将要面临的挑战)。

 

经过技术团队讨论之后,决定在抢购系统之前加一层限流服务,将流量限制在TD能够处理的能力范围之内,由此便演进出一个新的服务——限流服务,内部代号: TC。

 

第二版抢购系统的整体架构介绍如下:

【年度案例】小米抢购限流峰值系统架构历年演进历程

 

最初的TC使用nginx+Lua开发,主要负责用户预约验证和每秒向后端放行流量控制。

  • TC在活动开始前,load预约用户数据到nginx共享内存中
  • 使用Lua开发每秒放量计数逻辑,nginx的异步IO机制加上Lua的多协程处理能力,使限流层能够达到一个很高的并发量。
  • Nginx是多进程运行,我们需要将预先限流层的每秒放行总量均摊到整个Nginx集群的进程数量上,而非按照机器数量均摊。

 

TC与TD之间以token的形式交互,用户从TC上取得放行资格,然后拿着这个资格去TD放量集群中请求放量,TD放行通过,才代表最终的抢购成功。

 

有了上述的系统结构,只要放行控制得当,再高的流量压力也不用怕了,我们也成功撑过了红米首发。

 

从此这个小米开放购买预约量进入了千万级别,红米2首发预约量更是在两千万级别。

 

尽管系统并发能力有了大大的提升,但是整个系统还有一些不足

1) 管理功能简陋:活动过程中需要人工干预活动的结束,在TD服务将商品卖完后,手动执行命令设置TC内存中指定商品的销售状态为售罄,这个误操作风险非常高,并且延迟很高,在商品售罄后不能及时的通知用户售罄状态。

2) 灵活性较弱:技术团队对于lua使用还不够灵活,TC也只能处理一些简单的业务逻辑;

3) 反应能力不足:前后端处理能力差距较大,一旦限流失误,后端很快就会崩溃,管理体系的不完善,不能瞬时响应。往往在流量高峰下,再多1秒的请求量,就有可能使整个系统发生雪崩。反应能力的不足造成,系统的可靠性下降。

 

在TC限流服务优化的过程中,我们调研了其他高并发的语言,如: C/C++、 scala、erlang、node.js、golang等。

 

从学习成本、开发效率、运维便捷性等多方面评估,我们选择使用golang开发了一版TC服务。在2013年11月,我们上线了golang版的TC服务,同时也针对这款golang版的TC限流服务和TD放号服务开发了统一的监控管理平台,由监控管理平台统一协调整个活动以及参与活动的商品的配置与状态,在整个系统流程上保证了很好的连贯性以及运营的灵活性。监控管理平台同时还负责监控各个服务的运行状态,及时报警。

 

想了解基于golang的大秒系统如何完美实现上述需求,请扫码关注以下高可用架构公众号,阅读后续发布的下篇:小米抢购限流峰值系统架构设计及案例实现

【年度案例】小米抢购限流峰值系统架构历年演进历程
标签: