redis分布式锁解决方案


一、什么是分布式锁?


线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)

进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

二、分布式锁的特性?

  • 互斥性: 任意时刻,只有一个客户端能持有锁。
  • 锁超时释放:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
  • 可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁。
  • 高性能和高可用:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
  • 安全性:锁只能被持有的客户端删除,不能被其他客户端删除

三、基于redis实现分布式锁

1.Redis分布式锁方案一:SETNX + EXPIRE

即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记释放

// 获取锁 基于 setnx 和 expire 此方法不会保证原子性 可以使用lua脚本(redis 又演变出 set加过期时间的方式)
 public boolean getLockNx(Jedis jedis, String lockeKey, String requestId, Long expireTime) {
        Long setnx = jedis.setnx(lockeKey, requestId);
        if (Objects.equals(setnx, 1)) {
            jedis.expire(lockeKey, new Long(expireTime).intValue());
            return true;
        }
        return false;
    }

2.Redis分布式锁方案二:SET的扩展命令(SET EX PX NX)

  // 获取锁, 设置超时时间,单位为毫秒 此方法目前可以满足大多数需求
    public boolean getLock(Jedis jedis, String lockKey, String requestId, Long 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;
    }

存在问题:

  1. 问题一:「锁过期释放了,业务还没执行完」。假设线程a获取锁成功,一直在执行临界区的代码。但是100s过去后,它还没执行完。但是,这时候锁已经过期了,此时线程b又请求过来。显然线程b就可以获得锁成功,也开始执行临界区的代码。那么问题就来了,临界区的业务代码都不是严格串行执行的啦。
  2. 问题二:「锁被别的线程误删」。假设线程a执行完后,去释放锁。但是它不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完呢

3.Redis分布式锁方案三:分布式锁的释放

//释放锁 存在问题是会误删他人的锁
    public boolean releaseLock(Jedis jedis1, String key, String requestId) {
        try {
            String result = jedis1.get(key);
            if (Objects.equals(result, requestId)) {
                // lockkey锁失效,下一步删除的就是别人的锁
                jedis1.del(key);
                return true;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            jedis1.close();
        }
        return false;
    }
   /**
     * 释放分布式锁 基于lua脚本释放 保证了原子性 和释放锁是符合自己的 解决并发问题
     *
     * @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 (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

4.Redis分布式锁方案四:Redisson框架

方案3还是可能存在「锁过期释放,业务没执行完」的问题。有些h会认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放

开源框架Redisson解决了这个问题。我们一起来看下Redisson底层原理图吧:
redis分布式锁解决方案_第1张图片
只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了「锁过期释放,业务没执行完」问题

 //redlock 实现分布式锁 最终方案
    public boolean getRedLock(RedissonClient redisClient){
        RLock lock = redisClient.getLock("REDLOCK_KEY");
        try {
            boolean flag = lock.tryLock();
            if (flag) {
                System.out.println("加锁成功");
            }
        } catch (Exception ex){
        } finally {
            lock.unlock();
        }
        return false;
    }

你可能感兴趣的:(Redis,java,redis)