一种高性能抢红包系统的设计与实现

2022-10-15 13:17:02刘磊陈华溢姚文辉
现代信息科技 2022年14期
关键词:总金额队列个数

刘磊,陈华溢,姚文辉

(广东开放大学(广东理工职业学院),广东 广州 510091)

0 引 言

据统计,2018年春节微信参与抢红包的峰值达到8.1 亿次/分钟,2019年春节支付宝抢红包的峰值达到每秒4.4 万笔,2022年春节抢红包大战总额超80 亿,抢红包已蔚然成为一种中国人特有的社交潮流,在全民狂欢的背后,强大、高效的抢红包系统作为基础架构支撑起了海量红包数据处理,笔者从抢红包系统的业务分析出发,尝试设计和实现一种基于B/S 架构的通用抢红包系统,能同时适用于手机端、桌面端、平板端,而不仅局限于APP 内部。

抢红包系统涉及发红包用户和抢红包用户,一个典型的抢红包业务为:一个发红包用户填写红包个数、总金额等信息发出红包,红包数据存入数据库,系统并将消息推送给抢红包的用户;多个抢红包用户收到红包消息,点击开始抢红包,发送抢的请求到服务端,服务端根据红包分配算法,从总金额生成一个小金额给抢到红包的用户,总金额值减小,红包个数减一,当大红包被用户抢完,红包个数和总金额减到零,抢到的红包明细数据存入数据库,这样就完成了一个抢红包过程,示意图如图1所示。抢红包业务通常规定,红包有效期为24 小时,同一用户对同一红包只能抢一次。通过分析可知,一个红包发出往往会被瞬间抢完,超过24 小时红包即为失效,系统具有“短时间”特点;一次抢红包活动抢到的人等于红包个数,这个数字在发红包时可以确定,不会太大,但参与抢的人通常大于红包个数,假设有1 亿人同时发出红包,按发和抢比例为1:9 计算,则有9 亿人几乎同时参与抢红包,系统要承载海量的“高并发”请求;一个用户发出红包,多个用户抢红包,实质是有限资源的瞬时竞争,是将一个大金额数字生成多个小金额数字,数据的存取和金额计算都需要耗时,用户发出抢红包请求是抢成功了、还是抢完了、或者是已抢过,都要最快响应,系统有“高性能”的需求。综上所述,设计一个鲁棒的抢红包系统在功能上要具备抢和发的能力,在性能上要达到高性能、高并发的要求。

图1 抢红包示意图

1 系统设计

1.1 功能设计

为着重验证抢红包功能,笔者设计了一个业务具有典型性的抢红包系统,功能设计为:

(1)用户注册登录:发红包用户和抢红包用户都要注册账号,使用账号登录系统。

(2)发红包:用户填写红包个数、总金额等信息发出红包。

(3)红包列表:用户登录成功后,可以查看所有能够参与的抢红包列表,以标记区别显示正在抢、已抢过、已抢完、已失效四种状态。

(4)抢红包:用户查看列表,点击抢红包,后端执行红包分配过程,返回前端抢到的结果。

(5)手气最佳:显示单个红包被抢到的最大金额。

(6)红包排行榜:降序显示单个红包被抢到的数据明细。

(7)我收到的红包:以列表形式展示用户抢到的红包数据。

(8)我发出的红包:以列表形式展示用户发出的红包数据。

1.2 数据库设计

围绕红包业务和功能分析,本方案数据库设计三个核心实体对象:用户、红包、清单。用户实体记录注册登录信息,用户ID 设置为主键;红包实体记录一个红包的发包者、红包个数、总金额、附言、创建时间等信息,红包ID 设置为主键;清单记录用户抢红包的明细,即哪个用户抢了哪个红包,抢到的金额是多少,字段包括抢包者ID、红包ID、抢到的金额、抢到的时间等。根据业务分析,一个用户可以发多个红包,一个红包只能被一个用户发出,用户与红包是一对多的关系,同时业务要求同一用户对同一红包不能重复抢,也就是清单表不能插入用户ID和红包ID两者都相同的记录,解决策略是只需要将红包ID 与用户ID 组成清单表的联合主键即可。本红包系统数据库模型设计如图2所示。

图2 数据库模型图

1.3 流程设计

用户发红包的流程是:用户在前端录入数据,点击发出红包,系统后端推送通知消息给抢红包的用户。用户抢红包的流程是:用户在前端收到红包提示信息,查看红包列表,点击抢红包,发送请求到后端服务器,服务器通过计算判断返回前端响应信息,若剩余金额和剩余红包个数为0,返回“已抢完”;若抢到的用户队列包含该用户ID,则返回“已抢过”;若剩余金额和剩余红包个数不为0,且不包含该用户ID,则从剩余金额分配出一个小金额给该用户,返回“抢红包成功”,显示抢到的金额。系统交互流程设计如图3所示。

图3 系统交互流程图

1.3.1 红包分配算法

大量用户抢有限的红包金额,实质是把一个大数根据一定算法分解成个小数的过程,小数的范围是[min,max]。考虑抢红包的高并发性,在兼顾公平的同时,红包分配算法越简单则越高效,运行越快则“资源竞争排队时间”越短,系统性能和并发处理能力则越高。从不同角度设计有不同红包算法,本方案设计了一种快速生成随机红包的算法。

算法原理:

(1)规定,因人民币最小金额为1 分,生成红包的金额最小为0.01 元。

(2)若红包个数是一个,则生成红包金额直接使用总金额。

(3)若红包个数是多个,则取0.01 与2 倍红包均值之间的随机数,红包均值=剩余总金额/剩余个数,即每轮生成的随机红包金额最小为0.01,最大为2 倍红包均值,使用2 倍红包均值做最大值可以保证每轮生成的红包金额不会相差太大。

(4)每生成一次红包,总金额减去生成的金额,红包个数减一,直至红包个数值为一,也就是最后一个红包,金额使用剩余的金额,不再随机生成。

假设剩余红包总金额为,剩余红包个数为,每轮生成的红包金额为,算法公式为:

使用Java 实现以上算法,关键代码为:

以10 人抢100 元大红包为例,一开始红包的数量为10、金额为100,同一个用户对同一个红包只能抢一次,也就是要经过10 轮才能抢完,10 位不同用户抢到的金额经过算法随机计算得到,经测试,其中一种分配情况如表1所示,本案例在笔者机器上经100 000 次运行,计算平均耗时约为7 微秒,可见使用本算法在内存生成红包的速度是很快的。

表1 以10 人抢100 元红包为例

1.3.2 发红包结构设计

发红包用户(简称发包者)的动作是封一个红包抛出去,封装的红包关键数据包括发包者ID、红包ID、个数、总金额等,为应对同时发红包的请求超过系统的并发处理能力,可在流量高峰阶段使用队列技术减缓对服务器的冲击。发红包的实现结构设计为:

(1)发包者在前端填写红包数据并向服务器提交。

(2)若同时发包提交请求超过一定量级,可先将请求放入发包队列。

(3)服务器先将红包数据存入缓存,缓存模块基于内存运行,因为内存的存取性能远远大于硬盘,将抢红包的过程放在内存完成性能是很高的,为保证缓存模块中单个红包数据的全局唯一性,这时可以通过Random 提前生成全局唯一ID 作为红包主键。

(4)红包数据在缓存之后,进入入库队列,被推送到数据库进行持久化存储。

(5)同时,红包数据进入红包广播队列,被推送到众多抢包者客户端,抢包者拿到红包ID 准备发起抢红包流程。发红包实现结构设计如图4所示。

图4 发红包实现结构

1.3.3 抢红包结构设计

大量用户争抢一个红包,实质是有限资源在高并发情况下的抢占,这就存在高并发竞争的问题,红包在发出时是有个数限制的,也就是说在发出时就能估算有效抢到红包用户的个数,超过红包个数的用户访问请求,红包已经分配完,再去争抢是无意义的,所以当抢红包的请求并发量比较大时,可以设置一层请求过滤队列,通过计数器统计,在红包个数内的请求直接放入,一旦红包已经抢完,红包个数减为零,后面的请求不再放入,直接返回“红包已抢完”,抢红包的过程在内存完成,红包数据和抢到的用户列表都暂存在缓存,这样可保证最快的响应性能,等红包抢完,则将抢到红包的用户ID 和金额批量入库。抢红包的实现结构设计为:

(1)抢包者发出抢红包的请求。

(2)当并发量比较大时,请求先放入队列,通过计数器统计,超过红包个数直接返回。

(3)放入有效的抢红包请求,抢红包过程在内存完成,根据红包ID 从缓存拿到红包剩余数量和剩余金额,通过红包分配算法随机生成一个金额给抢包者,这个计算是在内存实时完成的,更新红包剩余金额,红包剩余数量减1,将更新后的红包数据再放回缓存以备下次抢红包使用,同时将抢到红包的用户ID 和金额也放入缓存,可维护一个抢到用户的数据数组,记录所有抢到用户的ID 和金额。

(4)当红包被抢完时,将抢到红包的用户数据数组放入入库队列。

(5)入库队列里的数据异步批量存入数据库。抢红包实现结构设计如图5所示。

图5 抢红包实现结构

2 技术实现

本系统实现方案采用业界流行的技术组合:Linux+Tomcat/Nginx+SSM+Bootstrap+MySQL+Redis+Mav en,这套组合被阿里、京东等互联网公司广泛应用于大中型Web应用,非常适于开发高并发、高性能、高扩展的Web系统。本方案涉及的技术点为:

(1)开发框架:后端使用SpringMVC、Spring、Mybatis 三大框架(SSM),主流的J2EE 企业级开发框架,具有轻量级、代码侵入性低、技术成熟的特点,支持典型的三层架构:DAO(数据层)、Service(业务层)、Web(表示层)。Mybatis 是数据层框架,支持定制SQL 语句,传参自由、灵活,结果集自动赋值,接口设计和SQL 语句分离。Spring 提供了一个统一托管对象的容器工厂,允许通过一致的访问接口,访问工厂里的任意实例。SpringMVC 是Web层框架,支持restful 风格的URL 和MVC 开发模式。前端使用Bootstrap 框架,基于HTML5、CSS3、JQuery 技术构建,提供了导航、分页、面板等可复用的静态组件,下拉菜单、标签页、弹出框等动态插件,强大灵活的栅格布局系统,使用Bootstrap 可以快速、高效的开发健壮和优雅的响应式静态页面。

(2)队列技术:当并发请求达到一定量级时,超过系统接纳能力,有可能压垮系统,这时可以使用队列技术对并发请求限流,队列能将同时发出的请求进行排队处理,从而达到异步处理、流量削峰的目的。本方案使用请求队列应对同时发红包和抢红包的并发数;为保证高性能,抢红包的过程是在内存完成,抢完后的红包数据明细使用任务队列批量异步入库;发红包的过程完成后,使用消息队列及时推送通知给客户端。

(3)缓存模块:使用基于内存的NoSQL 型数据库Redis,Redis 性能非常高,经测试每秒钟SET 操作可执行110 000 次、GET 操作可执行81 000 次,Redis 支持原子性操作,也可将一组命令封装以事务的方式执行,即事务包括内的命令要么都执行,要么都不执行,保证数据一致性。本方案使用Redis 缓存红包数据,Redis 支持以键值(Key-Value)存储数据,每个红包维护两组数据,大红包数据使用红包ID 标识,记录剩余个数和剩余金额,小红包数据使用同样的红包ID 标识(为避免重复,可取反),记录抢包者ID 和抢到的金额,大红包经过算法分配生成小红包明细,过程都在内存完成,保证了高性能,存储结构如图6所示。

图6 缓存的红包数据

(4)原子计数:一个红包发出时,小红包的数量决定了有多少人能抢到红包,也就是有效抢红包的请求是可以确定的,对同一个红包,可能有非常多的抢包者同时发送抢红包请求,在能够确定红包数量的基础上,对放入的请求进行原子计数,当数目和红包个数相等时,即红包被抢完,后面的请求无须再放入,直接返回“已抢完”,既能提高响应速度又能减轻系统压力。本方案使用Redis 两个原子性的操作命令incr、decr 完成计数,以红包ID 为key,每次放入一个抢红包请求,计数就加1,在Redis 中使用代码:redis>incr key,在Spring 环境中使用代码:Long incrementId= redisTemplate.opsForValue().increment(key,1)。

2.1 集群化部署架构

理论上,一台服务器若能顶住1 000 的并发量,两台服务器就能顶住2 000 的并发量,一般来说,当上线的Web 系统并发量达到一定数量级时,都可以通过大规模服务器集群化部署提高系统的抗压能力,也就是常说的“当一头牛拉不动时,那就用三头牛”。本方案设计的系统架构、选用的开发框架都非常适于以集群的方式运行,对于实际线上的抢红包系统,通过不断增加服务器可以快速提高系统的并发处理能力。本系统线上集群化部署可以分为五层:

(1)CDN 缓存层:缓存静态化资源。

(2)负载均衡层:根据访问量,负责将请求智能转发到不同服务器。

(3)Web 应用层:在Tomcat 集群上部署相同的Web应用。

(4)Redis 缓存层:在内存缓存抢红包过程产生的数据。

(5)数据存储层:数据落地,使用主从库同步、读写库分离、分库分表等技术增强抗压能力。线上抢红包系统集群化部署方案参考如图7所示。

图7 抢红包系统集群化部署方案

2.2 系统测试

为了测试本文设计的抢红包系统的性能,笔者在PC机上进行了两组模拟实验,硬件配置为:CPU Core i5 3.3 GHz、内存8 GB;软件配置为:Tomcat 7.0、JDK 1.7、Redis 3.0、MySQL 5.5。

2.2.1 测试红包分配算法

为了测试本文设计的红包分配算法的公平性,笔者模拟10 人抢100 元红包,每轮产生10 个小红包数据,执行1 000 000 轮,每人产生了1 000 000 条红包数据,将每个人的红包数据求平均值和标准差,得到的数据为表2所示。

表2 模拟10 人抢100 元(执行1 000 000 次)

由实验数据可知:

(1)10 人的平均值大致相等,都在100/10 =10 附近波动,从统计学角度看,说明每人抢到红包的期望值是大致相等的,也就是大家抢到的红包面额在概率上是大致均匀分布的,都接近于理论平均值(总金额/总个数)。

(2)10 人的标准差存在差异,从第1 人到第10 人逐渐增大,这表明,先生成的红包数据波动较小,后生成的红包数据波动较大,也就是后抢的人有较大概率拿到“手气最佳”或者“手气最差”,增加了抢红包的趣味性。

2.2.2 测试抢红包性能

使用Java 编程模拟高并发抢红包场景,红包个数为100 000 个,总金额为1 000 000 元,红包数据和明细都存储在Redis,抢红包过程在内存完成,同时启动30 个线程运行抢红包程序。通过抢完的红包总个数除以总耗时可得到抢红包速度,经多次测算,每毫秒可以抢23 个,也就是每秒大致可以抢2.3 万个红包,可见在内存完成抢红包过程性能是很高的,能满足绝大多数场景需求。另外,本方案实现的抢红包系统作为笔者参与的电商平台核心模块已经上线运行,通过生产环境中的性能优化和集群化部署,在多次的促销活动中,顶住了上万的并发压力,表现稳定。

3 结 论

本文设计和实现的抢红包解决方案,使用SSM 框架实现高扩展性,基于Redis 缓存实现高性能红包分配算法,使用集群化部署增强系统并发处理能力,试验表明本方案具有典型性,对于解决抢红包、秒杀这类“瞬时高并发抢资源”的需求有较普遍的参考价值,本文没有更多讨论抢红包系统的安全性,有待进一步研究。当然抢红包系统的线上实现方案不止一种,从不同角度也可以设计不同的红包分配算法,没有最佳和一劳永逸的方案,只有最适应业务需求的方案,读者可以根据场景灵活选用。

猜你喜欢
总金额队列个数
怎样数出小正方体的个数
队列里的小秘密
基于多队列切换的SDN拥塞控制*
软件(2020年3期)2020-04-20 00:58:44
等腰三角形个数探索
怎样数出小木块的个数
在队列里
怎样数出小正方体的个数
丰田加速驶入自动驾驶队列
哪些电影赔了钱
海外星云(2016年7期)2016-04-27 21:30:55
主要刊期的期刊出版数量