Redis实战之抢红包
【摘要】 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)