秒杀系统是日常系统开发过程中经常遇到的场景,那么如何可以准备哪些措施来保证秒杀过程中系统的可用性以及一致性呢?
秒杀活动,需要满足各方的需求
-
作为用户,希望能够抢到自己中意的优惠
-
作为商户,希望券不超发,系统运行稳定
-
作为微信支付,希望能通过每周末活动提高业务口碑,同时保证资金安全
这意味着,我们的目标是高并发场景下保证系统的高可用以及数据的一致性
1. 秒杀活动面临的挑战
整个流程包含支付有优惠、微信支付营销系统、微信开平消息推送、以及大量的商户系统 因此我们主要以下痛点:
-
有优惠活动参与用户多,请求量会有激增
-
秒杀活动轮次多,每轮展示奖品内容多
-
兑换流程长,涉及多个系统、多个服务
-
商家券最终依赖商户系统,商户系统发券失败影响微信支付口碑,甚至引起客诉
因此我们的系统主要面临着一下的挑战:
-
挑战1: 系统高并发场景下高质量、稳定支持秒杀活动,同时保证所有下游商家系统也能平稳支撑
-
挑战2: 奖品不超发,在支付有优惠、营销系统、商户系统之间的最终一致性
-
挑战3: 服务上线后,需要做好BCP建设
2. 挑战1-可用性建设
痛点1: 秒杀活动轮次多,每轮展示奖品内容多(超过100个)
我们每周末都会有多个场次,每场展示的奖品超过100个,每个奖品都有大量的素材、图片等信息,同时还需要判断用户是否已经参与过秒杀、兑换过每个奖品,这里如果每次都实时拉取,毫无疑问会增加后台的负担。那么我们能不能将这些信息缓存下来,等来用户请求的时候,我们直接从本机读取即可,而不需要发起RPC,调用存储。
破局1: 设计合理的缓存方案(查询峰值30W/min)
-
我们可以区分出静态数据(活动配置、会场数据、奖品配置)和动态数据(用户兑换数据),针对静态数据(变化频率极低的数据)我们可以提前缓存到本地,而动态数据,因为需要实时计算,这里不进行缓存。这样我们就可以减少大量的RPC请求,从而达到保护系统的目的。
-
但是这些静态数据仍然可能需要更新,因为运营同学可能需要修改奖金、额度等一些信息,因此我们需要有机制去更新缓存。更新机制一般分为同步和异步,这里我们采用一个daemon进程定时从存储组件中获取静态数据信息,然后写到本机的memcache中,同时过期方式设置为永不过期。这样系统就不存在缓存雪崩、缓存穿透问题。从而提高系统稳定性。
-
同时针对用户的兑换数据,我们存储在TTLKV中,设置过期时间为活动的结束时间,同时每10s更新一次
如上方法,就能够解决数据查询的问题
解决了看的问题,接下来我们需要解决抢的问题
痛点2: 秒杀活动瞬时请求量大(峰值20W/min)
秒杀活动一定会出现激增流量,因此我们需要想办法让用户的秒杀请求陆续到达后台,这样就可以通过前端保护后端的方式,提高系统的稳定性,使得用户可以正常秒杀奖品
破局2: 流量削峰
增加用户维度限流和接口维度限流,降低流量,达到保护商家,保护系统的目的
痛点3: 商户系统不可控(每周末3场秒杀,涉及3个商户,普通奖品涉及商户更多)
商户系统属于外部系统,而发券其实强依赖于商户。
如果是同步发券,我们系统就会把所有流量打到营销系统,再由营销系统透传到商户系统,但是商户系统可能无法支撑这么大的请求量,最后引起商户系统雪崩,而用户就无法及时获得商家券,最终引起客诉。所以我们要避免这种情况。
破局3:异步发券
-
因为我们可以将发券操作变为异步发券,以3000/s的QPS将发券请求发送到营销系统。然后营销系统再将发券请求通知到商户,这样商户系统的最高流量就变成了可预估的流程,从而达到保护商户的目的,降低潜在的客诉风险。
-
所有参与秒杀的商户都必须配合联合压测,并提供压测报告,如压测结果不符合预期,我们可以更换商户,换用系统能够符合要求的商户参与活动
3. 挑战2-一致性建设
接下来我们看看如何提高系统的一致性。 在整个秒杀活动中,我们需要保证以下目标:
目标
-
奖品不超发
-
用户不多抢(一轮只能秒杀一次)
-
有优惠系统、营销系统、商户系统数据一致性
那么我们需要如何做到以上的目标呢?
这是个类似分布式事务一致性的问题,我们调研了多种方案,包括代金券系统,消费券系统,行业权益系统等,这里列出了2个:
-
一个是代金券系统的方案,代金券要求在扣除券库存之后,记录预发记录,还要扣除用户限额也就是自然人限额,它是通过mysql事务,保证全库存与预发记录的一致性,而用户限额扣除后置,非原子操作,极端情况下可以突破自然人限制。
-
另一个方案是行业消费券的方案,消费券先预扣库存,再扣用户限额,在提交库存之后将订单落MQ,交由MQ处理后续事宜。如果有失败,会执行库存回滚。如果库存预扣、提交和回滚有任何不可预知失败,都会抛一个MQ事件,交由MQ驱动数据一致。为避免MQ故障,通过对账服务对出MQ未处理到的部分,再进行处理。
我们的场景有些不一样,我们这里因为包含以下的额度: 活动场次限额、奖品限额、用户账户(兑换奖品需要金币)。而其中活动场次库存由秒杀服务处理,奖品库存、用户账户都是由兑换服务处理。
实现
-
通过秒杀服务直接扣库存(Quotakv),这样可以保证奖品不超发,这里没有二阶段的原因是,会增加系统的复杂性,只需要保证不超发,不保证少发。
-
然后设计合理的幂等ID来保证一个用户只能参与一次秒杀。
-
通过接入事件中心的方式来保证发券的成功,可以利用事件中心的反查机制,重试用户发券的流程,防止中间任何一步出现问题,保证发券成功
-
同时对账机制,保障支付有优惠系统和营销系统的数据一致性,如果出现单边账,通过补发机制打到最终数据一致
3. 挑战3-BCP、安全建设
虽然前面我们已经做好了各种可用性的保证,但是等到服务正式运行到过程中,仍然可能出现各种各样的可能会发生的情况,因此我们要对这些这些可能发生的情况准备好各种预案,降低系统风险,
主要包含以下几点:
-
QuotaKv故障,我们可以通过秒杀服务按照机器数量,在每台机器上预先加载部分库存,本地库存消耗完毕后,再调用quotakv消耗远端库存,并更新本地库存
-
TabeleKV故障,可以通过挂公告安抚用户,奖品24小时内陆续到帐
-
秒杀服务故障,可以通过跨园区多副本部署,降低故障概率
-
可靠事件中心故障,通过异步发券的方式,让MQ进行重试,同时接入双链路事件中心,降低故障概率
同时我们也应用STRIDE安全模型,针对系统可能存在的安全风险制定合理的安全消减的方案,保证业务的安全性
4. 秒杀系统总结
总结下来,我们采用了如下策略应对相关的挑战,使用一张图概括
可用性
-
并发控制:接入限频组件,保证系统处理请求数量在可控范围内
-
幂等重试:设计合理幂等方案保证接口可重入
-
过载保护:系统不可处理时,快速拒绝
-
流量削峰:前端保护后端,流量打散请求
-
灰度策略:针对银行立减金奖品,只对定向人员进行展示
一致性
-
基本可用:及时将结果展示给用户,降低用户不安情绪
-
最终一致性:同步变异步,降低快慢系统之间的耦合
-
对账补偿:通过对账发现数据不一致的地方
BCP
-
降级预案:准备降级预案,应为未知风险(屏蔽入口,暂停活动,秒级生效,用于黑天鹅事件事中处理)
-
公告预案:准备公告方案,及时安抚用户情绪