一、需求分析
发红包:如果是等额金额红包这个好计算,要是随机金额红包该怎能实现? 抢红包:一个红包每个人只能抢一次,怎么记录这个人已经抢过,防止重复抢?而且不会因为抢红包的先后顺序而造成不公平? 并发性:这种情况肯定是多人共同抢一个红包,怎么保证高并发,而且不会出现超抢的情况?
二、具体实现
2.1 发红包
等额金额红包 :直接将红包总金额除以所发红包的个数。 随机金额红包:在这种情况比较特殊,虽然是红包金额随机分配,但也不能完全随机分配,比如:100的红包,有人抢了0.2,有人抢了90,抢到0.2的人心里面不太好受,为了避免这种特殊情况发生,一般会采用二倍均值法来实现随机金额红包的分配。
等额金额红包不再多说,这里说一下随机红包 (二倍均值法)
M = (剩余红包金额M ÷ 剩余人数N ) X 2
每次抢到的金额:就在(0,M)中随机取值
举个例子:假如有50元的红包随机金额发放给5个人
第一次:(50 ÷ 5) X 2 = 20,所以第一个人金额的范围是(0,20];假设第一个人随机到10元,那么剩余金额是50-10 = 40 元。 第二次:(40 ÷ 4) X 2 = 20,所以第二个人金额的范围是(0,20];假设第二个人随机到10元,那么剩余金额是40-10 = 30 元。 依此类推:如果还剩最后一个人,跳过以上计算,最后所剩钱全部归最后的人。
2.2 技术分析
抢红包是一个肯定是高并发业务,如果是多个群抢红包会把Mysql直接搞死,所以选择了Redis,在抢红包的时候肯定也要统计是谁抢了红包
生成红包:可以用Redis中的List记录生成红包 记录红包:可以用Redis中的Hash统计都是谁抢过红包
至于为什么用这两个数据类型,请大家参考以前写的文章(Redis的数据类型及其应用场景)
2.3 代码实现
@RequestMapping("/send")
public String sendRedPackage(BigDecimal totalMoney,int redPackageNumber){
//1 拆红包,总金额拆分成多少个红包,每个小红包里面包多少钱
List<BigDecimal> splitRedPackages = spiltRedPacket(totalMoney, redPackageNumber);
//2 红包的全局ID
String key = RED_PACKAGE_KEY+IdUtil.simpleUUID();
//3 采用list存储红包并设置过期时间,红包主有且仅有一个,不用加锁控制
String key1 = RED_PACKAGE_KEY+ "bigDecimals" + IdUtil.simpleUUID();
List<BigDecimal> bigDecimalList = spiltRedPacket(totalMoney, redPackageNumber);
redisTemplate.opsForList().leftPushAll(key1,bigDecimalList);
redisTemplate.expire(key,1,TimeUnit.DAYS);
return splitRedPackages.toString();
}
/**
* 拆红包
* @param totalMoney 总金额
* @param redPackageNumber 红包数量
* @return
*/
private List<BigDecimal> spiltRedPacket(BigDecimal totalMoney, int redPackageNumber){
BigDecimal usedMoney = new BigDecimal(0);
//单个红包大小集和
List<BigDecimal> redPacketList = new ArrayList<>();
//单个红包大小
BigDecimal redPacket = new BigDecimal(0);
for (int i = 0; i < redPackageNumber; i++){
//最后一个红包
if(i == redPackageNumber - 1){
redPacket = totalMoney.subtract(usedMoney);
}else {
BigDecimal avgMoney =
((totalMoney.subtract(usedMoney))
.divide(BigDecimal.valueOf(redPackageNumber - i),2, RoundingMode.DOWN))
.multiply(BigDecimal.valueOf(2));
//最大值
BigDecimal maxMoney = BigDecimal.valueOf(1)
.add(avgMoney.divide(BigDecimal.valueOf(1)));
float minF = new BigDecimal(0.01).floatValue();
float maxF = maxMoney.floatValue();
//取随机值
redPacket = new BigDecimal(Math.random() * (maxF - minF) + minF)
.setScale(2, RoundingMode.DOWN);
}
redPacketList.add(redPacket);
usedMoney = usedMoney.add(redPacket);
}
return redPacketList;
}
2.3 抢红包
抢红包就很简单了,如果有人抢红包就从List中一次取值,并用Hash记录一下抢红包的记录
代码如下
/**
*抢红包,谁发送的红包,分别被谁抢走了。
* @param redPackageKey 红包key
* @param userId 用户ID
* @return
*/
@RequestMapping("/rob")
public String rodRedPackage(String redPackageKey,String userId)
{
//1 验证某个用户是否抢过红包
Object redPackage = redisTemplate.opsForHash()
.get(RED_PACKAGE_CONSUME_KEY + redPackageKey, userId);
//2 没有抢过就开抢,否则返回-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);
//TODO 后续异步进mysql或者RabbitMQ进一步处理
return String.valueOf(partRedPackage);
}
//抢完
return "errorCode:-1,红包抢完了";
}
//3 某个用户抢过了
return "errorCode:-2, message: "+"t"+userId+" 用户你已经抢过红包了";
}
以上就是抢红包的基本设计思路,如果您觉得文章对您有帮助,麻烦您动动小手,点波关注
原文始发于微信公众号(Java的编程之美):基于Redis实现微信抢红包功能
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/37723.html