java实现抢红包功能

最近项目中,实现了抢红包的功能,觉得挺有意思,这边根据自己的实战经验做个总结,抢红包主要面临两个问题,一个是红包的分配算法,一个是抢红包的并发问题。

红包并发问题

并发就会考虑用锁,面对集群项目,得采用分布式锁,比如是用redis锁,在抢的时候做处理,保证抢红包的原子性,另一种方式也就是我目前所用的方式,预算红包金额,并放入队列,也就是提前先把红包存入队列,在用户抢红包的时候从队列里吐出来,这种方式很好的避开了抢红包是的并发问题,也方便下面说的红包算法的计算。
我这里使用redis作为队列,ListOperations.leftPush(),ListOperations.rightPop(),但有个小问题,就是队列没有失效时间,所以如果不取出的话,会一直存在队列里。

红包分配算法

红包算法得满足:
1.每个小红包加起来等于总金额
2.小红包的金额满足正态分布,两端的值比较少,中间值比较多
3.每个人的平分这个大红包的概率是相等的
这里我是用的是***线段切割法***,不BB,直接上代码

public static void distributeRedPacket(BigDecimal totalAmount, int num) {
        int amount = totalAmount.multiply(new BigDecimal("100")).intValue();
        Set<Integer> points = new TreeSet<>();
        Random random = new Random();
        while (points.size() < num - 1) {
            points.add(random.nextInt(amount - 1) + 1);
        }
        points.add(amount);
        int before = 0;
        for (int point : points) {
            System.out.println((double) (point - before) / 100);
            before = point;
        }
    }

    public static void main(String[] args) {
        distributeRedPacket(new BigDecimal(12),5);
    }
// 运行结果
  0.45
  4.9
  2.76
  3.72
  0.17

先把金钱统一乘100转成分来做,然后把总金额切割成n-1块,概率都是1/总金额,如果出现重复的点,则跳过,继续切割,最后金额顺序排序,两两相减,就好像切肉一样,分成一块一块的,下刀点就是切割点,切出的肉块,就是我们的小红包(说的我都流口水了?)。这样小红包总和肯定也等于总金额。

红包分配算法进阶

突然产品提出他想实现新用户获取大值面额的概率更高时,这时候就要想点法子了。我们现在已经是事先预分配好了红包面值,那么就不能再新用户抢红包时,再去算出高面值的红包了。这时候就要利用排序加权分配法(我自己起的。。)。
首先在预分配时对红包大小进行从小到大的排序,再根据产品需要的比例,比如新用户百分之80获得大概率的红包,这时候如果是新用户则先匹配概率,如果在百分之80之内,那么就从队尾pop,如果是老用户或者没有匹配到百分之80内,那么就从队头pop,但用户们抢红包的顺序,决定了红包大小的顺序,第一个新用户抢的可能就是最大的,因为之前红包是按照大小排序的,那么可以对红包的排序稍微做些修改,先取出红包的中数,再把大的放在右边,无序,小的放在左边,也是无序,这样就可以解决上面的问题了。
这些就是我在项目中遇到的问题,如果有不足,还望指出,如果有各种奇葩的分配红包需求,也可以一起讨论,一起进步。