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锁的容错性。

红锁的实现:

redisson 设置不可重入锁_分布式锁

过程:

1.对多个redis节点进行RLock,并将其构造成RedLock

2.如果循环加锁失败,允许重试次数为集群的最大个数,最终判断加锁失败的个数,比如3个允许失败1个,5个允许失败2个,保证大多数能加锁成功。

3.加锁过程设置一个过期时间,比如4ms,如果加锁时间(总时间)超过这个值则返回加锁失败。