• 随机瓜分百万红包


    年关将近,各类促销活动即将上线,类似支付宝集五福的那种,用户凑齐卡片之后,可以瓜分百万红包。

    因为这种瓜分活动集齐的人数肯定是很多的,直接随机之后再扣减,感觉不是很合适。

    参考:https://www.cnblogs.com/canglong/p/canglong001.html?utm_source=itdadao&utm_medium=referral

    大致思路如下:因为是集卡截止后再进行红包瓜分的,集齐的用户可能几十万上百万,所以就先根据集齐卡片的用户数,先将红包随机瓜分好,存放起来,等到瓜分的时候,直接领取就行了。

    假设5个人集齐,等长度生成一个随机数数组[2,5,9,8,6]。根据这个随机数数组里面的值所占整个数组元素和(30)的比例来计算每个红包的大小,如果是瓜分10快钱。生成的真实红包数组[(2/30)*10,(5/30)*10,...],最后一个不要按比例计算,直接就是是剩余的钱,这样就有可能出现最后的这个金额最大,我们再把生成这个数组重新洗牌一下,这样以保证更好的随机性。最后将这些已经生成好的红包放到redis的list中。等到瓜分红包的时候,每个用户进来直接从list中弹出一个元素就行了,因为这个list本来就是随机生成的。这样也正好满足了随机性了。

    红包数组生成(一些细节都在代码注释里面):

    package com.nijunyang.algorithm.redpackage;
    
    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Random;
    
    /**
     * Description:
     * Created by nijunyang on 2019/12/12 22:03
     */
    public class RedPackageUtils {
    
        /**
         * 按人数随机分配红包
         * @param moneyTotal 红包总额
         * @param number 人数
         * @return 随机红包集合
         */
        public static List<BigDecimal> shareMoney(BigDecimal moneyTotal, int number) {
            if (moneyTotal.compareTo(new BigDecimal(number).multiply(new BigDecimal("0.01"))) < 0) {
                throw  new RuntimeException("每人至少一分钱.");
            }
            // 按分计算,钱转换成分
            long money = moneyTotal.multiply(BigDecimal.valueOf(100)).longValue();
            //生成一个和人数一样的数组,分布随机数,然后计算随机数占比,根据对应占比分钱。
            double randomCount = 0;
            double[] randomArr = new double[number];
            Random random = new Random();
            for (int i = 0; i < number; i++) {
                int r = random.nextInt(number * 100) + 1;  //避免出现0
                randomArr[i] = r;
                randomCount += r;
            }
            // 根据每个随机数占比计算每份红包金额
            long alreadyShare = 0;
            List<BigDecimal> moneyList = new ArrayList<>(number);
            for (int i = 0; i < number; i++) {
                // 每份占比
                double ratio = randomArr[i] / randomCount;
                /**
                 * 向下取整,如果用round,可能导致多个向上舍入之后,最后还没分完,却没钱了,向下取整可以保证正能分完
                 * 这样可能导致最后,最后剩余的那份相对而言多一点,最后再将整个集合重新洗牌shuffle
                 */
                long shareMoney = (long) Math.floor(ratio * money);
                // 几率太小,总数太少,向下取整可能出现0,处理最少1分钱
                if (shareMoney == 0) {
                    shareMoney = 1;
                }
                alreadyShare += shareMoney;
                if (i < number - 1) {
                    moneyList.add(new BigDecimal(shareMoney).divide(new BigDecimal(100)));
                } else {
                    // 最后一份直接把剩余的钱分过去
                    moneyList.add(new BigDecimal(money - alreadyShare + shareMoney).divide(new BigDecimal(100)));
                }
            }
            //洗牌
            Collections.shuffle(moneyList);
            return moneyList;
        }
    }

    在往redis里面放的时候 发现有两个比较坑的地方

    1.ListOperations的 leftPushAll(K var1, Collection<V> var2) 这个方法 是以整个集合为一个元素去放的,等于说使用这个方法push之后,redis的list里面之后一个元素。不知道这个本来就是个bug,还是我对这个方法的理解和写这个方法的人不一样。(spring-data-redis版本2.1.10)

    2..ListOperations的 leftPushAll(K var1, V... var2) 这个方法 数组长度过大无法添加,会报IO异常,因为不知道会有多少集齐,所以我从几万,几十万都没问题,百万就会报IO异常了:(java.io.IOException: 远程主机强迫关闭了一个现有的连接)。测试了下长度100万可以加入,110万长度就会报错了,暂时没有去深入研究,应该代码里面有长度限制的,如果长度太长的话,建议成几个数组,依次添加进去。

    redis代码:方便测试都是用的get请求

        @GetMapping("/push/redpackage/{money}/{number}")
        public ResponseEntity<Long> pushRedPackage(@PathVariable Integer money, @PathVariable Integer number) {
    
            List<BigDecimal> redPackageList = RedPackageUtils.shareMoney(BigDecimal.valueOf(money), number);
            /**
             * leftPushAll(K var1, Collection<V> var2)  以整个集合为一个元素形式存放 并不是单个元素存放
             * leftPushAll(K var1, V... var2)  数组长度过大无法添加,会报IO异常,测试了下长度100万可以110万长度就会报错了
             */
            String[] redPackages = new String[redPackageList.size()];
            for (int i = 0; i < redPackages.length; i++) {
                redPackages[i] = redPackageList.get(i).toString();
            }
            Long length = listOperations.leftPushAll(SHARE_RED_PACKAGE_KEY, redPackages);
            return new ResponseEntity<>(length, HttpStatus.OK);
        }
    
        @GetMapping("/share/redpackage")
        public ResponseEntity<Object> share() {
            Object money = listOperations.leftPop(SHARE_RED_PACKAGE_KEY);
            if (money == null) {
                return new ResponseEntity<>("红包已瓜分完毕", HttpStatus.OK);
            }
            return new ResponseEntity<>(money, HttpStatus.OK);
        }

    用Jmeter试了下,瓜分红包的接口(从redis的list弹出数据)可以达到2000多点的QPS,本地起的单机服务,redis也是装在vmware虚拟机中的1核2G,2000+感觉还是将就了。

  • 相关阅读:
    JVM字节码(七)
    JVM字节码(六)
    JVM字节码(五)
    JVM字节码(四)
    JVM字节码(三)
    JVM字节码(二)
    JVM字节码(一)
    JVM类加载器(五)
    JVM类加载器(四)
    php之 人员的权限管理
  • 原文地址:https://www.cnblogs.com/nijunyang/p/12037705.html
Copyright © 2020-2023  润新知