1、Redis分布式锁流程图(二个要点:①超时解锁 ②获得锁的线程唯一标识,用以谁的锁谁来解锁)

  

2、Redis分布式锁算法:

  ①加锁

    a:锁的唯一标识(设置随机值作为锁的持有人,只有锁的持有人才可以解锁)

    b:锁的超时时间

      加锁指令:jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)

      lockKey:key值

      requestID:value值,即锁的唯一标识,只有该requestId才可以解锁对应锁

      set_if_not_exist:当key不存在时进行set操作,如果key存在则不做任何操作

      set_with_expire_time:过期时间key

      expireTime:具体过期时间值(一般是通过压测得到一个经验值)

      总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

 

心细的童鞋就会发现了,我们的加锁代码满足我们可靠性里描述的三个条件。首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。由于我们只考虑Redis单机部署的场景,所以容错性我们暂不考虑

 

   ②解锁

    a:检查是否持有锁

    b:持有锁条件下删除锁

    采用Lua脚本进行删锁操作

    

 

3、redis代码:

3.1redis加锁代码

  redis是线程安全,redis server中指令操作是原子的

   
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
View Code

  上述代码执行有两个结果:①锁不存在,进行加锁操作   ②存在锁,不做任何操作

  代码中满足三个条件:①requestID唯一锁持有人标识 ,满足谁的锁谁去解锁

            ②expireTime 设置超时时间,如果超时自动解锁

            ③nx 保证key存在则不进行任何操作

 

3.2redis解锁代码  

public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
View Code

  我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。

  Lua脚本功能:获取key对应的value,然后和requestID对比是否一样,如果一样则解锁,返回true,如果不相等则返回false

 

 

4、主节点在同步锁到子节点前挂掉处理方案:redLock

                          

 

                   

另外,如果客户端A在master1、2、3获取到锁后,master1宕机,此时客户端B又过来请求锁,在master1、4、5获得锁,这种情况下A、B均获得锁了,解决方案是:master1宕机后不会立马重启,而是延迟重启,在延迟重启的时间段内,保证客户端A便执行完代码

 

posted on 2019-06-07 16:39  colorfulworld  阅读(415)  评论(0编辑  收藏  举报