• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Redis实现分布式锁(地址)

武飞扬头像
昨日晨曦丶
帮助1

前言

单机版应用可以用锁(lock、synchronized)解决共享资源操作问题,但在分布式系统下 比如多个进程同时对一个资源进行修改,我们无法控制 ,中间必须加一层中间件来控制共享资源的操作,目前比较流行的分布式锁基于zookeeper、redis、数据库等来实现,本文主要是基于redis实现分布式锁。

实现基本原理

说下redis基本命令

SETNX key value

SETNX是key如果存在则返回0,不存在则返回1,如下图所示
学新通

那么说白了就是:== redis作为一个中间件来控制分布式系统中多个服务操作 共享资源 ==
单机版可以换成lock、synchronized方式,就如下图
学新通

正文

案例1

源码

public class LockCase1 extends RedisLock {

    public LockCase1(Jedis jedis, String name) {
        super(jedis, name);
    }

    @Override
    public void lock() {
        while(true){
            String result = jedis.set(lockKey, "value", NOT_EXIST);
            if(OK.equals(result)){
                System.out.println(Thread.currentThread().getId() "加锁成功!");
                break;
            }
        }
    }

    @Override
    public void unlock() {
        jedis.del(lockKey);
    }
}
学新通

代码解释

这个案例很简单,直接set一个key,如果不存在则成功。

存在问题

如果获得锁的线程挂了,那么这个锁就永远不会被释放,其他线程获取不到锁。

案例2

源码

public class LockCase2 extends RedisLock {

    public LockCase2(Jedis jedis, String name) {
        super(jedis, name);
    }

    @Override
    public void lock() {
        while(true){
            /**
             * 这里设置key和设置过期时间需要保持原子性,
             * 设置存活时间30秒,解决LockCase1存在的问题.
             * @see com.learnRedis.lock.case1.LockCase1
             */
            String result = jedis.set(lockKey, "value", NOT_EXIST,SECONDS,30);
            if(OK.equals(result)){
                System.out.println(Thread.currentThread().getId() "加锁成功!");
                break;
            }
        }
    }

    @Override
    public void unlock() {
        jedis.del(lockKey);
    }
}
学新通

代码解释

这个案例在案例1的基础上增加一个过期时间,解决案例1的问题。

存在问题

  • 1.如何保证锁不会被误删除?
  • 2.过期时间如何保证大于执行时间?

案例3

源码

public class LockCase3 extends RedisLock {

    public LockCase3(Jedis jedis, String name) {
        super(jedis, name);

    }

    @Override
    public void lock() {
        while(true){
            /**
             * 设置value为当前线程特有的值
             */
            String result = jedis.set(lockKey, lockValue, NOT_EXIST,SECONDS,30);
            if(OK.equals(result)){
                System.out.println(Thread.currentThread().getId() "加锁成功!");
                break;
            }
        }
    }

    @Override
    public void unlock() {
        /**
         * 此处不具备原子性,可以分为三个步骤
         * 1.获取锁对应的value值
         * 2.检查是否与requestId相等
         * 3.如果相等则删除锁(解锁)
         */
        String lockValue = jedis.get(lockKey);
        if (lockValue.equals(lockValue)){
            jedis.del(lockKey);
        }

    }
学新通

代码解释

在案例2的基础上,添加了唯一id value值,释放锁时判断,key对应的value是否是锁他的那个线程。

存在问题

解锁过程(查询、比较、删除)不具备原子性

案例4

源码

public class LockCase4 extends RedisLock {

    public LockCase4(Jedis jedis, String lockKey) {
        super(jedis, lockKey);

    }

    @Override
    public void lock() {
        while (true) {
            String result = jedis.set(lockKey, lockValue, NOT_EXIST, SECONDS, 30);
            if (OK.equals(result)) {
                System.out.println(Thread.currentThread().getId()   "加锁成功!");
                break;
            }
        }
    }

    @Override
    public void unlock() {
        // 使用lua脚本进行原子删除操作
        String checkAndDelScript = "if redis.call('get', KEYS[1]) == ARGV[1] then "  
                                    "return redis.call('del', KEYS[1]) "  
                                    "else "  
                                    "return 0 "  
                                    "end";
        jedis.eval(checkAndDelScript, 1, lockKey, lockValue);
    }
学新通

代码解释

在案例3的基础上,解锁时添加lua脚本(原子删除操作)。

存在问题

这个案例在很多项目中其实已经是最终版本了,设置一个执行的超时时间;但为了保证 过期时间大于执行时间,增加案例5

案例5

源码

    /**
     * 刷新key的过期时间
     */
    private class ExpirationRenewal implements Runnable{
        @Override
        public void run() {
            while (isOpenExpirationRenewal){
                System.out.println("执行延迟失效时间中...");

                String checkAndExpireScript = "if redis.call('get', KEYS[1]) == ARGV[1] then "  
                        "return redis.call('expire',KEYS[1],ARGV[2]) "  
                        "else "  
                        "return 0 end";
                jedis.eval(checkAndExpireScript, 1, lockKey, lockValue, "30");

                //休眠10秒
                sleepBySencond(10);
            }
        }
    }


学新通

代码解释

在案例4的基础上,增加上述方法,刷新key的过期时间,完整源码在下面链接。

代码地址

https://gitee.com/yubin0707/daily 代码地址.
第一篇博客,大佬们看完给点个赞😄

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhiaccfi
系列文章
更多 icon
同类精品
更多 icon
继续加载