需求分析
1、有一个总的大红包拆分成多个小红包,
2、一个人只能抢一次
3、红包过期 退回
4、 计时一个大红包抢完耗时多少
拆红包算法
要求
- 所有人抢到金额之和等于红包金额,不能超过,也不能少于。
- 每个人至少抢到一分钱。
- 保证所有人抢到金额的几率相等。
解决方案
二倍均值法
剩余红包金额为M,剩余人数为N,那么有如下公式:
每次抢到的金额 = 随机区间 (0, (剩余红包金额M ÷ 剩余人数N ) X 2)
发红包过程
不需要加锁,使用二倍均值法分配红包金额,lpush到redis的list中,并设置过期时间为1天
抢红包过程
首先要判断你有没有抢过,如果抢过,就显示抢的金额,
没有抢过 rpop出去一个红包,如果redis返回null 表示抢完了,返回已抢完
rpop出一个红包,不为null,表示抢到了,对用户抢到的红包,放入hash,作记录防止用户多次抢一个红包,然后提示用户抢到了多少钱,并进一步扣款。
解决方案
用redist的hash,来存储用户抢过哪一个红包。
抢红包,直接使用list的pop操作,过程不需要加锁,因为redis的工作线程是单线程的。
具体代码编写
// 发红包
@RequestMapping("/send")
public String sendRedPackage(int totalMoney,int redPackageNumber)
{
//1 拆红包,总金额拆分成多少个红包,每个小红包里面包多少钱
Integer[] splitRedPackages = splitRedPackage(totalMoney, redPackageNumber);
//2 红包的全局ID
String key = UUID.fastUUID().toString();
//3 采用list存储红包并设置过期时间,红包主有且仅有一个,不用加锁控制
redisTemplate.opsForList().leftPushAll(key,splitRedPackages);
redisTemplate.expire(key,1, TimeUnit.DAYS);
return key+"\t"+"\t"+ Ints.asList(Arrays.stream(splitRedPackages).mapToInt(Integer::valueOf).toArray());
}
//抢红包流程
@RequestMapping("/rob")
public String rodRedPackage(String redPackageKey,String userId)
{
//1 验证某个用户是否抢过红包
Object redPackage = redisTemplate.opsForHash().get(redPackageKey, userId);
//2 没有抢过就开抢
if (redPackage == null) {
// 2.1 从list里面出队一个红包,抢到了一个
Object partRedPackage = redisTemplate.opsForList().leftPop(RED_PACKAGE_KEY + redPackageKey);
if (partRedPackage != null) {
//2.2 抢到手后,记录进去hash表示谁抢到了多少钱的某一个红包
redisTemplate.opsForHash().put(RED_PACKAGE_CONSUME_KEY + redPackageKey,userId,partRedPackage);
System.out.println("用户: "+userId+"\t 抢到多少钱红包: "+partRedPackage);
//抢到红包的用户账户金额增加
return String.valueOf(partRedPackage);
}
//抢完
return "红包抢完了";
}
//3 某个用户抢过
return "message: "+"\t"+userId+" 用户你抢到的金额";
}