美文网首页
简述两种红包方案

简述两种红包方案

作者: 西5d | 来源:发表于2019-08-02 07:14 被阅读0次

这篇文章写在飞机晚点严重的一天,晚了10个小时,无力吐槽。到了早上的时候,人越来越多,候机厅比较吵,想起还有个类似问题,值得写一篇文章,于是开搞,很快完成了。奇妙的是,感觉时间过得很快,周围也没有那么吵了,哈哈。
最近遇到要写一个红包程序,这个问题其实有很多可以考虑的地方,是个有意思的问题,我就慢慢说来。

首先,我们最先想到和可能接触最多的是微信的拼手气红包,不管怎么抢,最后都能抢完,而且每个人得到的也基本是分布均匀的;这个其实是一类,还有一类,当时产品要求给单个红包设置限额,比如最少1个额度,最多10个额度,而且有总的红包额度。这种情况下,就会出现比较复杂的情况,用微信的方法是不能满足的要求的。下面分开各自说明下。

红包问题几个实现的关键点:平均分布或基本平均,额度随机,能够分配完,每个都能分到,总额固定不会超出。

举几个例子:限定单个上限的话,前面分配完成了,最后剩余很多,不可取。或者前面分配完了,后面分配不到。又或者单个获得红包额和人数的和,超过了总金额,这种情况可能是并发或者分配策略不当造成。

问题提出来,再去解决问题,首先看看微信是怎么做的。

概况来说,微信有个固定的算法,实时计算获得的红包金额,每次用剩余的额度除以剩余人数的一半作为最大随机边界,即保证能分配完,又能随机,每个人都分配到。以下是核心代码简单逻辑,只需要最开始初始化就可以。


public int randomPacket() {

if (remainSize ==0 || !checkConfig()) {

return -1;

    }

if (remainSize ==1) {

remainSize--;

        return remainCoin;

    }

int max =remainCoin /remainSize *2;

    int t = RandomUtils.nextInt(1, max);

    remainSize--;

    remainCoin -= t;

    return t;

}

下面我们再看看限定上界的方案。当时想的是生成对应的槽位,比如n个人,就有n个槽位,首先用1填充,可以默认为1,然后剩下的槽位随机多次遍历,用1到上限的随机数填充,达最大,跳过找下一个,直到所有的额度分配完成,相当于预先生成红包,然后每次就从预生成的中取就可以。但是有几个问题。1、生成过程中多次随机,会很慢,某些概率下会造成假死,一直找不到合适的位置;2、预生成无疑增加了存储的成本;3、多了存储,相应的交互也多了起来,实现会繁重和复杂。下面是个简单的实现例子:


    private int totalCoin;
    private int maxCoin;
    private int minCoin;

    private int size;
    private final AtomicInteger current = new AtomicInteger(0);

    private Map<Integer, Integer> packets;

    public PacketPrepare(int maxCoin, int minCoin, int totalCoin, int size) {
        this.maxCoin = maxCoin;
        this.minCoin = minCoin;
        this.totalCoin = totalCoin;
        this.size = size;
        this.packets = randomPackets();
    }

    private Map<Integer, Integer> randomPackets() {
        Map<Integer, Integer> packets = Maps.newHashMap();
        if (!checkConfig()) {
            return packets;
        }

        int remain = totalCoin;
        Integer t;
        int rand;
        int round = 0;
        while (remain > 0) {
            for (int i = 0; i < size; i++) {
                if (round == 0) {
                    packets.put(i, 1);
                    remain--;
                    continue;
                }
                rand = RandomUtils.nextInt(minCoin - 1, maxCoin);

                if (remain - rand <= 0) {
                    t = packets.get(i);
                    packets.put(i, remain + t);
                    return packets;
                }
                remain -= rand;
                t = packets.get(i);
                if (t == null) {
                    t = 1;
                }
                t += rand;
                packets.put(i, t);
            }
            round++;
        }
        return packets;
    }

    private boolean checkConfig() {
        if (maxCoin <= 0 || minCoin <= 0 || totalCoin <= 0 || size <= 0) {
            return false;
        }
        if (totalCoin < size) {
            return false;
        }
        if (maxCoin >= totalCoin) {
            return false;
        }
        return true;
    }

最后做个总结。鉴于预先生成的方案有各种弊端,最后说服产品统一使用微信的方案,比较业界大头。这里还有个问题要补充,线上服务一般都是多机器部署的,红包肯定不能用内存存储,这里想到了用Redis做记录,一个唯一key,只对应总人数和总额度。内存计算红包后,对人数和额度做减少,而且Redis增减是原子的。当然也可以用MySQL等update set n = n - x ,但这种方式对锁不是很友好,而且也比较繁琐。除了Redis,额外的用MySQL记录些红包基本信息和领取记录,整个就实现完成了。功能还有很多可以改进的点,期待以后更多接触对之进行优化。

相关文章

网友评论

      本文标题:简述两种红包方案

      本文链接:https://www.haomeiwen.com/subject/xoqkdctx.html