一、依赖
- 使用的是 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;
}
}