学习抢票下单流程设计与优化

以B站票务抢购下单流程演进

1.背景

bilibili 会员购票务目前业务覆盖全国绝大部分2次元及2.5次元的展览、演出等项目。比如展览方面,有B站自己主办的BiliBili World和各种协作承办的漫展等。目前会员购票务在漫展垂直的市场率在行业中处于TOP级领先地位。bilibili 会员购票务提供多种业务形态,包括:

  1. 抢票功能:帮助用户抢购热门活动票,避免黄牛抢购风险场景发生。

  2. 电影票:提供便捷的电影票购买通道,接通站内电影营销。

  3. 选座功能:支持用户自主选择座位。

  4. 检票工具:提升现场入场效率,体现平台服务独特性

  5. 结算:业内高效、快速对账结算体系。

这些服务为用户提供便捷的票务体验,涵盖了从购票到入场到结算全流程需求。

2.目标

对于一个热门的抢购项目,用户的基本访问链路为:

项目列表/收藏 ==> 商品详情 ==> 选中对应的场次以及票种 ==> 结算页补充下单信息 ==> 下单。

在上述链路中,其中项目列表以及商详基本都是一些固定信息(商详页面的是否售完,用户也可以接受短期的延迟),所以这部分流程基本可以通过缓存去做处理

而大部分用户在结算页补充完个人信息之后,会对于创单接口进行多次且频繁的访问。下单接口流量大、实时性要求高,且直接影响交易收入转化,所以保障下单接口稳定很重要。

围绕流量峰值承载、数据库压力优化、用户等待时长缩短三大核心目标,我们对下单链路进行了三阶段迭代。

第一版 - 同步事务处理

在票务从0-1的过程中,为了增加市场的占有率,需要实现功能的快速迭代,所以对于方案的设计要以基础功能为主。

方案

流程逻辑:用户请求实时同步处理库存扣减与订单写入。

效果

初始版本过程中,足以应对普通流量下的用户下单需求,但如果是抢购的场景,大事务以及库存DB的单行扣减问题的会影响到接口的性能以及接口响应时间,甚至出现库存死锁等问题。从而导致服务的雪崩。

✅ 高并发下大事务阻塞——DB连接池耗尽,响应延迟飙升。

✅ 单行库存热点——InnoDB行锁竞争引发死锁,扣减失败率超30%。

✅ 无弹性扩展能力——服务雪崩风险显著。

第二版-异步下单 - 异步削峰

针对于抢购的场景,为了避免出现因库存以及大事务场景下影响用户下单,衍生出了异步下单的版本:

方案

流程逻辑:解耦请求接收与事务执行,引入异步分批处理。

🔹前端交互:用户获取下单Token,轮询查询结果(平均等待5-8秒)。

🔹后端处理:

✅ 库存批量冻结:合并SQL减少DB操作频次。

✅ 优惠券并行校验:拆分耦合模块提升吞吐量。

针对于用户下单接口中的容易导致DB出现异常的模块,进行异步削峰处理==>将用户下单请求以及实际下单接口,拆分为两个模块处理,并且将下单批量化处理,减少DB的操作次数,降低DB的风险。

此时用户下单分成了三个步骤:

  1. 用户下单,获取下单标识(唯一token)

2. 定时任务,批量下单,将之前的单条库存扣减、订单插入修改为进行批量冻结库存,并行冻结优惠券,批量合并sql插入数据库,最大限度上减少性能消耗

3.C端在下单页新增轮询接口根据唯一token轮询下单结果

效果

此方法很大程度上解决了数据库压力问题,但由于前端沦胥,用户体验感下降,因轮询导致阻塞等待。

第三版-redis缓存扣减库存下单

异步下单已经能够解决大部分的热门项目的抢购问题,但是从2022年之后,漫展的市场热度出现了巨大的变化,在抢购之前预估流量为平时流量的2倍,然而实际结果是比预估的流量大了10倍不止,紧随其后的BW的抢购更是说明了异步下单已经不能保证一些热门项目的抢购, 对于下单接口需要进一步的改造。

在之前的异步下单链路中还是存在几个问题

  1. 前端轮询下单结果,会有一个较长时间的等待。用户体验感差

  2. 支付回调流量不可把控,如果支付回调QPS过高,也会导致库存单行扣减压力

  3. 整个流程都是串行处理,如果下游接口响应耗时过高,会导致服务雪崩问题。

所以还需要对下单链路进行处理

  1. 库存单行扣减优化

  2. 接口部分调用串行转成并行处理,降低接口响应耗时,提升服务处理速度

方案

架构升级:

🔹 库存分层设计:

  1. Redis预扣(90%库存):通过Lua脚本保证原子扣减,提升下单库存性能

  2. DB兜底(10%库存):故障熔断时启用,结合库存校准机制 防超卖(日志回溯+定时校准)。

🔹 支付回调异步化:

  1. 临时表削峰:支付成功后写入待扣减记录,定时任务批量处理DB库存。

  2. 并行化链路:优惠券核销、积分计算等模块异步执行,接口响应下降。

对于库存的扣减优化,主要有两点:

  1. 下单扣减库存从DB扣改为redis扣,具体处理方式为:

a. 下单减库存,取全量库存的90%放入Redis进行扣减,确保在Redis不可用的情况下,能有部分数据库库存 承接流量等待库存校准完成。

b. Redis扣减失败会通过数据库进行扣减,失败达到阈值触发库存校准,并关闭热点标。

c. 库存校准:每次缓存库存操作会记录日志,用于校准数据库库存,避免因Redis超时重试等情况产生的超卖少卖。

2. 支付回调后扣减进入临时表缓冲

支付回调存在了QPS不稳定的风险,并且因为涉及到订单状态的变更,也不能进行限流操作,但是因为此处库存不影响前台项目的销售,所以接受一定程度的延迟,在支付回调过程中,将需要扣减的冻结库存暂时写入到一张临时表中,通过定时任务的方式做批量化处理,既起到了削峰的作用,也降低了库存的操作频次,大大降低了DB的热点数据问题。

同时,为了系统性地提高整体的读写并发度,我们做了如下梳理和优化

  1. 应用节点(链路)过长

    • 强弱依赖梳理,提供降级开关

    • 缩短RT时长,提升下单链路的各个接口性能

    • 规整服务告警和异常处理机制,尽量收缩核心链路范围,保证爆发期主链路的服务可用率达到SLA(下图是focus主链路异常处理和SLA监控的梳理逻辑)

  1. 数据库

  • sql慢查询优化

  • DB主从数据同步延迟:下单链路查主库

  • 数据库表优化:合表减少减少查表次数等

  • 大事务:将数据库查询工作和rpc调用移除事务

  • 扫描资源消耗过大的定时任务做提前预案,抢票时提前降级:改用databus消费

  • 锁等待:避免非抢票相关的链路导致数据库加锁

  • 数据库隔离级别:RR改为RC

为什么在高并发场景下使用RC更合适

首先,RC 在加锁的过程中,是不需要添加间隙锁Gap Lock和临键锁 Next-Key Lock 的,只对要修改的记录添加行级锁就行了。

这就使得并发度要比 RR 高很多。

另外,因为 RC 还支持"半一致读",可以大大的减少了更新语句时行锁的冲突;对于不满足更新条件的记录,可以提前释放锁,提升并发度。

减少死锁

因为RR这种事务隔离级别会增加Gap Lock和 Next-Key Lock,这就使得锁的粒度变大,那么就会使得死锁的概率增大。

带来的问题

首先使用 RC 之后,就需要自己解决幻读的问题。还有就是使用 RC 的时候,不能使用statement格式的 binlog,这种影响其实可以忽略不计了,因为MySQL是在5.1.5版本开始支持row的、在5.1.8版本中开始支持mixed,后面这两种可以代替 statement格式。

详情请看 B站票务抢购下单流程演进

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值