Skip to content

一、依赖

  • 使用的是 maven 依赖管理
xml
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.27.1</version>
</dependency>

二、配置

  • 配置文件 application.yaml
yaml
# Redis 配置
spring:
  redis:
    # 连接IP地址
    host: 127.0.0.1
    # 连接端口号
    port: 6379
    # jedis 配置
    jedis:
      # 连接池
      pool:
        # 最大 8个
        max-active: 8
        # 最小 0个
        min-idle: 0
        # 阻塞的最大时间。(使用负值表示无限期阻塞)
        max-wait: -1
    # 读超时 60秒
    timeout: 60
  • 配置类 RedissonConfig.java
java
package com.dongpeng.red.base.config;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.redisson.config.TransportMode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * redisson配置
 *
 * @author calvin
 * @date 2024/03/20
 */
@Configuration
@Slf4j
public class RedissonConfig {

  @Value("${spring.redis.host}")
  String host;

  @Value("${spring.redis.password}")
  String password;

  @Value("${spring.redis.port}")
  String port;

  @Bean
  public RedissonClient redissonClient() {
    Config config = new Config();
    config.setTransportMode(TransportMode.NIO);
    // 单机下的Redis
    SingleServerConfig singleServerConfig = config.useSingleServer();
    // redis 连接地址 ( //可以用"rediss://"来启用SSL连接)
    singleServerConfig.setAddress("redis://" + host + ":" + port);
    // redis 密码
    singleServerConfig.setPassword(password);
    // 最小空闲Redis连接量 (默认) 24
    singleServerConfig.setConnectionMinimumIdleSize(24);
    // 连接池: 连接数 (默认: 64 个)
    singleServerConfig.setConnectionPoolSize(64);
    // 使用 Redis那个数据库 (默认: 第0个)
    singleServerConfig.setDatabase(0);
    // 初始化配置
    return Redisson.create(config);
  }
}

三、分布式锁

  • 库存实现类 StockServiceImpl.java,使用 Redisson 分布式锁。
java
package com.calvin.spring.boot.example.redisson.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.calvin.spring.boot.example.redisson.domain.entity.Stock;
import com.calvin.spring.boot.example.redisson.mapper.StockMapper;
import com.calvin.spring.boot.example.redisson.service.StockService;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 库存-服务实现类
 *
 * @author Calvin
 * @date 2024/3/21
 * @since v1.0.0
 */
@Service
@Slf4j
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements StockService {

  @Resource
  RedissonClient redissonClient;


  /**
   * 库存锁
   */
  private static final String STOCK_LOCK_KEY = "STOCK_LOCK";

  @Override
  public Integer reduce(Integer number) {
    return this.stockOperation(number, OperationType.REDUCE);
  }

  @Override
  public Integer increase(Integer number) {
    return this.stockOperation(number, OperationType.INCREASE);
  }


  /**
   * 库存操作
   *
   * @param number        数量
   * @param operationType 操作类型
   * @return {@link Integer}
   */
  private Integer stockOperation(Integer number, OperationType operationType) {
    String currentThreadName = Thread.currentThread().getName();
    long currentTimeMillis = System.currentTimeMillis();
    String threadName = currentThreadName + "-" + currentTimeMillis;
    // 获取锁
    RLock rlock = redissonClient.getLock(STOCK_LOCK_KEY); 

    long stockTotal = 0L;
    try { 
      // 尝试上锁,锁的等待时间 10 秒,获取锁后的执行时间为 30秒(默认)//
      boolean isLocked = rlock.tryLock(10, TimeUnit.SECONDS);  
      log.info("线程名称:{} => 获取锁是否成功: {}", threadName, isLocked);
      if (!isLocked) { 
        throw new RuntimeException("系统繁忙, 请稍后再试!");
      } 
      // 验证: 库存
      Stock stock = this.verifyStockParamReturn(number, operationType);
      // 计算: 库存数量
      stockTotal = this.computeStockNum(threadName, stock.getQuantity(), number, operationType);
      log.info("线程名称:{} => 当前库存总数: {}", threadName, stockTotal);
      // 更新: 库存数量
      updateQuantity(1L, stockTotal);

    } catch (InterruptedException e) { 
      throw new RuntimeException(e);
    } finally { 
      if (rlock != null) { 
        rlock.unlock(); 
      } 
    }
    return (int) stockTotal;
  }

  /**
   * 更新库存数量
   *
   * @param id         id
   * @param stockTotal 库存总数
   */
  private void updateQuantity(Long id, long stockTotal) {
    LambdaUpdateWrapper<Stock> lambdaUpdate = Wrappers.<Stock>lambdaUpdate()
      .eq(Stock::getId, id)
      .set(Stock::getQuantity, stockTotal);
    update(lambdaUpdate);
  }

  /**
   * 计算库存量
   *
   * @param threadName    线程名字
   * @param stockQuantity 股票数量
   * @param number        数量
   * @param operationType 操作类型
   * @return long
   */
  private long computeStockNum(String threadName, Long stockQuantity, Integer number, OperationType operationType) {
    String desc = OperationType.REDUCE == operationType ? "扣减" : "增加";
    long finalStock = OperationType.REDUCE == operationType ? stockQuantity - number : stockQuantity + number;

    log.info("线程名称:{} => {}库存: {} {} {} = {}",
      threadName,
      desc,
      stockQuantity,
      OperationType.REDUCE == operationType ? "-" : "+",
      number,
      finalStock);

    return finalStock;
  }


  /**
   * 验证: 库存参数
   *
   * @param number        数量
   * @param operationType 操作类型
   * @return {@link Stock}
   */
  private Stock verifyStockParamReturn(Integer number, OperationType operationType) {
    Stock stock = getOne(Wrappers.<Stock>lambdaQuery().eq(Stock::getId, 1L));
    if (stock == null) {
      throw new RuntimeException("【库存" + operationType.getDesc() + "】操作失败, 当前没有可用库存!");
    }

    // 库存为0
    if (stock.getQuantity() == null || stock.getQuantity() == 0) {
      throw new RuntimeException("【库存" + operationType.getDesc() + "】操作失败,库存已售罄!");
    }

    if (number == null) {
      throw new RuntimeException("【库存" + operationType.getDesc() + "】操作失败,操作数不能为负数!");
    }

    if (OperationType.REDUCE == operationType && number > stock.getQuantity()) {
      throw new RuntimeException("【库存" + operationType.getDesc() + "】操作失败, 库存不足!");
    }

    return stock;
  }


  /**
   * 操作类型
   *
   * @author calvin
   * @date 2024/03/31
   */
  @Getter
  @AllArgsConstructor
  enum OperationType {

    /**
     * 增加
     */
    INCREASE("增加"),

    /**
     * 减少
     */
    REDUCE("扣减"),
    ;

    /**
     * 描述
     */
    private String desc;
  }


}

四、视频演示