|
|
@@ -0,0 +1,132 @@
|
|
|
+package com.fs.common.core.redis.service;
|
|
|
+
|
|
|
+import com.fs.common.constant.RedisConstant;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Optional;
|
|
|
+import java.util.Random;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.stream.IntStream;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 高并发库存扣减服务(Java 8 + Redis分布式锁)
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class StockDeductService {
|
|
|
+
|
|
|
+ // 注入RedisTemplate
|
|
|
+ public final RedisTemplate<String, Object> redisTemplate;
|
|
|
+
|
|
|
+ // 构造器注入(Spring 推荐,Java 8 支持)
|
|
|
+ public StockDeductService(RedisTemplate<String, Object> redisTemplate) {
|
|
|
+ this.redisTemplate = redisTemplate;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 库存扣减Lua脚本(预编译,提升高并发性能)
|
|
|
+ private static final DefaultRedisScript<Long> STOCK_DEDUCT_SCRIPT;
|
|
|
+ // 锁释放Lua脚本(预编译)
|
|
|
+ private static final DefaultRedisScript<Long> LOCK_RELEASE_SCRIPT;
|
|
|
+
|
|
|
+ // 库存扣减Lua脚本(优化后,增强健壮性)
|
|
|
+ static {
|
|
|
+ // 初始化库存扣减脚本
|
|
|
+ STOCK_DEDUCT_SCRIPT = new DefaultRedisScript<>();
|
|
|
+ STOCK_DEDUCT_SCRIPT.setScriptText("if redis.call('exists', KEYS[1]) ~= 1 then " + "return -2; " + "end " + "local stock_str = redis.call('get', KEYS[1]); " + "local stock = tonumber(stock_str); " + "if stock == nil then " + "return -3; " + "end " + "local deductNum_str = ARGV[1]; " + "local deductNum = tonumber(deductNum_str); " + "if deductNum == nil or deductNum <= 0 then " + "return -4; " + "end " + "if stock >= deductNum then " + "return redis.call('decrby', KEYS[1], deductNum); " + "else " + "return -1; " + "end");
|
|
|
+ STOCK_DEDUCT_SCRIPT.setResultType(Long.class);
|
|
|
+
|
|
|
+ // 锁释放脚本保持不变
|
|
|
+ LOCK_RELEASE_SCRIPT = new DefaultRedisScript<>();
|
|
|
+ LOCK_RELEASE_SCRIPT.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) " + "else return 0 end");
|
|
|
+ LOCK_RELEASE_SCRIPT.setResultType(Long.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化商品库存(Redis)
|
|
|
+ *
|
|
|
+ * @param productId 商品ID
|
|
|
+ * @param initStock 初始库存
|
|
|
+ */
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 高并发库存扣减(核心方法,落地Java 8特性)
|
|
|
+ *
|
|
|
+ * @param productId 商品ID
|
|
|
+ * @param deductNum 扣减数量(默认1)
|
|
|
+ * @return 扣减结果:true=成功,false=失败
|
|
|
+ */
|
|
|
+ public CompletableFuture<Boolean> deductStockAsync(Long productId, Integer deductNum) {
|
|
|
+ // Java 8 CompletableFuture 异步处理,提升高并发吞吐量
|
|
|
+ return CompletableFuture.supplyAsync(() -> {
|
|
|
+ // 1. 参数校验(Java 8 Optional 空值处理)
|
|
|
+ Integer num = Optional.ofNullable(deductNum).orElse(1);
|
|
|
+ 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);
|
|
|
+ if (Boolean.TRUE.equals(result)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 随机延迟:50ms~150ms,避免所有请求同时重试
|
|
|
+ long randomDelay = RedisConstant.LOCK_RETRY_INTERVAL + new Random().nextInt(100);
|
|
|
+ TimeUnit.MILLISECONDS.sleep(randomDelay);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+ // 4. 未获取到锁,直接返回失败
|
|
|
+ if (!isLockAcquired) {
|
|
|
+ System.err.println("商品" + productId + "获取锁失败,高并发限流中");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 5. 执行库存扣减Lua脚本(原子操作,防超卖)
|
|
|
+ // 新增日志:打印当前库存值和扣减数量
|
|
|
+ Integer currentStockStr = (Integer) redisTemplate.opsForValue().get(stockKey);
|
|
|
+ System.out.println("拿到锁成功 → 库存Key:" + stockKey + ",当前库存值:" + currentStockStr + ",扣减数量:" + num);
|
|
|
+
|
|
|
+ // 执行库存扣减Lua脚本
|
|
|
+ Long remainingStock = redisTemplate.execute(
|
|
|
+ STOCK_DEDUCT_SCRIPT,
|
|
|
+ Collections.singletonList(stockKey),
|
|
|
+ 1
|
|
|
+ );
|
|
|
+
|
|
|
+ // 新增日志:打印Lua返回结果
|
|
|
+ System.out.println("Lua脚本返回值:" + remainingStock);
|
|
|
+
|
|
|
+ // 6. 判断扣减结果
|
|
|
+ if (remainingStock != null && remainingStock >= 0) {
|
|
|
+ System.out.println("商品" + productId + "库存扣减成功,剩余库存:" + remainingStock);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ System.err.println("商品" + productId + "库存不足,扣减失败");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ // 7. 释放分布式锁(Lua脚本保证原子性,仅释放自己持有的锁)
|
|
|
+ redisTemplate.execute(LOCK_RELEASE_SCRIPT, Collections.singletonList(lockKey), lockOwner);
|
|
|
+ System.out.println("商品" + productId + "锁释放成功,持有者:" + lockOwner);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|