|
|
@@ -1,6 +1,7 @@
|
|
|
package com.fs.common.core.redis.service;
|
|
|
|
|
|
import com.fs.common.constant.RedisConstant;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
@@ -16,6 +17,7 @@ import java.util.stream.IntStream;
|
|
|
/**
|
|
|
* 高并发库存扣减服务(Java 8 + Redis分布式锁)
|
|
|
*/
|
|
|
+@Slf4j
|
|
|
@Service
|
|
|
public class StockDeductService {
|
|
|
|
|
|
@@ -54,7 +56,7 @@ public class StockDeductService {
|
|
|
public void initStock(Long productId, Integer initStock) {
|
|
|
String stockKey = RedisConstant.STOCK_KEY_PREFIX + productId;
|
|
|
redisTemplate.opsForValue().set(stockKey, initStock, 24 * 60 * 60, TimeUnit.SECONDS);
|
|
|
- System.out.println("商品" + productId + "库存初始化完成,初始库存:" + initStock);
|
|
|
+ log.info("商品" + productId + "库存初始化完成,初始库存:" + initStock);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -64,7 +66,7 @@ public class StockDeductService {
|
|
|
* @param deductNum 扣减数量(默认1)
|
|
|
* @return 扣减结果:true=成功,false=失败
|
|
|
*/
|
|
|
- public CompletableFuture<Boolean> deductStockAsync(Long productId, Integer deductNum) {
|
|
|
+ public CompletableFuture<Boolean> deductStockAsync(Long productId, Integer deductNum, Long userId) {
|
|
|
// Java 8 CompletableFuture 异步处理,提升高并发吞吐量
|
|
|
return CompletableFuture.supplyAsync(() -> {
|
|
|
// 1. 参数校验(Java 8 Optional 空值处理)
|
|
|
@@ -72,13 +74,10 @@ public class StockDeductService {
|
|
|
String stockKey = RedisConstant.STOCK_KEY_PREFIX + productId;
|
|
|
String lockKey = RedisConstant.LOCK_KEY_PREFIX + productId;
|
|
|
|
|
|
- // 2. 生成锁持有者唯一标识(UUID + 线程ID,避免误释放)
|
|
|
- String lockOwner = UUID.randomUUID().toString() + "-" + Thread.currentThread().getId();
|
|
|
-
|
|
|
// 3. 尝试获取分布式锁(非阻塞重试,Java 8 Stream API 实现重试)
|
|
|
// 3. 尝试获取分布式锁(优化:加入随机延迟,避免惊群效应)
|
|
|
boolean isLockAcquired = IntStream.range(0, RedisConstant.LOCK_MAX_RETRY).anyMatch(retryCount -> {
|
|
|
- Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, lockOwner, RedisConstant.LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
+ Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, userId, RedisConstant.LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
if (Boolean.TRUE.equals(result)) {
|
|
|
return true;
|
|
|
}
|
|
|
@@ -102,7 +101,7 @@ public class StockDeductService {
|
|
|
// 5. 执行库存扣减Lua脚本(原子操作,防超卖)
|
|
|
// 新增日志:打印当前库存值和扣减数量
|
|
|
Integer currentStockStr = (Integer) redisTemplate.opsForValue().get(stockKey);
|
|
|
- System.out.println("拿到锁成功 → 库存Key:" + stockKey + ",当前库存值:" + currentStockStr + ",扣减数量:" + num);
|
|
|
+ log.info("拿到锁成功 → 库存Key:{},当前库存值:{},扣减数量:{}", stockKey, currentStockStr, num);
|
|
|
|
|
|
// 执行库存扣减Lua脚本
|
|
|
Long remainingStock = redisTemplate.execute(
|
|
|
@@ -112,20 +111,28 @@ public class StockDeductService {
|
|
|
);
|
|
|
|
|
|
// 新增日志:打印Lua返回结果
|
|
|
- System.out.println("Lua脚本返回值:" + remainingStock);
|
|
|
+ log.info("Lua脚本返回值:{}", remainingStock);
|
|
|
|
|
|
// 6. 判断扣减结果
|
|
|
- if (remainingStock != null && remainingStock >= 0) {
|
|
|
- System.out.println("商品" + productId + "库存扣减成功,剩余库存:" + remainingStock);
|
|
|
+ if (remainingStock >= 0) {
|
|
|
+ log.info("商品{}库存扣减成功,剩余库存:{}", productId, remainingStock);
|
|
|
return true;
|
|
|
} else {
|
|
|
- System.err.println("商品" + productId + "库存不足,扣减失败");
|
|
|
+ String errorMsg = "";
|
|
|
+ switch (remainingStock.intValue()) {
|
|
|
+ case -1: errorMsg = "库存不足"; break;
|
|
|
+ case -2: errorMsg = "库存Key不存在"; break;
|
|
|
+ case -3: errorMsg = "库存值非数字"; break;
|
|
|
+ case -4: errorMsg = "扣减数量无效"; break;
|
|
|
+ default: errorMsg = "未知错误,错误码:" + remainingStock;
|
|
|
+ }
|
|
|
+ log.info("商品{}扣减失败:{}", productId, errorMsg);
|
|
|
return false;
|
|
|
}
|
|
|
} finally {
|
|
|
// 7. 释放分布式锁(Lua脚本保证原子性,仅释放自己持有的锁)
|
|
|
- redisTemplate.execute(LOCK_RELEASE_SCRIPT, Collections.singletonList(lockKey), lockOwner);
|
|
|
- System.out.println("商品" + productId + "锁释放成功,持有者:" + lockOwner);
|
|
|
+ redisTemplate.execute(LOCK_RELEASE_SCRIPT, Collections.singletonList(lockKey), userId);
|
|
|
+ log.info("商品{}锁释放成功,持有者:{}", productId, userId);
|
|
|
}
|
|
|
});
|
|
|
}
|