防止接口重复调用方法
1、使用数据库唯一索引:
为需要防重复的字段添加唯一索引,再尝试插入,如果重复会报错。
2、使用线程本地变量
利用ThreadLocal存储是否调用过的标识变量。
private static final ThreadLocal<Bolean> CALLED =new ThreadLocal<>();
if(CALLED.get() != null) {
//已调用
} else {
CALLED.set(true);
//调用逻辑
}
3、使用synchronized同步块
private static final Object LOCK = new Object();
synchronized(LOCK) {
if(isCalled) {
//已调用
} else {
isCalled = true;
//调用逻辑
}
}
4、使用AQS同步器实现锁
ReentrantLock、ReentrantReadWriteLock等。
5、使用全局变量加volatile
保证可见性,但不保证原子性。
6、使用数据库sequence生成唯一ID
避免业务重复调用。
7、redis锁。
防止重复调用的原理如下:
1、Redis锁
利用Redis的SETNX命令可以实现原子锁,保证同一时刻只有一个客户端能获取锁,其他客户端获取锁失败。通过设置过期时间实现锁的自动释放。
数据库唯一索引
为需要保证唯一的字段添加唯一索引约束。如果重复插入会报错,从而保证不重复执行业务逻辑。
2、ThreadLocal
ThreadLocal利用线程局部变量存储是否调用过的标记。由于每个线程都有自己的副本,可以保证同一线程内不重复调用。
3、同步锁
synchronized通过监视器对象(锁)实现同步,保证同一时刻只有一个线程持有锁执行同步代码块。其他线程获取锁失败被阻塞。
4、全局变量
利用volatile修饰全局变量,保证变量在不同线程之间的可见性。但不保证原子性,可能还是存在重复调用的风险。
5、序列号
使用数据库序列生成全局唯一的ID,保证业务执行时使用的ID不重复,从而避免重复调用。
总之,这些方法通过加锁、唯一约束等手段,都可以保证同一时刻只有一个线程/客户端执行需要防重复的业务逻辑,从而达到防重复调用的目的。
使用Redis的setkey和getkey方法防止重复调用有一定的问题:
1、setkey和getkey操作不是原子性的,存在竞争条件。多个线程同时调用setkey设置值,那么getkey可能拿到旧值,导致重复调用。
2、getkey只能判断值是否存在,无法实现锁功能。无法保证同一时刻只有一个线程执行业务逻辑。
3、没有设置key的过期时间,key会永久存在Redis中,不利于资源回收。
所以使用setkey和getkey防止重复调用存在以下潜在问题:
1、可能导致重复调用,无法保证原子性。
2、无法实现锁功能,无法保证同时只有一个线程执行。
3、key资源无法自动回收,可能会长期占用Redis存储空间。
相比之下,使用Redis的SETNX命令实现锁功能会更可靠一些:
1、SETNX是原子操作,可以保证同时只有一个客户端获取锁成功。
2、可以设置key的过期时间,锁资源可以自动释放。
3、锁可以实现同步控制,保证同时只有一个线程执行业务逻辑。
所以,如果要使用Redis防止重复调用,推荐使用SETNX实现分布式锁,而不是直接使用setkey和getkey。这可以更好地保证原子性和同步控制。
业务层代码:
//分布式锁key
String lockKey = "";
//防重复提交(通过Redis的setIfAbsent实现了分布式锁)
if (!redisUtil.tryGetDistributeLock(lockKey, 2)) {
//释放锁
redisUtil.releaseDistributeLock(lockKey);
// 锁获取失败,直接返回
return new BaseModel(HttpUtil.failCode, "您的操作过于频繁,请稍后再试!", null);
}
Try{
}catch(){
}finally{
//释放锁
redisUtil.releaseDistributeLock(lockKey);
}
redisutil:
//获取锁
public Boolean tryGetDistributeLock(String lockKey, int timeout) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, "1", timeout, TimeUnit.SECONDS);
}
//释放锁
public void releaseDistributeLock(String lockKey) {
redisTemplate.delete(lockKey);
}