1. Demand
-
Red envelopes
-
Grab a red envelope
-
No locking guarantees atomicity and supports high concurrency
-
Each person can only grab once
-
-
Remember the red envelope
- Record how much each person robbed
-
Open the red envelope
- Red envelope algorithm (double mean method)
- The sum of the amount grabbed by everyone is equal to the amount of the red envelope, which cannot exceed or be less than
- Everyone grabs at least a penny
- To ensure that everyone has the same chance of grabbing the amount
- Red envelope algorithm (double mean method)
double mean method
The amount grabbed each time = random pickup (0, (remaining red envelope amount ÷ remaining number of people N) × 2)
This formula ensures that the average value of each random amount is equal, and will not cause unfairness due to the order of grabbing red envelopes
2. Coding
- RedPackageService
import cn.hutool.core.util.IdUtil;
import com.google.common.primitives.Ints;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 模拟抢红包
*
* @author 晓风残月Lx
* @date 2023/4/5 16:00
*/
@Service
public class RedPackageService {
@Autowired
private RedisTemplate redisTemplate;
public static final String RED_PACKAGE_KEY = "redpackage:";
public static final String RED_PACKAGE_CONSUME_KEY = "redpackage:consume";
/**
* 拆红包的算法 ---> 二倍均值算法
*
* @param totalMoney
* @param redpackageNumber
* @return
*/
public Integer[] splitRedPackageAlgorithm(int totalMoney, int redpackageNumber) {
Integer[] redPackageNums = new Integer[redpackageNumber];
// 已经被抢夺的红包金额
int useMoney = 0;
for (int i = 0; i < redpackageNumber; i++) {
if (i == redpackageNumber - 1) {
redPackageNums[i] = totalMoney - useMoney;
} else {
// 二倍均值算法,每次拆分后塞进子红包的金额
// 金额 = 随机取件(0,(剩余红包金额 / 未被抢的剩余红包数 N )* 2 )
int avgMoney = ((totalMoney - useMoney) / (redpackageNumber - i)) * 2;
redPackageNums[i] = 1 + new Random().nextInt(avgMoney - 1);
}
useMoney = useMoney + redPackageNums[i];
}
return redPackageNums;
}
/**
* 发红包
*
* @param totalMoney
* @param redPackageNumber
*/
public String sendRedPackage(int totalMoney, int redPackageNumber) {
// 1. 拆红包,将总金额totalMoney拆分成 redpackageNumber 个子红包
Integer[] splitRedPackages = splitRedPackageAlgorithm(totalMoney, redPackageNumber); // 拆分红包算法(二倍均值算法)通过后获得的多个子红包数组
// 2. 发红包并保存进 list 结构 并且设置过期时间
String key = RED_PACKAGE_KEY + IdUtil.simpleUUID();
redisTemplate.opsForList().leftPushAll(key, splitRedPackages);
redisTemplate.expire(key, 1, TimeUnit.DAYS);
// 3.发红包ok,返回前台显示
return key + "\t" + Ints.asList(Arrays.stream(splitRedPackages).mapToInt(Integer::valueOf).toArray());
}
public String robRedPackage(String redPackageKey, String userId) {
// 1.验证某个用户是否抢过红包,不可以多抢
Object redPackage = redisTemplate.opsForHash().get(RED_PACKAGE_CONSUME_KEY + redPackageKey, userId);
// 2.没有抢过红包可以抢,如果抢过返回 -2 表示抢过
if (null == redPackage) {
// 2.1 从 list 结构中出队一个作为抢的红包
Object partRedPackage = redisTemplate.opsForList().leftPop(RED_PACKAGE_KEY + redPackageKey);
if (partRedPackage != null) {
// 2.2 抢到红包后 需要记录到hash结构中 记录每人抢到的红包
redisTemplate.opsForHash().put(RED_PACKAGE_CONSUME_KEY + redPackageKey, userId, partRedPackage);
System.out.println("用户" + userId + "\t 抢到了多少钱的红包:" + partRedPackage);
// TODO 后续异步进mysql或者MQ进一步做统计处理,每一年你发出多少红包,抢到了多少红包
return String.valueOf(partRedPackage);
}
// 2. 抢完了
return "errorCode: -1, 红包抢完了";
} else {
// 3. 抢过了红包
int myRedPackage = (int) redisTemplate.opsForHash().get(RED_PACKAGE_CONSUME_KEY + redPackageKey, userId);
return "errorCode: -2, message:" + userId + "\t 你已经抢过了红包,不能在抢了,你抢的金额是" + myRedPackage;
}
}
}
- RedPackageController
import com.xfcy.service.RedPackageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 模拟抢红包,模拟整数的
* @author 晓风残月Lx
* @date 2023/4/5 15:53
*/
@RestController
public class RedPackageController {
public static final String RED_PACKAGE_KEY = "redpackage:";
public static final String RED_PACKAGE_CONSUME_KEY = "redpackage:consume";
@Autowired
private RedPackageService redPackageService;
@RequestMapping("/send")
public String sendRedPackage(int totalMoney, int redPackageNumber) {
String str = redPackageService.sendRedPackage(totalMoney, redPackageNumber);
return str;
}
@RequestMapping("/rob")
public String robRedPackage(String redPackageKey, String userId) {
String str = redPackageService.robRedPackage(redPackageKey, userId);
return str;
}
}
-
test
-
http://localhost:7070/send?totalMoney=100&redPackageNumber=5
-
http://localhost:7070/rob?redPackageKey=b82b3408c2b541eab29b15656c5a7747&userId=1
-
3. Batch delete (extended)
"***" stands for key prefix
redis-cli keys "***" | xargs redis-cli del