java抢红包实现

目录

总体思想

红包算法

个人看法

控制器

测试效果

优化点

lua脚本的好处


之前看过一篇文章介绍抢红包的,现在自己搞一哈

总体思想

说下大概思路,有一种是抢一个红包,那么下一个拿到的是总数-抢到的钱数,然后再去随机

另一种是先把钱拆分好,然后再按人头去分,这一篇主要是这种方法

拆分完之后放到redis list,然后通过leftpop进行输出

红包算法

参考网上的,然后个人再新增一个函数,输出拆分红包的list:

package com.example.demo.entity;

import java.math.BigDecimal;
import java.util.LinkedList;
import java.util.List;

public class Red {
    private int remain;//金额,单位厘
    private int count;//个数
    private Precision precision;//精度
    private int max;//上限,单位厘
    private int min;//下限,单位厘
    private int[] redPool;
    private int index;

    public static void main(String[] args) {
        getRedPackage(10000 * 100, 5000, 280, 1 * 10);
    }

    /**
     * 拆分红包
     * @param money 总钱数 (分)
     * @param count 红包个数
     * @param max 最大的红包(分)
     * @param min 最小红包(分)
     * @return
     */
    public static List<Double> getRedPackage(int money, int count, int max, int min) {
        Red red = Red.newInstance(money * Precision.FEN.getPre(), count, Precision.FEN, max * Precision.FEN.getPre(), min * Precision.FEN.getPre());
        List<Double> RedPackageList = new LinkedList<>();
        BigDecimal bigDecimal = new BigDecimal(money);
        BigDecimal bigDecimal1 = new BigDecimal(100);
        System.out.println("总钱数为:" + bigDecimal.divide(bigDecimal1) + "元");
        for (int i = 0; i < count; i++) {
            int money1 = red.getRed();
            BigDecimal bigDecimal2 = new BigDecimal(money1);
            BigDecimal bigDecimal3 = new BigDecimal(1000);
            double lastMoney = bigDecimal2.divide(bigDecimal3).doubleValue();
            RedPackageList.add(lastMoney);
            System.out.println("拆分红包:" + lastMoney + "元");
        }
        return RedPackageList;
    }

    public int getRed() {
        return index < count ? redPool[index++] : 0;
    }

    public static Red newInstance(int money, int count, Precision precision, int max, int min) {
        Red red = new Red(money, count, precision, max, min);
        String msg;
        if ("".equals(msg = red.validate())) return red;
        else throw new RuntimeException(msg);
    }

    private Red(int money, int count, Precision precision, int max, int min) {
        this.remain = money;
        this.count = count;
        this.precision = precision;
        this.max = max;
        this.min = min;
        init();
    }

    private void init() {
        redPool = new int[count];
        int remain_ = remain;
        for (int i = 0; i < count - 1; i++) {
            int max = getRealMax(remain_, count - i);
            int min = getRealMin(remain_, count - i);
            int money = ((int) (Math.random() * (max - min + precision.getPre())) + min)
                    / precision.getPre() * precision.getPre();//[min, realMax]
            remain_ -= money;
            redPool[i] = money;
        }
        redPool[count - 1] = remain_;
        randomPool();
    }

    private void randomPool() {
        for (int i = 0; i < count; i++) {
            int index = (int) (Math.random() * count);
            int temp = redPool[i];
            redPool[i] = redPool[index];
            redPool[index] = temp;
        }
    }

    private int getRealMax(int remain, int count) {
        int calMax = remain - ((count - 1) * min);
        return Math.min(calMax, max);
    }

    private int getRealMin(int remain, int count) {
        int calMin = remain - ((count - 1) * max);
        return Math.max(calMin, min);
    }

    public int getRemain() {
        return remain;
    }

    public void setRemain(int remain) {
        this.remain = remain;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getMax() {
        return max;
    }

    public void setMax(int max) {
        this.max = max;
    }

    public int getMin() {
        return min;
    }

    public void setMin(int min) {
        this.min = min;
    }

    public Precision getPrecision() {
        return precision;
    }

    public void setPrecision(Precision precision) {
        this.precision = precision;
    }

    private String validate() {
        String msg = "";
        if (remain <= 0) {
            msg = "余额不能为0";
        } else if (remain % precision.getPre() != 0) {
            msg = "余额的精度不对";
        } else if (count <= 0) {
            msg = "红包个数必须为正数";
        } else if (max % precision.getPre() != 0) {
            msg = "上限的精度不对";
        } else if (max <= min) {
            msg = "上限必须大于下限";
        } else if (min % precision.getPre() != 0) {
            msg = "下限的精度不对";
        } else if (min <= 0) {
            msg = "下限必须大于0";
        } else if (getRealMax(remain, count) < getRealMin(remain, count)) {
            msg = "上下限设置错误";
        }
        return msg;
    }

}

enum Precision {
    LI(1),
    FEN(10),
    JIAO(100),
    YUAN(1000);

    private int pre;

    private Precision(int pre) {
        this.pre = pre;
    }

    public int getPre() {
        return pre;
    }
}

个人看法

个人评论一下,在下面这个函数中,你需要将max设计很有水平才能实现拆分效果会比较好,不然就一堆0.01

public static List<Double> getRedPackage(int money, int count, int max, int min)

 

控制器

package com.example.demo.Controller;


import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

import static com.example.demo.entity.Red.getRedPackage;

@RestController
public class RedController {

    @Resource
    private RedisTemplate redisTemplate;

    private static String key = "redpackage";

    @RequestMapping("/red")
    public void getRed() throws InterruptedException {
        List<Double> list = getRedPackage(10000 * 100, 5000, 280, 1 * 10);
        for(double d:list){
            redisTemplate.opsForList().leftPush(key, d);
        }

        ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
        long time = System.nanoTime();
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 5000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    countDownLatch.countDown();
                    /*if (concurrentHashMap.get(Thread.currentThread().getName()) != null) {
                        System.out.println("该线程已经抢过");
                    } else {
                        concurrentHashMap.put(Thread.currentThread().getName(), 1);
                    }*/
                    System.out.println(Thread.currentThread().getName()+"抢到红包:"+redisTemplate.opsForList().leftPop(key));
                }
            }).start();
        }
        countDownLatch.await();
        long time1 = System.nanoTime();
        System.out.println("耗时" + (time1 - time));
    }

}

 

测试效果

大概一秒可以完成1W个抢红包,跟原来看到的文章有所出入,因为另一位作者是使用redis+lua实现的,包括防止用户重复抢,使用hset,我这里使用redisTemplate好像没有hset这个功能,所以这里考虑使用Map来保存,实际开发如果不是高并发的话,可以保存到数据库中,进行查询用户是否已经领取红包。不可能都保存到map中,一个红包一个map,占用内存巨大的。

 

优化点

除了上面说的使用hset保存用户抢到的红包,去重。之外可以考虑将用户的请求全部放到一个队列,然后再批量抢红包,使用lua脚本实现。

 

lua脚本的好处

1)通过类似机器语言,相当执行一条语句,来保证线程安全

2)一次性执行完,减少redis反复请求,减少网络请求

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值