前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解密微信红包算法及抢红包案例实现

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

原创
作者头像
小明爱吃火锅
发布2023-12-13 14:31:55
7061
发布2023-12-13 14:31:55
举报
文章被收录于专栏:小明说Java

前言

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

微信红包设计流程

依照发红包,红包拆分,抢红包的流程来涉及整个红包流程,采用什么数据结构进行红包设计,由于抢红包,是高并发的,并且响应也要及时,所以采用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腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 微信红包设计流程
  • 代码实现
  • 总结
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档