Redis分布式非公平锁的使用

目录
  • 前言
  • redis分布式锁第一版
  • redis分布式锁第二版
  • redis分布式锁第三版
  • redis分布式锁最终版

前言

看了很多博客,和资料,这里只针对redis做分布式锁做一下深入探讨,希望对你们有帮助。网上提供了很多分布式锁的操作,这里逐一举例然后评论优缺点及改进方案,希望这样子能让当家更好的理解redis分布式锁。

redis分布式锁第一版

大家应该都知道redis做分布式锁无非就是incr命令或者是setnx命令,这里我们采用setnx命令。
操作:setnx key 如果操作成功则代表拿到锁,如果没有操作成功则代表没有拿到锁。

缺点:如果这个人拿到锁后宕机了怎么办,那么这个锁就再也不能释放了。

改进:给这个锁增加一个过期时间,这样如果有效期过了,那么这个锁就会自动释放了。

redis分布式锁第二版

通过上面所说我们应该对redis分布式进行改进。
操作: 使用setnx 命令,之后,在expireat key 30000 这条命令设置key的有效期为30秒。
这里我们可能会发现,如果要是刚setnx结束之后,要是宕机了。怎么办?那么我们为了保证原子性,所以jedis提供了一个原子操作,set(key,value,nx,30,时间单位)这样便解决了。
缺点:如果这个锁的时间不够用怎么办,那么就会导致这个功能锁不住。假设:a拿到锁了,但是a还没有执行结束,b又拿到锁了,那么a执行结束的时候是不是会把b的这个锁给删除掉。这样就导致了锁不住的效果。
改进:我们可以学习乐观所,给锁的value值是一个唯一的编号,或者版本号,我们每次对锁进行操作的时候,就会去验证这个版本号,还是不是自己的版本号。如果不是了就不允许操作了。

redis分布式锁第三版

通过上面的总结这第三版想必也很简单了。知识多了一个唯一值而已。但是加了唯一值还是改变不了锁不住的结果,只是解决了帮其他的线程解锁的问题,那么要怎么样才能锁得住呢?当时我想到的是给他 时间久一点,后来发现其实再久,也一样会出现锁不住的时候,而且太久了如果宕机了,就会有很长时间机器无法工作,很容易造成线程堆积。

redis分布式锁最终版

由上面我们发现一般简单实用redis做锁其实是有很多漏洞和bug的,但是有没有能够解决这些的呢?当然是有的。
模仿aqs锁, lock方法执行完之后,执行下面代码是被锁的,unlock执行完,释放锁。其他线程等待,而不是直接返回错误结果。

最终版还是打算先上代码再说,为了方便我把所有的实现都写在了一个类里面。

 @autowired
    private redistemplate redistemplate;

    @autowired
    private redisutils redisutils;

    @autowired(required = false)
    private threadpooltaskscheduler threadpooltaskscheduler;

    public final string lock_prefix = "redis_lock";

    private final long lock_expire = 30 * 1000l;

    private final long over_time = 10l;

    private map<string,scheduledfuture<?> > futuremap = new concurrenthashmap<>();

    private jedis jedis;

    public lock() {
    }

    private reentrantlock reentrantlock;

    /**
     * 给线程枷锁
     *
     * @param key
     */
    public void lock(string key) {
        //自旋获取锁
        while (true) {
            if (setlock(key)) {//拿锁成功
                //获取锁后开启任务
                threadpooltaskscheduler.schedule(()->{
                    set<string> keys = scan(lock_prefix);
                    iterator<string> iterator = keys.iterator();
                    //遍历所有的key 延长key的时间
                    while (iterator.hasnext()) {
                        log.info("执行动态定时任务: " + localdatetime.now().tolocaltime());
                        redisutils.expire(key, long.valueof(over_time), timeunit.seconds);//延长时间(秒)
                    }
                },new trigger(){
                    @override
                    public date nextexecutiontime(triggercontext triggercontext){
                        return new crontrigger("0/10 * * * * ?").nextexecutiontime(triggercontext);
                    }
                });
                return;
            }
        }
    }

    /**
     * setnx
     *
     * @param key
     * @return
     */
    public boolean setlock(string key) {
        string lock = lock_prefix + key;
        return (boolean) redistemplate.execute(new rediscallback<object>() {
            @override
            public object doinredis(redisconnection redisconnection) throws dataaccessexception {
                long expireat = system.currenttimemillis() + lock_expire + 1;
                boolean acquire = redisconnection.setnx(lock.getbytes(), string.valueof(expireat).getbytes());
                if (acquire) {
                    return true;
                } else {
                    byte[] value = redisconnection.get(lock.getbytes());
                    if (objects.nonnull(value) && value.length > 0) {
                        long expiretime = long.parselong(new string(value));
                        if (expiretime < system.currenttimemillis()) {
                            // 如果锁已经过期
                            byte[] oldvalue = redisconnection.getset(lock.getbytes(), string.valueof(system.currenttimemillis() + lock_expire + 1).getbytes());
                            // 防止死锁
                            return long.parselong(new string(oldvalue)) < system.currenttimemillis();
                        }
                    }
                }
                return false;
            }
        });
    }

    /**
     * 删除锁
     *
     * @param key
     */
    public void unlock(string key) {
        string lock = lock_prefix + key;
        synchronized (this) {
            futuremap.get(lock).cancel(true);//停止任务
            redistemplate.delete(lock);
        }
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean haskey(string key) {
        try {
            return redistemplate.haskey(key);
        } catch (exception e) {
            e.printstacktrace();
            return false;
        }
    }

    public set<string> scan(string key) {
        return (set<string>) redistemplate.execute((rediscallback<set<string>>) connection -> {
            set<string> keys = sets.newhashset();

            jediscommands commands = (jediscommands) connection.getnativeconnection();
            multikeycommands multikeycommands = (multikeycommands) commands;

            scanparams scanparams = new scanparams();
            scanparams.match("*" + key + "*");
            scanparams.count(1000);
            scanresult<string> scan = multikeycommands.scan("0", scanparams);
            while (null != scan.getstringcursor()) {
                keys.addall(scan.getresult());
                if (!stringutils.equals("0", scan.getstringcursor())) {
                    scan = multikeycommands.scan(scan.getstringcursor(), scanparams);
                    continue;
                } else {
                    break;
                }
            }

            return keys;
        });
    }

分析:

  • 判断是否获取到锁,获取到锁,继续执行,没有获取到锁,自旋继续获取。
  • 获取到锁后调度一个任务。每10秒执行一次,并且如果发现所没有释放延长10秒。
  • 释放锁,删除掉redis中的key,并结束掉对应的锁的任务。

加锁运行原理:

解锁操作原理:

解锁操作就比较简单了。但是得为了不出必要的麻烦,最好是给停止锁延时任务,和删除所 这两部添加进程锁,可以使用synchronized,也可以使用aqs lock锁。

这里redis非公平锁详解算是结束了,后期可能会更新使用redis,实现公平锁,谢谢大家的支持,如果有需要的小伙伴可以直接拿走,希望能给大家带来帮助。

在这里我希望看过文章的小伙伴能够根绝实现原理自己去实现,这样可以帮助小伙伴理解非公平锁机制,和redis实现非公平,如果不喜欢自己去实现的话,这里我给大家推荐一个redission 这个插件,这个插件是一个redis锁的很好的一个实现,大家可以直接用这个。具体怎么用就不讲解了,操作非常简单。

到此这篇关于redis分布式非公平锁的使用的文章就介绍到这了,更多相关redis分布式非公平锁内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐