当前位置:网站首页>设计一个抢红包系统
设计一个抢红包系统
2022-07-06 15:51:00 【OoZzzy】
1.需求分析
常见的红包系统,由用户指定金额、红包总数来完成红包的创建,然后通过某个入口将红包下发至目标用户,用户看到红包后,点击红包,随机获取红包,最后,用户可以查看自己抢到的红包。整个业务流程不复杂,难点在于抢红包这个行为可能有很高的并发。所以,系统设计的优化点主要关注在抢红包这个行为上。
- 发红包:用户设置红包总金额、总数量
- 抢红包:用户从总红包中随机获得一定金额
抢红包必须保证高可用,不然用户会很愤怒。其次,必须保证系统数据一致性不能超发,不然抢到红包的用户收不到钱,用户会很愤怒。最后一点,系统可能会有很高的并发。
2.表结构设计
红包活动表
CREATE TABLE `t_redpack_activity`
(
`id` bigint(20) NOT NULL COMMENT '主键',
`total_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '总金额',
`surplus_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '剩余金额',
`total` bigint(20) NOT NULL DEFAULT '0' COMMENT '红包总数',
`surplus_total` bigint(20) NOT NULL DEFAULT '0' COMMENT '红包剩余总数',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '用户编号',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
红包表
CREATE TABLE `t_redpack`
(
`id` bigint(20) NOT NULL COMMENT '主键',
`activity_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '红包活动ID',
`amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '金额',
`status` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '红包状态 1可用 2不可用',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
明细表
CREATE TABLE `t_redpack_detail`
(
`id` bigint(20) NOT NULL COMMENT '主键',
`amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '金额',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '用户编号',
`redpack_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '红包编号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
活动表,就是你发了多少个红包,并且需要维护剩余金额。明细表是用户抢到的红包明细。红包表是每一个具体的红包信息。为什么需要三个表呢?事实上如果没有红包表也是可以的。但我们的方案预先分配红包需要使用一张表来记录红包的信息,所以设计的时候才有此表。
3.基于分布式锁的实现

基于分布式锁的实现最为简单粗暴,整个抢红包接口以activityId作为key进行加锁,保证同一批红包抢行为都是串行执行。分布式锁的实现是由spring-integration-redis工程提供,核心类是RedisLockRegistry。锁通过Redis的lua脚本实现,且实现了阻塞式本地可重入。
4.基于乐观锁的实现

第二种方式,为红包活动表增加乐观锁版本控制,当多个线程同时更新同一活动表时,只有一个 clien 会成功。其它失败的 client 进行循环重试,设置一个最大循环次数即可。此种方案可以实现并发情况下的处理,但是冲突很大。因为每次只有一个人会成功,其他 client 需要进行重试,即使重试也只能保证一次只有一个人成功,因此 TPS 很低。当设置的失败重试次数小于发放的红包数时,可能导致最后有人没抢到红包,实际上还有剩余红包。
5.基于悲观锁的实现

由于红包活动表增加乐观锁冲突很大,所以可以考虑使用使用悲观锁:select * from t_redpack_activity where id = #{id} for update,注意悲观锁必须在事务中才能使用。此时,所有的抢红包行为变成了串行。此种情况下,悲观锁的效率远大于乐观锁。
6.预先分配红包,基于乐观锁的实现

可以看到,如果我们将乐观锁的维度加在红包明细上,那么冲突又会降低。因为之前红包明细是用户抢到后才创建的,那么现在需要预先分配红包,即创建红包活动时即生成 N 个红包,通过状态来控制可用/不可用。这样,当多个 client 抢红包时,获取该活动下所有可用的红包明细,随机返回其中一条然后再去更新,更新成功则代表用户抢到了该红包,失败则代表出现了冲突,可以循环进行重试。如此,冲突便被降低了。
7.基于 Redis 队列的实现

和上一个方案类似,不过,用户发放红包时会创建相应数量的红包,并且加入到 Redis 队列中。抢红包时会将其弹出。Redis队列很好的契合了我们的需求,每次弹出都不会出现重复的元素,用完即销毁。缺陷:抢红包时一旦从队列弹出,此时系统崩溃,恢复后此队列中的红包明细信息已丢失,需要人工补偿。
8.基于 Redis 队列,异步入库

这种方案的是抢到红包后不操作数据库,而是保存持久化信息到Redis中,然后返回成功。通过另外一个线程UserRedpackPersistConsumer,拉取持久化信息进行入库。需要注意的是,此时的拉取动作如果使用普通的pop仍然会出现crash point的问题,所以考虑到可用性,此处使用Redis的BRPOPLPUSH操作,弹出元素后加入备份到另外一个队列,保证此处崩溃后可以通过备份队列自动恢复。崩溃恢复线程CrashRecoveryThread通过定时拉取备份信息,去 DB 中查证是否持久化成功,如果成功则清除此元素,否则进行补偿并清除此元素。如果在操作数据库的过程中出现异常会记录错误日志redpack.persist.log,此日志使用单独的文件和格式,方便进行补偿(一般不会触发)。
Redis则需要做高可用。
边栏推荐
- JS import excel & Export Excel
- 新手问个问题,我现在是单机部署的,提交了一个sql job运行正常,如果我重启了服务job就没了又得
- 这个『根据 op 值判断操作类型来自己组装 sql』是指在哪里实现?是指单纯用 Flink Tabl
- GPT-3当一作自己研究自己,已投稿,在线蹲一个同行评议
- 浅谈网络安全之文件上传
- MySQL中正则表达式(REGEXP)使用详解
- Per capita Swiss number series, Swiss number 4 generation JS reverse analysis
- mysql拆分字符串作为查询条件的示例代码
- Flutter life cycle
- Spark Tuning (II): UDF reduces joins and judgments
猜你喜欢

电脑重装系统u盘文件被隐藏要怎么找出来
MySQL中正则表达式(REGEXP)使用详解

js對JSON數組的增删改查

【全网首发】Redis系列3:高可用之主从架构的

Wu Enda 2022 machine learning course evaluation is coming!

基础图表解读“东方甄选”爆火出圈数据

The worse the AI performance, the higher the bonus? Doctor of New York University offered a reward for the task of making the big model perform poorly

JDBC programming of MySQL database

(1)长安链学习笔记-启动长安链

What can be done for traffic safety?
随机推荐
B站大佬用我的世界搞出卷積神經網絡,LeCun轉發!爆肝6個月,播放破百萬
What does front-end processor mean? What is the main function? What is the difference with fortress machine?
Laravel8 uses passport authentication to log in and generate a token
CRMEB商城系统如何助力营销?
flinksql select id ,count(*) from a group by id .
AI金榜题名时,MLPerf榜单的份量究竟有多重?
Nftscan Developer Platform launches Pro API commercial services
公链与私链在数据隐私和吞吐量上的竞争
Detailed explanation of regular expression (regexp) in MySQL
The problem that dockermysql cannot be accessed by the host machine is solved
Use mitmproxy to cache 360 degree panoramic web pages offline
Let me ask you if there are any documents or cases of flynk SQL generation jobs. I know that flynk cli can create tables and specify items
How to choose the server system
The important data in the computer was accidentally deleted by mistake, which can be quickly retrieved by this method
How to implement Lua entry of API gateway
Demonstration of the development case of DAPP system for money deposit and interest bearing financial management
(flutter2) as import old project error: inheritfromwidgetofexacttype
同一个作业有两个source,同一链接不同数据库账号,为何第二个链接查出来的数据库列表是第一个账号的
Introduction to network basics
Should the jar package of MySQL CDC be placed in different places in the Flink running mode?