Redis实战之抢红包

举报
LoneWalker、 发表于 2023/08/27 16:47:48 2023/08/27
【摘要】 Redis实战之抢红包

前言🔍

上一篇:

本篇涉及相关命令:Lpush、Expire、Hset、Lpop

示例基于SpringBoot 2.7.14

Lpush

Lpush 命令将一个或多个值插入到列表头部。 如果 key 不存在,一个空列表会被创建并执行 Lpush 操作。 当 key 存在但不是列表类型时,返回一个错误。

Expire

Expire 命令用于设置 key 的过期时间。key 过期后将不再可用。

返回值:设置成功返回 1 。 当 key 不存在或者不能为 key 设置过期时间时返回 0 。

Hset

Hset 命令用于为哈希表中的字段赋值 。如果哈希表不存在,一个新的哈希表被创建并进行 Hset 操作。如果字段已经存在于哈希表中,旧值将被覆盖。

Lpop

Lpop 命令用于移除并返回列表的第一个元素。

返回值:列表的第一个元素。 当列表 key 不存在时,返回 nil 。

示例💡

分析:

  • 1个大红包被拆分为多个小红包
  • 红包要有时效性
  • 每个人只能抢一次
  • 总金额要具有原子性

使用Redis的List保存拆分后的小红包,使用Redis的Hash将用户抢到的红包信息存起来,再次抢该红包时判断之前是否已抢过。

@RequestMapping("/redis")
@RestController
public class RedisController {

    @Resource
    private RedisService redisService;
/**
     * 发红包
     * @param totalMoney 总金额
     * @param number     个数
     * @return
     */
    @PostMapping("/give")
    public String give(int totalMoney,int number){
        return redisService.give(totalMoney, number);
    }

    /**
     * 抢红包
     */
    @GetMapping("/grab")
    public int grab(String redPackageId,String userId) {
        return redisService.grab(redPackageId, userId);
    }
}    

service

@Slf4j
@Service
public class RedisService {

    public String give(int totalMoney, int number) {
        //拆解红包
        List<Integer> packet = this.splitRedPackage(totalMoney, number);
        String id = IdUtil.simpleUUID();
        String key = RedisKey.RED_PACKAGE_KEY+ id;
        //采用list存储红包
        RedisUtil.leftPush(key,packet);
        //设置1天过期
        RedisUtil.expireByDay(key,1);
        log.info("拆解红包{}={}",key,packet);
        return id;
    }

    public int grab(String redPackageId,String userId){
        //第一步:验证该用户是否抢过
        Object checkResult = RedisUtil.hashGet(RedisKey.RED_PACKAGE_CONSUME_KEY + redPackageId, userId);
        if(ObjectUtil.isNull(checkResult)){
            //第二步:从list队列,弹出一个红包
            Object redPackage = RedisUtil.leftPop(RedisKey.RED_PACKAGE_KEY + redPackageId);
            if(ObjectUtil.isNotNull(redPackage)){
                //第三步:抢到红包存起来
                Map<String,Object> map = new HashMap<>();
                map.put(userId,redPackage);
                RedisUtil.hashSet(RedisKey.RED_PACKAGE_CONSUME_KEY+redPackageId,map);
                log.info("用户={}抢到{}",userId,redPackage);
                //TODO 异步把数据落地到数据库上
                return (Integer) redPackage;
            }
            //-1 代表红包已抢完
            return -1;
        }
        //-2 该用户已抢过
        return -2;
    }

    private List<Integer> splitRedPackage(int totalMoney, int number) {
        Integer[] redPackageNumbers = new Integer[number];
        int useMoney = 0;
        for (int i = 0; i < number; i++) {
            if (i == number - 1) {
                //剩余的金额都给最后一个红包
                redPackageNumbers[i] = totalMoney - useMoney;
            } else {
                //二倍均值算法,每次拆分后塞进子红包的金额 = 随机区间(0,(剩余红包金额M ÷ 未被抢的剩余红包个数N) * 2)
                int avgMoney = ((totalMoney - useMoney) / (number - i)) * 2;
                redPackageNumbers[i] = 1 + new Random().nextInt(avgMoney - 1);
            }
            useMoney = useMoney + redPackageNumbers[i];
        }
        return Arrays.asList(redPackageNumbers);
    }

    private Integer[] splitRedPackage(int totalMoney, int number) {
        Integer[] redPackageNumbers = new Integer[number];
        int useMoney = 0;
        for (int i = 0; i < number; i++) {
            if (i == number - 1) {
                //剩余的金额都给最后一个红包
                redPackageNumbers[i] = totalMoney - useMoney;
            } else {
                //二倍均值算法,每次拆分后塞进子红包的金额 = 随机区间(0,(剩余红包金额M ÷ 未被抢的剩余红包个数N) * 2)
                int avgMoney = ((totalMoney - useMoney) / (number - i)) * 2;
                redPackageNumbers[i] = 1 + new Random().nextInt(avgMoney - 1);
            }
            useMoney = useMoney + redPackageNumbers[i];
        }
        return redPackageNumbers;
    }
}

RedisUtil

@Component
public class RedisUtil {
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 将一个或多个值插入到列表头部
     *
     * @param key   键
     * @param value 值
     * @return true为成功
     */
    public static Boolean leftPush(String key, List<?> value) {
        try {
            redisTemplate.opsForList().rightPush(key, value.toArray());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 指定缓存失效时间
     *
     * @param key 键
     * @param day 天数
     * @return Boolean
     */
    public static Boolean expireByDay(String key, Integer day) {
        try {
            if (day > 0) {
                redisTemplate.expire(key, day, TimeUnit.DAYS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 用于返回哈希表中指定字段的值。
     *
     * @param key  键
     * @param item 项
     * @return 值
     */
    public static Object hashGet(String key, String item) {
        if (StrUtil.isEmpty(key) || StrUtil.isEmpty(item)) {
            return null;
        }
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 用于移除并返回列表的第一个元素。
     *
     * @param key 键
     * @return 元素
     */
    public static Object leftPop(String key) {
        try {
            return redisTemplate.opsForList().leftPop(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 用于同时将多个 field-value (字段-值)对设置到哈希表中。
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public static Boolean hashSet(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}    

常量

public class RedisKey {
    public static final String RED_PACKAGE_KEY = "redPackage";
    public static final String RED_PACKAGE_CONSUME_KEY = "redPackageConsume";
}

演示📖

发10块钱5个包

模拟五个用户去抢

抢完了就返回-2

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。