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 {
    
    

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

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

      在这里插入图片描述
      在这里插入图片描述

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

      在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

3. 批量删除(扩展)

“***” 代表 key前缀

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

猜你喜欢

转载自blog.csdn.net/m0_55993923/article/details/130035592
今日推荐