Redis实现分布式锁

Redis实现分布式锁

1 问题导入

使用setnx来实现分布式锁?

2 问题分析

问题1:setnx命令在参数中不能设置过期时间,要执行expire才能进行过期时间设置,不是原子性的操作,可能会在执行setnx后服务器宕机,没有设置expire,从而造成死锁现象

解决:在redis中可以使用set key value ex expireTime nx 命令来进行替换,在代码中可以使用redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS)来实现

问题2:当A线程获取锁正在执行时,锁过期了,这时,线程B获取到了锁进入执行,当A线程执行完操作删除锁,实际是删除的B线程持有的锁(出现误删的情况)

解决:可以在设置锁的时候以线程id进行标识,删除时先进行判断,然后再进行删除

问题3:先进行key的判断,再删除key的操作不是原子性的

解决:设置守护线程,在key过期之前进行续时,线程业务执行完后才能进行删除,即使当前线程所在服务器宕机该key也会自动过期释放,不会出现死锁

3 具体实现

  • 1.使用set key value ex expireTime nx命令,此命令是原子性操作,在key不存在的情况下,进行set操作,同时给该key设置一个过期时间
  • 2.应用中可以使用setIfAbsent(key, value, expireTime, TimeUnit.SECONDS)来实现
  • 3.添加一个守护线程来进行key的监控以延长过期时间(续命),直到业务执行完毕

4 代码测试

4.1 加锁

/**
 * 分布式锁(加锁)
 * (setIfAbsent+后台守护线程为其续时)
 * @param key 键
 * @param value 值
 * @param expire 过期时间
 * @return true 成功,false 失败
 */
public boolean setIfAbsent(String key,Object value,long expire){
     
    Boolean flag =false;
    try{
     
        if(expire>0){
     
            flag = redisTemplate.opsForValue().setIfAbsent(key, value, expire, TimeUnit.SECONDS);
        }
    }catch (Exception e){
     
        e.printStackTrace();
    }
    return flag;
}

4.2 续时

/**
 * 分布式锁(续命)
 * 通过守护线程进行key的续时
 * @param key
 * @param expire
 */
public void setExpire(String key ,long expire){
     
    log.info("===>进入setExpire,进行续时操作");
    try{
     
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }catch (Exception e){
     
        e.printStackTrace();
    }
}

4.3 解锁

/**
 * 分布式锁(释放锁)
 * 线程执行完毕,先进行value的检验,检验成功,则删除key
 * @param key
 * @param checkValue 当前线程的id作为value值进行校验
 */
public void delKeyByValue(String key,Object checkValue){
     
    log.info("===>delKeyByValue,进行解锁操作");
    try{
     
        Object value = redisTemplate.opsForValue().get(key);
        if(checkValue.equals(value)){
     
            redisTemplate.delete(key);
        }
    }catch (Exception e){
     
        e.printStackTrace();
    }
}

4.4 测试

  • 启动三个线程(两个作为工作线程,一个作为守护线程)
  • 工作线程获取锁,获取锁后执行业务操作,然后释放锁
  • 守护线程只负责对当前的key进行续时操作
final static String REDIS_LOCK_KEY = "REDIS_LOCK_KEY";//key
final static Long EXPIRE_TIME = 60L;//过期时间
//测试方法
@RequestMapping("/test")
public String test() {
     
    //守护者线程
    Thread p = new Thread(() -> {
     
        while (true) {
     
            try {
     
                //每50s进行一次续时
                TimeUnit.SECONDS.sleep(50);
                redisLockUtil.setExpire(REDIS_LOCK_KEY, EXPIRE_TIME);
            } catch (Exception e) {
     
                e.printStackTrace();
            }
        }
    }, "protector");
    //设置为守护线程
    p.setDaemon(true);

    //工作线程1
    Thread t1 = new Thread(() -> {
     
        try {
     
            //加锁,如果未获取锁,则一直进行自旋来获取锁
            boolean setIfAbsentFlag;
            do {
     
                setIfAbsentFlag = redisLockUtil.setIfAbsent(REDIS_LOCK_KEY, Thread.currentThread().getName(), EXPIRE_TIME);
            } while (!setIfAbsentFlag);

            if (setIfAbsentFlag) {
     
                log.info("======>加锁成功:key=REDIS_LOCK_KEY,value=" + Thread.currentThread().getName());
            }

            //模拟业务执行的过程(执行100s)
            TimeUnit.SECONDS.sleep(100);

        } catch (Exception e) {
     
            e.printStackTrace();
        } finally {
     
            //解锁
            redisLockUtil.delKeyByValue(REDIS_LOCK_KEY, Thread.currentThread().getName());
        }
    }, "thread001");
    //工作线程2
    Thread t2 = new Thread(() -> {
     
        try {
     
            //加锁,如果未获取锁,则一直进行自旋来获取锁
            boolean setIfAbsentFlag;
            do {
     
                setIfAbsentFlag = redisLockUtil.setIfAbsent(REDIS_LOCK_KEY, Thread.currentThread().getName(), EXPIRE_TIME);
            } while (!setIfAbsentFlag);

            if (setIfAbsentFlag) {
     
                log.info("======>加锁成功:key=REDIS_LOCK_KEY,value=" + Thread.currentThread().getName());
            }

            //模拟业务执行的过程(执行100s)
            TimeUnit.SECONDS.sleep(100);

        } catch (Exception e) {
     
            e.printStackTrace();
        } finally {
     
            //解锁
            redisLockUtil.delKeyByValue(REDIS_LOCK_KEY, Thread.currentThread().getName());
        }
    }, "thread002");

    t1.start();
    t2.start();
    p.start();

    return "Test success!";
}
/*测试结果
======>加锁成功:key=REDIS_LOCK_KEY,value=thread002
===>进入setExpire,进行续时操作
===>进入setExpire,进行续时操作
===>delKeyByValue,进行解锁操作
======>加锁成功:key=REDIS_LOCK_KEY,value=thread001
===>进入setExpire,进行续时操作
===>进入setExpire,进行续时操作
===>delKeyByValue,进行解锁操作
*/

注:此内容仅供参考,如有问题欢迎指出,谢谢!

你可能感兴趣的:(redis,redis,分布式锁)