一: 有并发情况下商品的库存扣减可以采用分段锁的方式:
public Boolean subtractionAwardStock(String cacheKey, Date endDateTime) {
try {
long surplus = redisService.decr(cacheKey);
if (surplus == 0) {
// 库存消耗没了以后,发送MQ消息,更新数据库库存
eventPublisher.publish(activitySkuStockZeroMessageEvent.topic(), activitySkuStockZeroMessageEvent.buildEventMessage(sku));
} else if (surplus < 0) {
// 库存小于0,恢复为0个
redisService.setAtomicLong(cacheKey, 0);
return false;
}
// 1. 按照cacheKey decr 后的值,如 99、98、97 和 key 组成为库存锁的key进行使用。
// 2. 加锁为了兜底,如果后续有恢复库存,手动处理等,也不会超卖。因为所有的可用库存key,都被加锁了。
// 生成锁的键
String lockKey = cacheKey + Constants.UNDERLINE + surplus;
// 设置合理的过期时间
long expireMillis = calculateExpireMillis(endDateTime);
Boolean lock = redisService.setNx(lockKey, expireMillis, TimeUnit.MILLISECONDS);
if (!lock) {
log.info("策略奖品库存加锁失败 {}", lockKey);
}
return lock;
} catch (Exception e) {
log.error("扣减库存并加锁失败", e);
return false;
}
}
private long calculateExpireMillis(Date endDateTime) {
if (endDateTime != null) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(endDateTime);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 0);
long endOfDayMillis = calendar.getTimeInMillis();
long now = System.currentTimeMillis();
return Math.max(endOfDayMillis - now, TimeUnit.MINUTES.toMillis(1)); // 至少1分钟过期时间
} else {
return TimeUnit.MINUTES.toMillis(1); // 默认1分钟过期时间
}
}
二: 在活动已经开始并且已经扣减库存的情况下,如果运营临时要求增加库存,我们需要考虑以下几个方面:
1 增加库存:使用INCRBY命令增加库存数量。
2 处理分段锁:由于之前已经加了一部分分段锁,增加库存后可能会导致库存数量跨越多个分段锁,需要重新评估和调整这些锁。
public Boolean incrementAwardStock(String cacheKey, int increment, Date endDateTime) {
try {
// 增加库存
long newStock = redisService.incrBy(cacheKey, increment);
// 获取当前库存的分段锁
for (int i = 0; i < increment; i++) {
String currentLockKey = cacheKey + Constants.UNDERLINE + (newStock - increment + i);
redisService.del(currentLockKey); // 释放旧的分段锁
}
return true;
} catch (Exception e) {
log.error("增加库存并释放锁失败", e);
return false;
}
}