再也不用担心面试官让我用Redis实现分布式锁啦(一、Jedis实现分布式锁)

目录

 

一、分布式锁实现方式

二、分布式锁得可靠性

三、jedis实现Redis分布式锁

获取分布式锁

错误示例1

释放锁

错误实例1

错误实例2


一、分布式锁实现方式

二、分布式锁得可靠性

  • 互斥性,同一时间段只有一个客户端能持有锁
  • 避免死锁,即使某一客户端在持有锁得时间段内崩溃未能正常得释放锁,也能让别的客户端成功加锁
  • 容错性,当然前提是Redis集群得前提下,大部分Redis节点是正常运行得情况下,可以实现加锁和释放锁
  • 解铃还须系铃人,某一客户端加得锁必须由同一客户端解锁,A不能给B得锁给解了 

三、jedis实现Redis分布式锁

  • 获取分布式锁

    /** 获取分布式锁 */
    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;
    }
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 这一行代码获取到了redis分布式锁。
  • 第一个key,锁得唯一key,这很好理解,每个锁都是唯一得
  • 第二个value,使用RequestId,这里可以使用 UUID.randomUUID().toString() 来获取,保证请求获取锁得唯一,这有人可能会说第一个key已经保证了唯一性,为什么还需要这个呢?这就是之前说过的解铃还须系铃人,虽然锁得key是唯一得但是我们必须保证上锁和解锁得是同一个客户端得请求,当我们使用即可保证。
  • 第三个NX,意思就是KEY如果存在及不创建只有在不存在得时候才会去创建,这就保证了第一点,同一时间段有且只有一个客户端可以获取到锁。
  • 第四个PX,意思给当前key设置一个过期时间,具体得失效时间由第五个参数决定
  • 第五个eTime,即失效时间,具体时间根据业务具体设置,主要保证程序出现问题不能正常释放锁,在有失效时间得保证上也可以释放锁,不至于造成死锁问题。

 

  • 错误示例1

public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {

    Long result = jedis.setnx(lockKey, requestId);
    if (result == 1) {
        // 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
        jedis.expire(lockKey, expireTime);
    }

}
  • 上面这段逻辑看似没什么问题,先在如果没有key得情况下,设置key。然后将key设置失效时间,但是有个问题,由于是两条redis命令,不具有原子性,在某种情况下,在设置完key得时候,程序崩溃,失效时间没有设置成功,那么就造成了死锁出现。

 

  • 释放锁

    /** 释放分布式锁 */
    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;

    }

其实很简单,就是一条简单得Lua脚本,大致意思就是获取当前key得value判断是否与requestId相等,如果相同,则删除key。至于为什么这么写,其实很简单,就是为了保证操作得原子性。

  • 错误实例1

public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
    jedis.del(lockKey);
}

这条命令很好理解,即删除当前key,释放锁。但是这样会有很大问题,即不能保证A线程获取的锁是他自身释放,很有可能造成A上锁,B释放锁的尴尬局面。

  • 错误实例2

public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
        
    // 判断加锁与解锁是不是同一个客户端
    if (requestId.equals(jedis.get(lockKey))) {
        // 若在此时,这把锁突然不是这个客户端的,则会误解锁
        jedis.del(lockKey);
    }

}

这段逻辑看似没有瑕疵,判断当前锁是否是客户端上锁,如果是则删除。但是还是有个问题,有可能释放掉别的客户端上的锁。比如,A客户端上锁,判断也是正确的,但是在删除之前,锁失效了,B尝试获取到了锁,则执行删除命令的时候其实真正释放的是B的锁,造成问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值