基于Redis实现微信抢红包功能

一、需求分析

  • 发红包:如果是等额金额红包这个好计算,要是随机金额红包该怎能实现?
  • 抢红包:一个红包每个人只能抢一次,怎么记录这个人已经抢过,防止重复抢?而且不会因为抢红包的先后顺序而造成不公平?
  • 并发性:这种情况肯定是多人共同抢一个红包,怎么保证高并发,而且不会出现超抢的情况?

二、具体实现

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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!