如何从零搭建10级QPS大流量、高并发优惠卷系统--上

从零搭建10级QPS大流量、高并发优惠卷系统--上

节日活动,多个业务方面都有发放优惠卷的需求,且对发券的QPS量级有明确的需求,所有的优惠券发放、核销、查询都需要一个新的系统来承载。因此,需要设计和开发一个能够支撑十万级QPS的券务系统,并且对优惠券完成的声明周期进行维护。

目录

从零搭建10级QPS大流量、高并发优惠卷系统--上

1、需求拆解和技术选型

1.1需求拆解

1.2系统选型及中级件

存储

缓存

消息队列

系统框架

2、系统开发用与实践

2.1系统整体架构

2.2数据结构ER图

2.3核心逻辑实现

发券

过期券

3、大流量、高并发场景下的问题及解决方案

3.1存储瓶颈及解决方案

瓶颈

解决方案

3.1.1.1容量预估

3.1.1.2热点库存问题及解决方案

6.3.1.3建券

3.1.1.4库存扣减

1、需求拆解和技术选型

1.1需求拆解

要配置券,会涉及到券批次(券模板)创建,券模板的有效期以及券的库存信息

要发券,会涉及到券记录的创建和管理(过期时间,状态)

可以将需求拆解为两部分:

同时,无论券模板还是券记录,都需要开放查询接口,支持券模版/券记录查询。

1.2系统选型及中级件

确定了基本需求,根据需求进一步分析用到中间件,以及系统整体的组织方式。

存储

由于券模版,券记录这些都是需要持久化的数据,同时还需要支持条件查询,所以我们选用通用的结构化存储MYSQL作为存储中间件。

缓存

发券时需要券模版信息,大流量情况下,不可能每次都从MYSQL获取券模板信息,因此引入缓存。

同理,券的库存管理,或者叫库存扣减,也是一个高频、实时的操作,因此需要考虑放入缓存。

主流的缓存Redis可以满足我们的需求,因此选用Redis作为缓存中间件。

消息队列

由于券模板/券记录都需要展示过期状态,并且根据不同的状态进行业务逻辑处理,因此有必要引入延迟消息队列来对券模板/券状态进行处理。选用RocketMQ

Apache RocketMQ 是一款低延迟、高并发、高可用、高可靠的分布式消息中间件。消息队列 RocketMQ 可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。

系统框架

发券系统作为下游服务,是需要被上游服务所调用的。公司内部服务之间,采用的都是RPC服务调用,系统开发语言使用golang,因此使用golang服务的RPC框架kitex进行代码编写。

RPC作用:

  • 屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法
  • 隐藏底层网络通信的复杂性,让我们更加专注业务逻辑。】

技术选型:kitex+MYSQL+Redis+RocketMQ来实现发券系统,RPC服务部署在docker。

2、系统开发用与实践

2.1系统整体架构

系统架构和具体功能如,如下:

2.2数据结构ER图

与系统架构对应的mysql数据库表

                                       

2.3核心逻辑实现

发券

发券流程分为三部分:参数校验、幂等校验、库存减扣

幂等操作,用于保证发券请求不正确情况下,业务通过重试,步长的方式再次请求,可以最终只发出一张券,防止资金丢失。

过期券

券过期时一个状态推进的过程,这里使用RocketMQ来实现。

由于RocketMQ支持的延迟消息最大限制,而卡券的有效期不固定,有可能会超过限制,所以我们将卡券过期消息循环处理,直到卡券过期。

3、大流量、高并发场景下的问题及解决方案

实现系统的基本功能后,如果在大流量、高并发的场景下,系统可能回遇到的问题:

3.1存储瓶颈及解决方案
瓶颈

在系统中,我们使用了Mysql,Redis作为存储组件。我们知道,单个服务器I/O能力是有限的,在实际测试过程中,能够得到如下数据:

①单个Mysql的每秒写入在4000QPS左右,超过这个数字,Mysql的I/O延时回骤增。

②mysql单表记录达到千万级别,查询效率会大大降低,如果过亿的话,数据查询会成为一个问题。

③redis单分片的写入瓶颈在2w左右,读瓶颈在10w左右。

解决方案

①读写分离,在查询券模板、查询券记录等场景下,我们可以将mysql进行读写分离,让这部分查询流量走mysql的读库,从而减轻mysql写库的查询压力。

②分治,在软件设计中,有一种分治思想,对于存储瓶颈问题,业界常用的方案就是分而治之:流量分散,存储分散,即:分库分表。

③发券,归根结底是要用户的领券记录做持久化存储。对于mysql本身I/O瓶颈来说,我们可以在不同的服务器上部署mysql的不同分片,对mysql做水平扩容,写请求就会分布在不同的Mysql主机上,这样就能够大幅提升mysql整体的吞吐量。

④给用户发了券,那么用户肯定需要查询自己获得的券。基于这个逻辑,我们以user_id后四位问为分片键,对用户领取的记录表做水平拆分,以支持用户维度的领券记录的查询。

⑤每种券都有对应的数量,在给用户发券的过程中,我们是将发券数记录在Redis中的,大流量的情况下,我们也需要对redis做水平扩容,减轻redis单机的压力。

3.1.1.1容量预估

基于上述思路,在满足发券12wQPS的需求下,我们预估一下存储资源。

a.Mysql资源

在实际测试中名单次发券对mysql有一次飞事务性写入,mysql的单机的写入瓶颈为4000,据此可以计算我们需要的mysql主库资源位:120000/4000=3;

b.redis资源

假设12w的发券QPS,均为同一模板,单分片的写入瓶颈为2w,则需要的最少redis分为:120000/20000=6。

3.1.1.2热点库存问题及解决方案

问题

大流量发券场景下,如果我们使用的券模板为一个,那么每次扣减库存时,访问到的redis必然是特定的一个分片,因此,一定会达到这个分片的写入瓶颈,更严重的,可能会导致整个redis集群不可用。

解决方案

热点库存的问题,业界通用的方案:即扣减库存key不要集中在谋一个分片上,如何保证这一个券模版的key不集中在谋一个分片上呢,我们拆key(拆库存)即可,如图:

在业务逻辑中,我们在建券模板的时候,就将这种热点券做库存拆分,后续扣库存时,也扣减相应的子库存即可。

6.3.1.3建券

3.1.1.4库存扣减

这里还剩下一个问题,扣减子库存,每次都是从1开始进行的话,那对redis对应分片的压力其实并没有减轻,因此,我们需要做到:每次请求,随机不重复的轮询子库存,以下是本项目采取的一个具体思路:

redis子库存的key的最后一位是分片的编号,如 xx_stock_key1、xx_stock_key2,在扣减子库存时,我们先生成对应分片总数的随机不重复数组,如第一次是[1,2,3],第二次可能是[3,1,2],这样,每次扣减子库存的请求,就会分不到不同的redis分片上,缓清redis单分片压力的同时,也能支持更高的QPS的扣减请求。

这种思路的一个问题是,当我们库存接近耗尽的情况下,很多分片子库存的轮询将变得毫无意义,因此我们可以每次在请求的时候,将子库存的剩余量记录下来,当某一个券的模板的子库存耗尽后,随机不重复的轮询操作,直接跳过这个子库存分片,这样能够优化系统在库存即将耗尽情况下的响应速度。

业界针对redis热点key的处理,除了分key以外,还有一种key备份的思路,即将相同的key,用某种策略备份到不同的redis分片上去,这样就能将热点打散。这种思路适用于那种读多写少的场景,不适合应对发券这种大流量写的场景,在面对具体业务场景时,我们需要根据业务场景需求,选用恰当的方案解决问题。

未完......

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值