互联网秒杀业务架构设计

原创
2015/11/11 10:52
阅读数 1.6K

#0 系列目录#

#1 抢购业务介绍# 我们常见的抢购业务分两种: 限时抢购、限量抢购,我简单分析了下这些case,如下图:

输入图片说明

输入图片说明

想必小米的抢购运营的最火爆了,每发一款新品,都限量发售,每次搞的大家心里痒痒的。记得之前还因为抢购太火爆,站点打不开,崩溃了。那么问题来了:为什么抢购总是引发RD、OP恐慌?我理解是,爆品太火爆,瞬时请求太大,导致业务机器、存储机器都在抢购高峰时扛了太多压力。那么,我们今天以一个抢购业务场景为例,看看如何扛住压力,做好抢购业务!假设,这时候我们接到了产品层面的需求,如下图:

输入图片说明

#2 抢购项目设计# 通过我们先行的抢购需求分析,我们画一个粗略的流程图,如下:

输入图片说明

我们将自身简单划为两部分:业务层、数据层,并且旁路设计一个“运营控制”环节。当然,数据源自第三方嘛,我们的数据层基于第三方资源数据构建。

这时,我们来看看这个草图里几个库和几个数据流,是怎样的。

首先,看看库。

数据层的“商品库”,显而易见,用于存储第三方商品数据,通过第三方推、我们拉的方式来构建这个数据库信息。

数据库层的“抢购计划”库,主要由旁路的“运营控制”环节产生的数据,由运营同学来维护抢购场次、商品数量。

业务层的“抢购库”,其实是商品库的子集,由运营同学勾选商品并配好该商品放出多少用于抢购,发布到业务层面的抢购库中。

业务层的Transaction Data,一会我们讲到与第三方对账时候,我们再说它。

##2.1 如何解耦前后端压力##

  1. 让我们业务的压力,不会传递到资源方,避免造成资源方接口压力同比增长。所以,我们自己建了商品库,此时,第三方笑了。

  2. 业务层与数据层解耦,我们让抢购库位于业务层,让商品库位于数据层。因为我们可以想象到,抢购高峰来临时,查询“商品还有没有?”的请求是最多的,若“有没有”这种高频请求每次都去数据层,那我们其实就将业务、数据耦合在一起了,那么,就有了抢购库这个子库,在业务层抗压力。(这里可以明确的是,数据层的商品库为关系型存储,业务层的抢购库为nosql的)。

有了业务层的nosql(我们就用redis吧)抗高频压力,数据层的商品库笑了。

这里就可以抛一个思想了:我们的架构设计中,需要分解压力,在互联网项目中,来自于用户的大流量不少见,这些流量最终都会落到一个地方,就看我们的设计如何分解这个压力了,如何避免它层层传递。抛个case,我们的水平分布业务机器,也是考虑通过水平扩展实例的方式,来分解大流量压力。

不扯概念的东西了,我们回归我们的抢购业务。

有了简单的分层设计,解决的大家都担心的压力问题,我们就看看抢购业务的时序是怎样的。 我们的时序图分两个视角来说明:

  1. 商品的角度:

**商品角度的时序图,从左到右:资源方、数据层、旁路-运营控制层、业务层。如下图: **

输入图片说明

录入商品即商品从资源方发布到我们的数据层,形式可以是通过API、可以是通过文件传输、可以是我们去拉去。通过我们的代码逻辑,记录到我们数据层的“商品库DB”。

有了自建的商品库的数据,我们的运营同学就可以基于商品库设计每天的抢购场次(此事就有Web界面的事情,这里我们就不展开这块了),运营同学创建好一批抢购场次,记录在数据层的“抢购计划”这一关系型数据库中

运营同学创建完抢购场次后,没完事,还得应产品需求,基于商品库,配置每场抢购场次中覆盖的商品,及商品的数量。这些抢购场次内的商品配置,会简单的记录在业务层的“抢购库”中。(抢购库记录的信息较为简单,例如商品库中ID为123123的商品有100件,业务层的抢购库中只存ID 123123商品运营配了在第X场抢购中有5件)。

此时,数据层的商品库有了资源方数据、数据层的抢购计划库中有运营配的抢购计划,业务层的抢购库中每场抢购活动中商品的情况。

那么,业务层此时就可以基于时间,来展示运营配的抢购场次了。业务层,如何展示,这块就是拼装数据、前端效果了,这里也不展开了。

  1. 用户的角度:

假设此事某场场次的抢购活动已经开始,我们再看看用户角度的时序图:

输入图片说明

用户点击某个商品的抢购按钮,业务层代码首先去看看抢购计划库此时是否开始(此步可缓存、也可cache在前端页面或Client,若有cache的话,此步可忽略)。若抢购在进行中,此时业务代码需要查询商品在本次抢购中的库存还有否(高频请求,即图中“争取名额”阶段)

“争抢名额”这块,一会我们细讲,先把时序图说完。

若用户抢到了名额,就允许用户跳转到第三方的支付页面产生消费。(此时第三方笑了),产生消费后,第三方自己的库存-1,并且可以实时、异步、完事对账的方式通知我们。

##2.2 如何保证商品库的库存可靠## 我们其实是将商品库的子库前置在业务层抗压力。那么,如何保证大家的库存情况稳定,不会因为抢购业务,导致库存波动影响用户体验。这里就需要提一个业务RD需要关注的问题,需要做好取舍。要么,我们保证大家看到的库存规律一致,要么,我们保证单个用户看到的库存规律一致。若保证大家看到的库存减少的规律一致,且同一时刻库存大家看到的库存都一样。这就对系统有数据强一致性要求,需要很大成本,还只能逐渐逼近此要求要求的效果。而我们若选择后者,仅保证单个用户看到的库存减少规律一致,虽放弃了数据强一致,但以更少的时间尽可能实现了最好的效果。所以,我们用到了用户来排队,若抢到名额了,在抢购库中的库存 –(减减),这样单用户操作期间,能看到规律的减少,不会出现此事看剩10个,一会看还有11个的情况。这时我们说如何内部排队如何来控制“查询商品在本次抢购中的库存还有否(高频请求)”这个高频请求。

输入图片说明

我们构建商品维度的cache,上图中虽然说是“队列”,我们可以用redis的list来真正实现个队列,也可以通过/–来实现。

假设商品A,运营配了20件,此事来了N多用户的请求,业务代码都会来查询cache_prefix_a_id这个队列的长度若队列长度≤0,则有权去–(减减)抢购库的商品库存若队列长度在20件内,则通过业务代码内的等待来等待队头的位置,然后获得抢购权限若队列长度太长,则可以直接返回,认为商品已被抢空

这时插入一个运营配库的时序,便于大家理解。该时序图有详细的说明和标注,就不展开了,如下图:

输入图片说明

此时,我们可以想象,若上游用户的请求压力是N,这个N会压在业务层的抢购库,俗话说“责任止于此”。

##2.3 如何和第三方多方对账## 那么,我们回顾目录“如何和第三方多方对账”? 这里就要提到“Transaction Data”这个库了。

Transaction ID为用户维度的Session记录,用户从进入抢购业务开始,产生一个Transaction ID,该Transaction ID生命周期截止到用户跳转去第三方支付为止。期间在生活服务中产生的浏览、抢购行为均会挂靠到该Transaction ID之下,并会在跳转去第三方支付页时携带该Transaction ID凭证。最主要的是需要记录下:用户获得商品名额后,跳转去第三方时,这一行为

考虑到Transaction ID为抢购业务中,用户操作行为的关键字段,值需要保证唯一。故此处可以采用发号器之类的能力。

我们构建的Transaction Data记录,就可以按照DailyRun的方式,与第三方对账,来fix两方数据库库存不一致等问题。

为什么会产生我们和资源方的库存不一致,可能是因为用户在第三方消费后,第三方callback我们时候失败造成,也可能是因为用户跳去第三方后并没有真正支付,但我们的商品库、抢购库的库存都已经减少造成的。原因可能有很多,对账机制是必要的。

#3 项目总结# 最后,我们回顾回顾设计,压力问题在业务层解决了库存不一致问题我们通过对账机制解决了产品的需求我们也通过旁路可配解决了,嗯,可以喝杯茶,发起评审,评审通过后开始写代码了。

展开阅读全文
打赏
0
15 收藏
分享
加载中
请问一下,至于秒杀队列这个,如果我采用单台服务器自己独立维护一个队列,不采用同一个队列的方式来做,会有什么问题?后续预扣库存和其他操作都是采用 redis 的分布式锁来做
2018/05/03 18:16
回复
举报
陶邦仁博主

引用来自“linapex”的评论

总的意思算是看明白了,全靠redis来抗起
redis只是其中一种方式,具体实现要看具体秒杀业务需求。
2016/05/03 08:12
回复
举报
总的意思算是看明白了,全靠redis来抗起
2016/05/02 14:19
回复
举报
更多评论
打赏
3 评论
15 收藏
0
分享
返回顶部
顶部