微信红包设计流程讲解与实战分析

277 阅读4分钟

前言

微信红包作为大家耳熟能详的一种互动方式,其背后的技术支持包含多个方面。从用户发出红包到红包被抢完,涉及到的流程包括发红包、红包存储、红包拆分以及抢红包等。本文将详细介绍这一系列流程,并通过代码案例来实践讲解,特别重点分析红包的拆分算法。

微信红包设计流程

整个红包流程按照发红包、红包拆分、抢红包的顺序来设计。在数据结构的选择上,考虑到抢红包的高并发特性和即时响应要求,采用Redis非关系数据库进行设计是优于MySQL的。Redis的每个命令都是单线程执行,保证原子性操作,无需额外的锁机制。

1. 发红包

一个红包通常会被拆分成多个小红包,例如100元可以拆分为20元、20元、20元、30元和10元。这种情况下,可以使用Redis的list结构来存储这些拆分后的小红包。

2. 抢红包

在高并发环境下,如何保证多线程抢红包时不加锁且保持原子性是关键。Redis的特性使得每个命令都是单线程且原子性的,因此使用LPOP命令即可实现。

3. 记红包

为了确保同一个用户不能抢夺同一个红包两次,需要记录哪些红包被哪些用户抢过。这里可以使用Redis的hash结构来进行存储。

拆红包算法

在拆红包算法中,较为合理的是采用“二倍均值算法”。该算法的核心思想是每次拆分红包时,取一个随机区间,其最大值为剩余红包金额的两倍与未被抢的剩余红包个数的乘积。这样可以保证拆分的随机性和公平性。

代码实现

二倍均值算法的具体代码实现如下:

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 {
            int avgMoney = ((totalMoney - useMoney) / (redPackageNumber - i)) * 2;
            splitRedPackageNumbers[i] = 1 + new Random().nextInt(avgMoney - 1);
        }
        useMoney += splitRedPackageNumbers[i];
    }
    return splitRedPackageNumbers;
}

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

splitRedPackageNumbers[i] = 1 + new Random().nextInt(avgMoney - 1);

首先,前面加1的原因是new Random().nextInt(avgMoney -1)会生成0到avgMoney - 1之间的随机数,但不能为0。为了保证第一个红包不为0元,所以需要+1。其次,avgMoney - 1是为了控制随机数的最大值接近于avgMoney但不等于它。这样可以确保最后一个红包也不会为0元。如果不这样做,有可能每次随机数都取最大值,导致最后剩余的红包金额只能为0。

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

红包useMoneyavgMoney每次随机数最大本次红包金额
1020 * 2new Random().nextInt(avgMoney) == 3940
24015*2new Random().nextInt(avgMoney) == 2930
37010*2new Random().nextInt(avgMoney) == 1920
4905*2new Random().nextInt(avgMoney) == 910
500

发红包

发红包的主要步骤是将拆分后的小红包保存到Redis的list结构中,并设置过期时间。具体代码如下:

@RequestMapping(value = "/send")
public String sendRedPackage(int totalMoney, int redPackageNumber){
    Integer[] splitRedPackages = splitRedPackageAlgorithm(totalMoney,redPackageNumber);
    String key = RED_PACKAGE_KRY + IdUtil.simpleUUID();
    redisTemplate.opsForList().leftPushAll(key,splitRedPackages);
    redisTemplate.expire(key,1, TimeUnit.DAYS);
    return key+"\t" + Ints.asList(Arrays.stream(splitRedPackages).mapToInt(Integer::valueOf).toArray());
}

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

image.png

抢红包

抢红包的逻辑是先验证用户是否已经抢过该红包,如果没有则允许其抢红包并记录到Redis中。具体代码如下:

@RequestMapping(value = "/rob")
public String robRedPackage(String redPackageKey,String userId){
    Object redPackage = redisTemplate.opsForHash().get(RED_PACKAGE_CONSUME_KRY + redPackageKey, userId);
    if (redPackage == null){
        Object partRedPackage = redisTemplate.opsForList().leftPop(RED_PACKAGE_KRY + redPackageKey);
        if (partRedPackage != null){
            redisTemplate.opsForHash().put(RED_PACKAGE_CONSUME_KRY+redPackageKey,userId,partRedPackage);
            System.out.println("用户"+userId+" 抢到红包了 " + partRedPackage);
            return String.valueOf(partRedPackage);
        }
        return "errorCode : -1 ,红包抢完了";
    }
    return "errorCode : -2 ," + userId + " 你已经抢过红包了";
}

调用抢红包接口后,用户可以返回抢到的红包金额,并且在Redis中也能查看到相应的红包记录。

image.png

总结

本文主要介绍了微信红包的拆分、发放和抢红包的流程,并重点讲解了二倍均值法在拆红包算法中的应用。通过随机生成红包金额的方式,实现了公平且随机的抢红包效果。整个过程中涉及到了Redis数据库的高效利用以及高并发环境下的数据处理问题。