现有的秒杀架构,为了支持高并发,通常把库存放在Redis中,收到订单请求时,在Redis中进行库存扣减。这种的设计,导致创建订单和库存扣减不是原子操作,如果两个操作中间,遇到进程crash等问题,就会导致数据不一致。
即使库存扣减不放在Redis中,而是放在数据库,不一致问题也通常是存在的。业务系统为了模块化,减少耦合,会将库存服务与订单服务分开。只要是分开的服务,那么数据不一致的情况就是无法避免的。
进程crash等问题,虽然发生的概率不高,但即使占比百分之一,甚至千分之一,都会产生数据不一致,例如扣减的库存量和创建成功的订单不一致。
库存与订单数据不一致是必须解决的难题,常见做法是,开发人员通过订单数据,去校准库存数据,这部分的工作非常繁琐复杂,耗费大量的开发工作,而且很多时候需要人工介入,对数据进行人工校验和修复。
下面我们来看看新架构如何优雅解决这个问题
我们明确业务场景,我们把秒杀系统的核心要点提取出来,为以下几点:
本架构基于 https://github.com/dtm-labs/dtm,这是一个分布式事务框架,提供跨服务,跨库的数据一致性解决方案。
上述的场景下,绝大部分扣减库的描述请求,都会失败,时序图如下:
在这个架构中,使用了分布式事务框架dtm。上述的时序图中,扣减库存是在Redis中进行的,与dtm相关的注册全局事务和取消全局事务也是在Redis中处理的,全程依赖Redis,与数据库无关,因此能够支持极高的并发,从后面的测试数据中可以看到,该架构可以轻易处理每秒上万单的秒杀请求。
虽然大部分请求因为扣减库存失败而结束,但是会有一定数量的请求,扣减库存成功,这种情况的时序图如下:
在这个时序图中,扣减库存成功后,会进入到订单服务,进行订单相关的创建,以及后续的支付。在这个新架构中,订单服务仅需要处理有效订单,此时并发量已经大幅下降,只需要通过常规的方法,例如订单分库分表、消息队列削峰处理,就可以轻松解决问题了。
在上述的架构中,如果在Redis中扣减库存后,在提交全局事务前,发生进程crash,就会导致两个操作没有同时完成,那么这种情况后续会怎么样?新架构如何保证数据最终严格一致?这种情况的整个的时序图如下:
一旦发生这类进程crash,导致两个操作过程中断,那么dtm服务器会轮询超时未完成的事务,如果出现已Prepare、未Submit的全局任务,那么他会调用反查接口,询问应用,库存扣减是否成功扣减。如果已扣减,则将全局事务提交,并进行后续的调用;如果未扣减,则将全局事务标记为失败,不再处理。
保证原子操作的原理,以及发生各种情况dtm的处理策略,可以参考二阶段消息,这里不做详细的描述。
秒杀接口的核心代码如下:
gid := "{a}flash-sale-" + activityID + "-" + uid
msg := dtmcli.NewMsg(DtmServer, gid).
Add(busi.Busi+"/createOrder", gin.H{activity_id: activityID, UID: uid})err := msg.DoAndSubmit(busi.Busi+"/QueryPreparedRedis", func(bb *BranchBarrier) error {
return bb.RedisCheckAdjustAmount(rds, "{a}stock-"+stockID, -1, 86400)})
}
反查的核心代码如下:
app.GET(BusiAPI+"/QueryPreparedRedis", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
return MustBarrierFromGin(c).RedisQueryPrepared(rds)
}))
开发人员编写反查的逻辑很简单,对于Redis里面的数据,只需要复制粘贴这上面的代码就行。反查的详细原理参考二阶段消息,二阶段消息的文档里介绍的是数据库中如何做,而这里则是用Redis来完成类似的反查逻辑,就不详细说明了。
从上面的介绍中,可以看到,对于大部分扣减库存失败的请求,只需要进行三个Redis操作,1. 注册全局事务;2. 扣减库存;3. 修改全局事务为以失败。这个三个操作都是lua脚本实现。一个普通的redis,每秒大约能够支持6w个lua脚本操作,照此分析,我们的新架构,理论上每秒能够支持2w个秒杀请求。我做的性能测试报告显示,当dtm与扣库存共享一个redis时,每秒可以轻松完成1.2w个秒杀订单,达到理论极限值的60%,详情可以参考后面的性能测试报告
更进一步分析,扣减库存与全局事务可以使用不同的Redis,那么
上述的分析还仅仅限于普通云厂商虚拟机上的自己安装Redis,假如通过简单的硬件升级,或者使用云厂商提供的Redis,那么Redis能提供更强劲的性能,上述的9w/s还能够再提高一个台阶。
参考一下阿里双十一的峰值订单:58.3万笔/秒,那么上述预估的9w/s,几乎足以应对所有的秒杀活动
完整的可运行的代码示例,可以参考https://github.com/dtm-labs/dtm-cases/flash
测试的环境,两台阿里云主机,类型为:ecs.hfc5.3xlarge 12核 CPU 3.1GHz/3.4GHz PPS 130万
测试过程:
选择虚拟机 A 安装 Redis
apt-get install -y redis# 修改 /etc/redis/redis.conf# bind 127.0.0.1 => 0.0.0.0systemctl redis restart
选择虚拟机 B 安装 dtm
apt update
apt install -y git
wget https://golang.org/dl/go1.17.1.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.1.linux-amd64.tar.gz && cp -f /usr/local/go/bin/go /usr/local/bin/go
git clone https://github.com/dtm-labs/dtm.git && cd dtm && git checkout v1.11.0 && cd bench && make
# 修改 dtm/bench/test-flash-sales.sh
# export BUSI_REDIS=localhost:6379 => 虚拟机A 的私网ip
sh test-flash-sales.sh
我的结果显示,每秒大约能够完成1.2w个秒杀请求:
Requests per second: 11970.21 [#/sec] (mean)
我们提出了一个全新的秒杀架构,可以保证创建订单和扣减库存的原子性,并且预估可以支撑9w/s的秒杀请求流量。帮助大家更好更快的解决秒杀的业务需求。
欢迎访问我们的项目,并star支持我们:
https://github.com/dtm-labs/dtm
网页名称:支持每秒上万单的秒杀扣库存事务
网址分享:http://www.shufengxianlan.com/qtweb/news38/391038.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联