解密微信红包算法及抢红包案例实现

2023-12-13 14:31:55 浏览数 (1)

前言

微信红包大家应该不陌生吧,别看小小的一个红包,涉及到技术涵盖很多方面的,比如如图所示,用户发一个红包,会涉及发红包,红包存储,红包拆分,抢红包等流程。本文将详细介绍,一个红包从诞生到过期的整个流程,并且通过代码案例实践讲解,而且重点会分析讲解红包的拆分算法。

微信红包设计流程

依照发红包,红包拆分,抢红包的流程来涉及整个红包流程,采用什么数据结构进行红包设计,由于抢红包,是高并发的,并且响应也要及时,所以采用Redis非关系数据库来设计,是比MySQL好,主要Redis处理每一个命令是单线程,原子操作,无需加锁。

发红包:一个红包会被拆分成多个小红包(金额),比如100块拆分成:20 20 20 30 10,所以可以用redis的list结构来存储

抢红包:需要保证如何保证高并发 多线程 不加锁且保证原子性,所以在redis,每个命令就是单线程原子性天生保证,Ipop出list即可。

记红包:需要保证同一个用户不可以抢夺2次红包,要记录那个红包被那个用户抢了,所以可以用hash结构来存储。

拆红包算法:拆红包算法其实有很多,但是比较合理的可以采用二倍均值算法

代码实现

二倍均值算法实现拆红包

二倍均值,字面也是是红包平均金额的两倍,为了保证随机,取随机区间,最大值为平均金额的两倍,所以最后公式如下:

每次拆分后塞进子红包的余额 = 随机区间(0,(剩余红包金额M / 未被抢的剩余红包个 N) * 2)

具体代码:

代码语言:javascript复制
 private Integer[]  splitRedPackageAlgorithm(int totalMoney, int redPackageNumber){
        Integer[] splitRedPackageNumbers = new Integer[redPackageNumber];
        // 已经被抢夺的红包金额,
        int useMoney = 0;

        for (int i = 0; i < redPackageNumber; i  ) {

            if(i == redPackageNumber - 1){
                // 最后一个红包,无需拆分
                splitRedPackageNumbers[i] = totalMoney - useMoney;
            }else {
                // 二倍均值算法,每次拆分后塞进子红包的余额 = 随机区间(0,(剩余红包金额M / 未被抢的剩余红包个 N) * 2)
                int avgMoney = ((totalMoney - useMoney) / (redPackageNumber - i) ) * 2;
                // new Random().nextInt(avgMoney -1) 是生成 0 到 avgMoney -1(不包括 avgMoney -1),但是不能为0,所以要  1 ,保证第一个红包不为 0
                // avgMoney -1 表示最接近二倍均值,而不能等于,这样,保证最后一个红包不为 0,如果不 -1,有可能每次获得随机数都是最大值,那么到最后有可能剩余的红包只能为 0
                splitRedPackageNumbers[i] = 1   new Random().nextInt(avgMoney - 1) ;
            }
            useMoney = useMoney   splitRedPackageNumbers[i];
        }
        return splitRedPackageNumbers;
    }

为什么生成每个小红包金额使用如下代码随机生成?

代码语言:javascript复制
splitRedPackageNumbers[i] = 1   new Random().nextInt(avgMoney - 1) ;

首先前面加1,原因是new Random().nextInt(avgMoney -1) 是生成 0 到 avgMoney -1(不包括 avgMoney -1),但是不能为0,所以要 1 ,保证第一个红包不为 0,不能抢红包抢到0元的吧。

后面avgMoney -1,如果不-1,结果是怎样呢?接下来模拟不-1的情况下,假设100块分5个红包,每次随机数都取最大值,那么有如下情况:

红包

useMoney

avgMoney

每次随机数最大

本次红包金额

1

0

20 * 2

new Random().nextInt(avgMoney) == 39

40

2

40

15*2

new Random().nextInt(avgMoney) == 29

30

3

70

10*2

new Random().nextInt(avgMoney) == 19

20

4

90

5*2

new Random().nextInt(avgMoney) == 9

10

5

0

0

可以发现如果随机数最大值,是avgMoney ,会发现,最后一个红包可能为0,所以就随机数控制最大值接近avgMoney -1

avgMoney -1 表示最接近二倍均值,而不能等于,这样,保证最后一个红包不为 0,如果不 -1,有可能每次获得随机数都是最大值,那么到最后有可能剩余的红包只能为 0。

发红包

主要是将拆红包得到的结果,也就是红包总金额totalMoney拆分为redPackageNumber个子红包,保存到list结构里面,并且设置过期时间

代码语言:javascript复制
 @RequestMapping(value = "/send")
    public String sendRedPackage(int totalMoney, int redPackageNumber){

        //1 拆红包,将红包总金额totalMoney拆分为redPackageNumber个子红包
        Integer[] splitRedPackages = splitRedPackageAlgorithm(totalMoney,redPackageNumber);
        // 2 发红包保存到list结构里面,并且设置过期时间
        String key = RED_PACKAGE_KRY   IdUtil.simpleUUID();
        redisTemplate.opsForList().leftPushAll(key,splitRedPackages);
        redisTemplate.expire(key,1, TimeUnit.DAYS);

        // 3 发红包成功,返回前台显示
        return key "t"   Ints.asList(Arrays.stream(splitRedPackages).mapToInt(Integer::valueOf).toArray());
    }

调用发红包接口,比如20块钱分成5个红包,查看redis

抢红包

先验证某个用户是否抢过红包,查询redis记录,没有的话,同意用户抢红包,直接获取列表中的其中一个红包,并存红包领取记录,使用hash结构记录红包领取记录

代码语言:javascript复制
    /**
     * 用户抢红包
     * @param redPackageKey 红包
     * @param userId 用户id
     * @return
     */
    @RequestMapping(value = "/rob")
    public String robRedPackage(String redPackageKey,String userId){
        // 1.验证某个用户是否抢过红包,查询redis记录
        Object redPackage = redisTemplate.opsForHash().get(RED_PACKAGE_CONSUME_KRY   redPackageKey, userId);

        // 2. 用户没有抢过红包
        if (null == redPackage){
            // 3. 用户抢红包,直接获取列表中的其中一个红包
            Object partRedPackage = redisTemplate.opsForList().leftPop(RED_PACKAGE_KRY   redPackageKey);
            if (partRedPackage != null){
                // 4.保存红包领取记录,使用hash结构记录红包领取记录
                redisTemplate.opsForHash().put(RED_PACKAGE_CONSUME_KRY redPackageKey,userId,partRedPackage);
                System.out.println("用户" userId "t 抢到红包了 "   partRedPackage);
         
                return String.valueOf(partRedPackage);
            }
            return "errorCode : -1 ,红包抢完了";
        }
        return "errorCode : -2 ," userId " 你已经抢过红包了";
    }

调用抢红包接口,返回红包金额

redis也能看到红包记录

总结

本文主要是讲解,红包拆分,发红包,抢红包的流程,并重点介绍了二倍均值法。抢红包的核心思想是将红包金额和数量进行随机分配,以实现公平、随机的抢红包效果。解密过程包括生成红包、抢红包、确认抢红包和查看红包等步骤。然后通过随机生成红包金额,实现了红包的发放和抢红包的功能。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

0 人点赞