抢红包设计"/>
抢红包设计
抢红包大致可以分为2步:1 发红包 ;2 抢红包
发红包流程
为了突出红包设计主题,以下设计会忽略支付流程、24H过期退款剩余金额、用户领取红包余额到账等业务,则简化后的相关表设计如下:
CREATE TABLE `red_record` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` bigint NOT NULL COMMENT '用户id',`total` int(11) NOT NULL COMMENT '人数',`amount` int(11) NOT NULL COMMENT '总金额(单位为分)',`status` tinyint(4) DEFAULT '1' COMMENT '状态:1 已发送,2 已抢完',`create_time` datetime DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='发红包记录';
// 忽略 支付流程 和 24H过期退还剩余金额CREATE TABLE `red_rob_record` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) NOT NULLL COMMENT '用户ID',`red_record_id` int(11) COMMENT '红包ID',`amount` int(11) NOT NULL COMMENT '红包金额(单位为分)',`rob_time` datetime DEFAULT NULL COMMENT '时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=118 DEFAULT CHARSET=utf8 COMMENT='抢红包记录';
红包算法选取二倍均值算法,保证无论用户是先抢还是后抢都保证抢到金额的概率一致。
二倍均值算法逻辑实现图:
算法代码实现:
/*** 二倍均值法计算红包金额* @param totalAmount 总金额 (单位分)* @param peopleNum 总人数* @return*/public static List<Integer> divideRedPackage(Integer totalAmount,Integer peopleNum) {List<Integer> list = new ArrayList<>();//金额和人数都必须大于0if (totalAmount > 0 && peopleNum > 0) {//重置入参Integer restAmount = totalAmount;Integer restNum = peopleNum;//计算红包while (restNum - 1 > 0) {//计算红包金额 高效随机数 随机范围 左闭右开 [1,restAmount / restNum * 2)//随机数使用高效的ThreadLocalRandomint amount = ThreadLocalRandom.current().nextInt(restAmount / restNum * 2 - 1) + 1;//金额减少 人数减一restAmount -= amount;restNum--;list.add(amount);}//最后一个人的红包list.add(restAmount);}return list;}
发红包代码实现:
/*** 发红包* @param totalAmount 总金额 (单位分)* @param peopleNum 总人数* @param userId 用户id* @return*/public void sendRedPackage(Integer totalAmount,Integer peopleNum,Long userId) {//金额和人数必须要大于0//持久化发红包信息RedRecordDO redRecordDO = new RedRecordDO();redRecordDO.setUserId(userId);redRecordDO.setAmount(totalAmount);redRecordDO.setTotal(peopleNum);//忽略支付流程 默认已支付redRecordDO.setStatus(1);redRecordService.save(redRecordDO);//计算发红包明细金额List<Integer> list = RedPackageUtil.divideRedPackage(totalAmount,peopleNum);//生成redis的key String key = "m:r:"+redId;//存储到redis中redisService.getListOps().rightPushAll(key,list);}
抢红包流程
抢红包直接使用redis作为数据源码,利用redis高吞吐量的特性,在发红包阶段先用红包算法将其拆分,存储到redis中的list类型中。
主要利用redis的list数据类型的lpop原子操作(移除并获取列表第一个元素)
可以在redis客户端上简单看一下 list的lpop操作:
具体代码实现如下:
/*** 抢红包* @param redId 红包id* @param userId 领取红包的用户id* @return null 表示红包已抢完*/public Integer robRedPackage(Long redId,Long userId){//redis key ,key建议缩写 m表示系统缩写 r 表示红包缩写String key = "m:r:"+redId;//先判断key是否存在if(redisService.exists(key)){//直接弹出list中的元素 lpop是原子性 ,且弹出list所有元素后会删除这个keyInteger amount = (Integer)redisService.getListOps().leftPop(key);//元素不为空,exists不是原子性指令,则 amount可能为null if(Objects.nonNull(amount)){//元素不为空则表示抢红包成功,异步更新红包流水redRobRecordService.robRecord(redId,userId,amount);}return amount;}//直接返回红包已被抢光return null;}
更多推荐
抢红包设计
发布评论