我在使用redis分布式锁的时候,遇到过的一些问题,如下:
加锁
解锁实现方式一:
/**
* @param key 锁的key,全局唯一的key
* @param value 锁的value,开始value和key值一样
* @return 加锁成功或失败
**/
public boolean tryLock(String key, String value) {
// 第一步设置key,如果存在就设置失败,加锁失败
Long set = jedisCluster.setnx(key, value);
if (1 == set) {
// 第二步,设置key的过期时间
jedisCluster.expire(key, 10);
return true;
}
return false;
}
1、这种写法的问题就是不是原子性的,如果服务A在执行到第一步过后,服务挂了,那么就会造成锁没有设置过期时间,就一直存在,其他服务就拿不到锁了,会造成“死锁”;
2、同时也还会有另外一个问题,就是value和key一样,这种情况会导致其他服务解锁非自己服务的锁。应该要遵循“解铃还须系铃人”,谁加的锁应该就由谁来解。解决办法就是value设为当前服务请求的全局唯一requestId。
解锁实现方式二:
/**
* @param key 锁的key,全局唯一的key
* @param value 锁的value,全局唯一的requestId
* @return 加锁成功或失败
*/
public boolean lock(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.SECONDS);
}
这种实现方式解决上面方式一的非原子性问题;但是也还是会有一种情况下会有问题:
如果服务逻辑处理时间比较长,还没有处理完,这个时候key的过期时间已经到了,其他服务就能拿到锁了,可能会造成数据的一致性问题。
解决方案,可以通过过期时间续约的方式,监听过期时间,如果逻辑还没有处理完,就给过期时间续约。
解锁
解锁实现方式一
/**
* @param key 锁key
* @return 解锁是否成功
*/
public boolean unLock(String key) {
Long del = jedisCluster.del(key);
if (1 == del) {
return true;
}
return false;
}
问题:
这种解锁方式的问题,在于在直接了,会导致误删不是自己加的锁;改进方式如下:
解锁方式二
/**
* @param key 锁key
* @param value 锁的value,全局唯一的requestId
* @return 解锁是否成功
*/
public boolean unLock(String key, String value) {
// 第一步比较value
if (value.equals(jedisCluster.get(key))) {
// 第二步删除key
Long del = jedisCluster.del(key);
if (1 == del) {
return true;
}
}
return false;
}
这种方式解决上面的问题,但是不是原子性的;在第一步服务挂了,过后就会解锁失败了。
改进如下(可以利用lue脚本,redis执行时原子性的):
正确的解锁方式
/**
* 正确解锁
*
* @param key 锁key
* @param requestId 锁value
* @return 解锁是否成功
*/
public boolean unLock(String key, String requestId) {
String lue = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedisCluster.eval(lue, Collections.singletonList(key), Collections.singletonList(requestId));
if (1L == result) {
return true;
}
return false;
}
好了以上就是遇到的一些问题,如果有不对的理解有误差希望可以指出,互相学习!