当前位置:网站首页>设计一个抢红包系统
设计一个抢红包系统
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则需要做高可用。
边栏推荐
- 电脑重装系统u盘文件被隐藏要怎么找出来
- TDengine 社区问题双周精选 | 第二期
- Hard core observation 545 50 years ago, Apollo 15 made a feather landing experiment on the moon
- 同一个作业有两个source,同一链接不同数据库账号,为何第二个链接查出来的数据库列表是第一个账号的
- Knowledge * review
- 实现多彩线条摆出心形
- mysql-cdc 的jar包 ,在flink运行模式下,是不是要放在不同的地方呢?
- The problem that dockermysql cannot be accessed by the host machine is solved
- js对JSON数组的增删改查
- Unified Focal loss: Generalising Dice and cross entropy-based losses to handle class imbalanced medi
猜你喜欢
Cover fake big empty talk in robot material sorting
资产安全问题或制约加密行业发展 风控+合规成为平台破局关键
Thinkphp5 multi table associative query method join queries two database tables, and the query results are spliced and returned
Implementation steps of mysql start log in docker
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
Today's sleep quality record 78 points
MySQL implementation of field segmentation from one line to multiple lines of example code
MySQL中正则表达式(REGEXP)使用详解
DockerMySQL无法被宿主机访问的问题解决
B站大佬用我的世界搞出卷積神經網絡,LeCun轉發!爆肝6個月,播放破百萬
随机推荐
基于PaddlePaddle平台(EasyDL)设计的人脸识别课堂考勤系统
每人每年最高500万经费!选人不选项目,专注基础科研,科学家主导腾讯出资的「新基石」启动申报...
Face recognition class attendance system based on paddlepaddle platform (easydl)
mysql查看表结构的三种方法总结
传统企业要为 Web3 和去中心化做的 11 个准备
人均瑞数系列,瑞数 4 代 JS 逆向分析
[unity] upgraded version · Excel data analysis, automatically create corresponding C classes, automatically create scriptableobject generation classes, and automatically serialize asset files
Children's pajamas (Australia) as/nzs 1249:2014 handling process
JDBC programming of MySQL database
A few suggestions for making rust library more beautiful! Have you learned?
How does crmeb mall system help marketing?
Docker mysql5.7 how to set case insensitive
石墨文档:4大对策解决企业文件信息安全问题
自动更新Selenium驱动chromedriver
What does security capability mean? What are the protection capabilities of different levels of ISO?
Restoration analysis of protobuf protocol of bullet screen in station B
Unified Focal loss: Generalising Dice and cross entropy-based losses to handle class imbalanced medi
TDengine 社区问题双周精选 | 第二期
PDF批量拆分、合并、书签提取、书签写入小工具
What should I do if the USB flash disk data is formatted and how can I recover the formatted USB flash disk data?