目录
之前看过一篇文章介绍抢红包的,现在自己搞一哈
总体思想
说下大概思路,有一种是抢一个红包,那么下一个拿到的是总数-抢到的钱数,然后再去随机
另一种是先把钱拆分好,然后再按人头去分,这一篇主要是这种方法
拆分完之后放到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反复请求,减少网络请求