问题说明,用户注册时加了防重复提交注解@RepeatSubmit,发现还是会有重复数据进来,
最开始的思路是使用redis,在注册的时候先去拿key获取有没有值,没有则正常注册数据,并将key-value值存入redis,并设置过期时间,但是使用jmeter进行并发测试,发现该处理方法并不能有效的解决,还是会有重复数据,通过同事的建议改使用redisson得分布式锁,经测试,可有效解决现有问题。
第一步,引入redisson 的依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.1</version>
</dependency>
第二步 配置yml
首先将redisson配置到redisson.yml中
# singleServerConfig.yml --- 存放于application.yml同级目录
singleServerConfig:
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 100000
# 连接超时,单位:毫秒
connectTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 命令失败重试次数
retryAttempts: 3
# 命令重试发送时间间隔,单位:毫秒
retryInterval: 1500
# 密码
password:
# 单个连接最大订阅数量
subscriptionsPerConnection: 5
# 客户端名称
clientName: null
# 节点地址
address: "redis://127.0.0.1:6379"
# 发布和订阅连接的最小空闲连接数
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# redis数据库编号
database: 0
# DNS监测时间间隔,单位:毫秒
dnsMonitoringInterval: 5000
# 线程池数量
threads: 0
# Netty线程池数量
nettyThreads: 0
# 编码
codec:
class: "org.redisson.codec.JsonJacksonCodec"
# 传输模式
transportMode: "NIO"
# 配置看门狗的默认超时时间为30s,这里改为10s
lockWatchdogTimeout: 10000
第三步:配置在application.yml中
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 本地密码
password:
#服务器密码
#password: zMjPyC0cBwTmdW8b
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
redisson:
file: classpath:redisson.yml
第四步: 本地封装Redisson分布式锁
1、定义IDistributedLock分布式锁接口
package com.yuepu.common.core.redis.redisson;
import java.util.concurrent.TimeUnit;
public interface IDistributedLock {
/**
* 获取锁,默认30秒失效,失败一直等待直到获取锁
*
* @param key 锁的key
* @return 锁对象
*/
ILock lock(String key);
/**
* 获取锁,失败一直等待直到获取锁
*
* @param key 锁的key
* @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
* @param unit {@code lockTime} 参数的时间单位
* @param fair 是否公平锁
* @return 锁对象
*/
ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
/**
* 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
*
* @param key 锁的key
* @param tryTime 获取锁的最大尝试时间
* @return
* @throws Exception
*/
ILock tryLock(String key, long tryTime) throws Exception;
/**
* 尝试获取锁,获取不到超时异常
*
* @param key 锁的key
* @param tryTime 获取锁的最大尝试时间
* @param lockTime 加锁的时间
* @param unit {@code tryTime @code lockTime} 参数的时间单位
* @param fair 是否公平锁
* @return
* @throws Exception
*/
ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
/**
* 解锁
*
* @param lock
* @throws Exception
*/
void unLock(Object lock);
/**
* 释放锁
*
* @param lock
* @throws Exception
*/
default void unLock(ILock lock) {
if (lock != null) {
unLock(lock.getLock());
}
}
}
2、IDistributedLock实现类
package com.yuepu.common.core.redis.redisson;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedissonDistributedLock implements IDistributedLock {
@Resource
private RedissonClient redissonClient;
@Override
public ILock lock(String key) {
return this.lock(key, 0L, TimeUnit.SECONDS, false);
}
@Override
public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
RLock lock = getLock(key, fair);
// 获取锁,失败一直等待,直到获取锁,不支持自动续期
if (lockTime > 0L) {
lock.lock(lockTime, unit);
} else {
// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
lock.lock();
}
return new ILock(lock, this);
}
@Override
public ILock tryLock(String key, long tryTime) throws Exception {
return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
}
@Override
public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
throws Exception {
RLock lock = getLock(key, fair);
boolean lockAcquired;
// 尝试获取锁,获取不到超时异常,不支持自动续期
if (lockTime > 0L) {
lockAcquired = lock.tryLock(tryTime, lockTime, unit);
} else {
// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
lockAcquired = lock.tryLock(tryTime, unit);
}
if (lockAcquired) {
return new ILock(lock, this);
}
return null;
}
/**
* 获取锁
*
* @param key
* @param fair
* @return
*/
private RLock getLock(String key, boolean fair) {
RLock lock;
String lockKey =key;
if (fair) {
// 获取公平锁
lock = redissonClient.getFairLock(lockKey);
} else {
// 获取普通锁
lock = redissonClient.getLock(lockKey);
}
return lock;
}
@Override
public void unLock(Object lock) {
if (!(lock instanceof RLock)) {
throw new IllegalArgumentException("Invalid lock object");
}
RLock rLock = (RLock) lock;
if (rLock.isLocked()) {
try {
rLock.unlock();
} catch (IllegalMonitorStateException e) {
log.error("释放分布式锁异常", e);
}
}
}
}
3、定义ILock锁对象
package com.yuepu.common.core.redis.redisson;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* <p>
* RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
* </p>
* @since 2023-06-08 16:57
*/
@AllArgsConstructor
public class ILock implements AutoCloseable {
/**
* 持有的锁对象
*/
@Getter
private Object lock;
/**
* 分布式锁接口
*/
@Getter
private IDistributedLock distributedLock;
@Override
public void close() throws Exception {
if(Objects.nonNull(lock)){
distributedLock.unLock(lock);
}
}
}
具体代码中的实现类
// 实现类
@Service
public class XXServiceImpl
implements XXService {
@Resource
private IDistributedLock distributedLock;
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveSupplierInfo(XXoDTO dto) {
// 手动释放锁
String sku = dto.getSku();
ILock lock = null;
try {
lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
// 业务代码
} catch (Exception e) {
log.error("保存异常", e);
throw new BaseException(e.getMessage());
} finally {
if (Objects.nonNull(lock)) {
distributedLock.unLock(lock);
}
}
return Boolean.TRUE;
}
}
感谢这位原博主的分享
Spring Boot 集成 Redisson分布式锁(注解版)_springboot 集成redisson分布式锁-CSDN博客
备注:这里关于原博主文章中说的,redisson配置在application.yml中redis下个层级得写法,本地按这种写法启动一直报错(后来找到原因是因为我把本地redis没有设置密码,设置过密码之后就正常了),改用新增一个redisson.yml,然后application.yml中redis下引用,可以正常启动,这里还有一个小插曲,虽然这是正确配置,但是同事反馈说同样的配置放到服务器上会报错,然后他将redisson的引入改为和redis平级,启动却正常了。这里具体的原因以后再去研究啦就
redisson: file: classpath:redisson.yml