> 文章列表 > Redis7之抢红包案例

Redis7之抢红包案例

Redis7之抢红包案例

1.需求

  1. 红包

  2. 抢红包

    • 不加锁保证原子性,支持高并发

    • 每人只能抢一次

  3. 记红包

    • 记录每人抢了多少
  4. 拆红包

    • 红包算法(二倍均值法)
      • 所有人抢到金额之和等于红包金额,不能超过,也不能少于
      • 每个人至少抢到一分钱
      • 要保证所有人抢到金额的几率相等

二倍均值法

每次抢到的金额 = 随机取件(0,(剩余红包金额 ÷ 剩余人数N)× 2)

这个公式保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平

2. 编码

  • 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 {@Autowiredprivate 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";@Autowiredprivate 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;}
}
  • 测试

    • http://localhost:7070/send?totalMoney=100&redPackageNumber=5

      Redis7之抢红包案例
      Redis7之抢红包案例

    • http://localhost:7070/rob?redPackageKey=b82b3408c2b541eab29b15656c5a7747&userId=1

      Redis7之抢红包案例

    Redis7之抢红包案例

    Redis7之抢红包案例

3. 批量删除(扩展)

“***” 代表 key前缀

redis-cli keys "***" | xargs redis-cli del