Redis分布式锁处理高可用并发修改问题
Redis分布式锁处理高可用并发修改问题
看到网上说了三种分布式锁的做法,综合下来觉得redis比较好,也熟悉方便处理:
a.使用redis分布式锁,主要使用了redis中的setnx和getset方法,分别对应redisTemplate的setIfAbsent和getAndSet方法
b.Redis是单线程(Redis内部是单线程,并且数据存在内存中,也就是说redis内部执行命令是不会有多线程同步问题的)
c.能保证线程的安全性,而且redis强大的读写能力能提高效率,也相对容易实现
如下,代码还未用于生产环境测试过,超时时间等需要根据自己的情况调整.
1.这里使用的是线程ID来标识锁,这样也比使用其他的内容更方便做线程的排他处理
2.看到网上几乎都没有使用Redis的过期时间的,不知道是用的Redis的版本问题还是什么原因
3.我用的是Redis含有超时参数的方法,从源码可以看出,还是使用的是和set的时候设置过期时间是一样的
4.StringRedisTemplate是继承RedisTemplate的,只是value指定只序列化String类型的数据,方便使用,避免强转的问题**
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import java.time.Duration;
/**
* * 基于Redis的分布式锁(处理高可用服务的并发修改问题)
* * <p>高可用--多个服务(微服务)</p>
*
* @author yunzhi-yangbiao
* @date 2019/12/16
*/
@Slf4j
@Configuration
@AllArgsConstructor
public class RedisDistributedLock {
private static int TIMEOUT_SECONDS = 25;
private static int DEFAULT_TIMEOUT_SECONDS = 3;
private final StringRedisTemplate redisTemplate;
/**
* @param loginUserId 当前操作的用户的ID
* @param duration 超时时间(释放锁时间/单位:秒)
* @return
*/
public boolean addLock(String loginUserId, Duration duration) {
//判断用户ID是否为空
if (!StringUtils.hasText(loginUserId)) {
throw new IllegalArgumentException("当前登陆用户ID不能为空");
}
//做个容错处理,防止因为设置过长的超时时间导致程序被长时间阻塞,而影响整个程序,如果有
if (duration.getSeconds() > TIMEOUT_SECONDS) {
throw new IllegalArgumentException("您的超时时间可能过长了");
}
//获取当前线程的ID作为value,这样可以很好的排开其他的线程
String threadId = Long.toString(Thread.currentThread().getId());
//setIfAbsent--如果不存在则set并设置超时时间(防止死锁),成功set返回true,否则返回false
if (redisTemplate.opsForValue().setIfAbsent(loginUserId, threadId, duration)) {
//设置值成功,也就是加锁成功
return true;
}
//如果锁已经获取了(获取验证了锁的线程再未超时的情况下,还可以获取锁),获取redis里存储的线程ID
String lockThreadId = redisTemplate.opsForValue().get(loginUserId);
//判断获取到的线程ID是否已经失效(如果没失效,持有锁的线程是可以继续执行程序的,不能中断其运行)
if (StringUtils.hasText(lockThreadId) && lockThreadId.equals(threadId)) {
//如果从redis上获取的线程就是当前线程,那么返回true,让程序继续往下执行,否则返回false,中断程序
return true;
} else {
//没有得到锁
return false;
}
}
/**
* 这是使用默认超时的加锁方法
*
* @param loginUserId 当前操作的用户的ID
* @return
*/
public boolean defaultTimeoutLock(String loginUserId) {
return addLock(loginUserId, Duration.ofSeconds(DEFAULT_TIMEOUT_SECONDS));
}
/**
* @param loginUserId
*/
public void unlock(String loginUserId) {
try {
//获取程序当前线程
String currentThread = Long.toString(Thread.currentThread().getId());
//获取redis里存储的线程ID
String threadId = redisTemplate.opsForValue().get(loginUserId);
//判断当前需要释放锁的线程是否是获取到锁的线程(redis里存储的线程就是获取到锁的线程)
if (StringUtils.hasText(threadId) && currentThread.equals(threadId)) {
//是当前线程加的锁,所以直接释放锁
redisTemplate.opsForValue().getOperations().delete(loginUserId);
}
} catch (Exception e) {
log.info("释放锁unlock()方法异常=={}", e.getMessage());
}
}
}
希望对您有帮助.