隨機瓜分百萬紅包


年關將近,各類促銷活動即將上線,類似支付寶集五福的那種,用戶湊齊卡片之后,可以瓜分百萬紅包。

因為這種瓜分活動集齊的人數肯定是很多的,直接隨機之后再扣減,感覺不是很合適。

參考: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+感覺還是將就了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM