redis分布式锁的四大特性:
1.互斥性,同一时间只有一台服务器能获取到锁
2.不会发生死锁,当某台服务器获取到锁后因为故障导致不能释放锁,其他服务器也能正常获取锁
3.容错性,大多数redis节点存活下,分布式锁任然有效,即不会因为一个节点挂而导致锁失效
4.解铃还需系铃人,加锁和解锁必须由同一个服务器来执行(防止因业务执行时间过长导致缓存时间已过)
说到redis分布式锁,我们都会说用setnx命令,再设置一个超时时间expireTime,但真的是这样的吗?
其实不然,如果使用两个命令来执行redis操作,那如果执行完setnx后发生异常,导致过期时间没有设置上,则会导致redis发生死锁,不满足特性2.(两个操作不是原子性)
那怎么做呢...
在较高版本的redis中,提供了set(key,value,set_if_not_exist,set_with_expire_time,expireTime),该操作保证了设置值和设置过期时间的原子性。
在上述set命令中,value值建议使用uuid作为唯一值,这样在解锁的时候可以用来唯一标示服务器
解锁时需要比较存的值,值一致才删除这个key
那怎么才能保证上述操作的原子性呢,答案就是lua脚本
public boolean release(String identify) {
if(identify == null){
return false;
}
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = new Object();
try {
result = jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(identify));
if (RELEASE_SUCCESS.equals(result)) {
log.info("release lock success, requestToken:{}", identify);
return true;
}}catch (Exception e){
log.error("release lock due to error",e);
}finally {
if(jedis != null){
jedis.close();
}
}
log.info("release lock failed, requestToken:{}, result:{}", identify, result);
return false;
}
使用lua脚本解决了多个操作的原子性问题,那是不是分布式锁就没问题了呢,理想情况下是没问题了!
但是:
如果redis是多节点的,redis是单进程单线程的写入和读取,主节点负责写请求,从节点负责读请求,它保证数据的最终一致性。
如果分布式锁在主节点上,主节点恰好挂了,那么会从从节点上选一个来作为主节点,那此时是不是分布式锁就失效了呢,并不是,redis引入了RedLock(红锁)来保证redis锁的容错性。
红锁的实现:
过程:
1.对多个redis节点进行RLock,并将其构造成RedLock
2.如果循环加锁失败,允许重试次数为集群的最大个数,最终判断加锁失败的个数,比如3个允许失败1个,5个允许失败2个,保证大多数能加锁成功。
3.加锁过程设置一个过期时间,比如4ms,如果加锁时间(总时间)超过这个值则返回加锁失败。