Redis实现分布式锁

2021年4月7日 7点热度 0条评论 来源: 小猿修行记

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,进行解锁操作 */

zookeeper实现分布式锁
注:此内容仅供参考,如有问题欢迎指出,谢谢!

    原文作者:小猿修行记
    原文地址: https://blog.csdn.net/weixin_45394138/article/details/112394321
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。