上文《秒杀抢红包实战系列(一)抢红包场景介绍》我们介绍了抢红包的相关产品,然后介绍了3个部分,第一个部分就是发红包。对于发红包的场景如下:
张三发了一个总额为100元的红包,分配可抢的红包个数是10个。
此时的话,这个红包就会被随机分配到10个红包里面,但是10个红包的总金额相加肯定是等于100元的。所以从技术上来看,我们做如下的分析:
1)提前分配红包
既然红包是提前可设置分配的,那么我们就提前把分配的红包个数和金额进行拆分计算好,这样子等到抢的时候直接抢我们提前分配好的红包即可,对吧?
2)使用那种分配算法
这里既然是分配红包,那么肯定是希望每个红包的金额是随机的,这样子才让抢红包更加有趣味性,所以从技术上来说,我们一般使用什么分配算法呢?据查网上说微信的红包分配算法是二倍均值法,那我们一会示例使用二倍均值法即可。
3)使用那种方式存储
抢红包的场景肯定是高并发的场景,所以对于我们来说,分配的红包如何存储呢?为了应对高并发的情况,我们一般会想到缓存,所以redis是第一选择。这里既然是高并发的场景,那么我们选择的技术就是redis+mq+mysql的方式,主力是mysql,异步为mq+mysql的形式。
4)如何避免红包不够分配
假设我们现在发了一个1毛钱的红包,但是分配的人数是11人,按照我们的常理,每人即使分配0.01元的话,也不够11个人分配,所以这种错误的情况需要考虑。
5)使用什么数据结构存储
这里其实主要是考虑redis这一层,为了保证后面的快速抢红包的体验,所以我们使用list消息队列结构即可。
6)红包id如何设置
像春晚这样的场景,这每分钟发的红包数量是非常客观的,所以这里每一个红包的id如何生成使用?这里我们采用类似淘宝订单的方式,即:年月日时分秒+用户id后六位。
根据以上的信息,我们使用java语言来实现一下这个分配红包的代码。示例如下:
1、编写分配红包的算法工具类
package com.example.demo.utils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Random; import com.example.demo.exception.RedPackageException; public class RedPackageUtils { // 设置随机数对象 public static final Random random = new Random(); // 设置百分比,后面需要除以100 private static final BigDecimal hundred = new BigDecimal("100"); // 二倍均值法基数 private static final BigDecimal two = new BigDecimal("2"); // 最低每人最低分配到0.01元 private static final BigDecimal min = new BigDecimal("0.01"); public static ArrayList<BigDecimal> getAllPackages(BigDecimal totalAmount, BigDecimal totalPeople) { // 存放红包金额的数组 ArrayList<BigDecimal> arrayList = new ArrayList<BigDecimal>(); if (totalAmount.compareTo(min.multiply(totalPeople)) < 0) { throw new RedPackageException("当前红包不足以分配给所有人"); } // 先给每个人全部分0.01元,然后把均值的金额给减掉,然后看还剩多少钱 BigDecimal remain = totalAmount.subtract(min.multiply(totalPeople)); BigDecimal redpeck; for (int i = 0; i < totalPeople.intValue(); i++) { // 随机取100以内的数,作为百分比 final int nextInt = random.nextInt(100); if (i == totalPeople.intValue() - 1) { // 直接把剩下的钱分配给最后一个红包 redpeck = remain; } else { // 采用2倍均值法进行红包的分配 redpeck = new BigDecimal(nextInt).multiply( remain.multiply(two).divide(totalPeople.subtract(new BigDecimal(i)), 2, RoundingMode.CEILING)) .divide(hundred, 2, RoundingMode.FLOOR); } // 比较下余额是否大于随机的分配的钱 if (remain.compareTo(redpeck) > 0) { remain = remain.subtract(redpeck); } else { remain = BigDecimal.ZERO; } // 最开始每人分的0.01元+随机分配的钱 arrayList.add(min.add(redpeck)); } return arrayList; } }
这个分配红包的思路比较简单,整体如下:
1、先做红包的金额与人数的判断。 2、然后给每个人先分配0.01元,保证所有的红包金额都是大于0的。 3、红包总额减去提前给每个红包分配的0.01元的总额就是可以进行随机分配的金额。 4、然后在可随机分配的金额基础上使用2倍均值法进行分配。 5、把每个红包随机分配到的钱加上0.01后塞进消息队列里面去。
2、编写发送红包的接口
这里编写一个发送红包的接口,使用springboot即可,示例代码如下:
/** * 发送红包 * * @param totalAmount 红包总金额 * @param totalPeople 红包个数 */ @RequestMapping("/send") public BaseResponse sendRedPacket(@RequestParam BigDecimal totalAmount, @RequestParam int totalPeople) { String redpkId = redPacketService.sendRedPacket(totalAmount, totalPeople); return BaseResponse.ok(redpkId); }
3、编写发送红包的逻辑service
接着这里调用service进行分发即可,示例如下:
public String sendRedPacket(BigDecimal totalAmount, int totalPeople) { // 随机生成一个红包的uid String redpksId = Constants.getOrderId(random.nextInt(999999)); // 根据红包个数,分配红包金额 ArrayList<BigDecimal> redpks = RedPackageUtils.getAllPackages(totalAmount, new BigDecimal(totalPeople)); for (BigDecimal money : redpks) { redisTemplate.opsForList().leftPush(redpksId, money.toString()); } return redpksId; }
这里的话我们通过工具类先提前把红包分配完毕,然后使用redis的lpush,把分配的金额发送到redis当中。
备注:
1、这里的核心代码是先把数据发送到redis中。 2、在实际的业务中,此代码缺少一块,即把分配红包的详情发送到mq中,供后端做数据异步消费和持久化处理。 3、这里的红包id我们使用的是年月日时分秒和用户id的组合模式,在一些大型的场景还可以添加几位的随机字母,保证高并发下的红包id不重复。
此时我们把项目运行起来,请求下发送红包的接口,示例图如下:
此时我们生成了一个红包id为:redpks_q_20240408152421102544的记录。我们看看redis的数据,示例图如下:
可以看到在redis中,我们有对应的红包id下面有一个list集合。里面是分配的所有红包记录数,个数为设置的10个。
以上就是抢红包场景的发送红包的部分,最后按照惯例,附上本案例的源码,登录后即可下载。
还没有评论,来说两句吧...