关于 Redis分布式锁 详细过程
简而言之Redis分布式锁的秒杀原理
首先必须清楚一个事实: 秒杀期间 1000个请求 可能只有10个获取锁并且秒杀成功的请求 其余990个将抛出异常(因为拿不到分布式锁)这就是秒杀分布式锁导致的结果(无数个请求尝试获取一个锁)
必须清楚有关redis操作的方法:
StringRedisTemplate.opsForValue().setIfAbsent(key, value) -> 原子性设置key和value(如果不存在此key:true 如果存在:false 不修改)
StringRedisTemplate.opsForValue().get(key)->获取value
StringRedisTemplate.opsForValue().getAndSet(key, value)->原子性先获取value之后设置自己的value值
开始代码思路
key:商品ID
value:当前时间+超时时间
上锁代码思路开始:设置key和value
1.开始:无数个请求拿分布式锁
2.判断(预期结果:存在此key/不存在此key):setIfAbsent(key,value) 原子性设置 并检查是否存在此key(返回true/false)不存在则设置自己的key和value,存在就直接返回false(只有一个线程可以设置到key和value)
3.(不存在此key)返回 true(意味获得分布式锁成功)
4.(存在此key)获取:get(key) 多个线程同时拿到当前的currentvalue
5.判断(预期结果:锁过期&¤tvalue不为null/currentvalue为null/锁没过期):检查是否超时了并且当前的currentvalue不为null,获取当前时间与currentvalue比较
6.获取并设置(锁过期了):多个线程 开始 原子性地 再次获取之前的currentvalue2然后 设置自己的value(同时只有一个线程可以设置)(一直到)
6.1 (可能多个线程) 判断(锁过期了)(预期结果:currentvalue2与currentvalue一致&¤tvalue不为null):这里只能让第一个修改了value的线程并判断true而成功获得锁,后来的线程都是拿到前一个线程所改变的value。
6.2 (currentvalue2与currentvalue一致&¤tvalue不为null)获得分布式锁成功
7.(锁没过期/currentvalue为null)此线程直接抛出异常
代码:
/**
* 加锁
* @param key
* @param value 当前时间+超时时间
* @return
*/
private StringRedisTemplate redisTemplate;
public boolean lock(String key, String value) {
//多个线程抢锁(设置key和value)
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
//获得分布式锁成功
return true;
}
//currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
log.info("锁过期了!!!");
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
解锁代码思路开始:清除key和value
开始拿value值
判断是否跟当前的value值相等
如果相等则清除key和value
代码:
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
//拿到redis的value
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
//清除redis的key和value
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
代码猜想
可能超时条件成立的时候存在死锁:(其实并不会存在死锁) 代码会连续收到百万请求,如果加锁的时候判断到前
一个锁超时此时可能会存在多个线程进来依次执行 redisTemplate.opsForValue().getAndSet(key, value);
这个语句。 此时会筛选掉除第一个之后的线程进入获取锁。但是value可能是判断锁过期后,最后一个线程设置的value值,在线程解锁的时候会出现 当前currentvalue != value导致无法解锁,这时候需要等到一种情况才能脱离暂时性的死锁,那也就是 判断这次的锁过期的时候,进来修改value值的线程只有一个的时候,才能顺利地获得锁并且解锁,但是前面无法解锁的也可能已经把逻辑执行完成了。