虫虫的技术博客 技术 生活

Wednesday, March 20, 2019

关于redis分布式锁的几个疑问点

我们在分布式场景下,需要用到分布式锁,分布式锁的实现方案可以有redis实现,zk实现等,这里只讨论redis的方案
先稍微铺垫一下两个redis的不成熟方案,

一,
Get,判断是否存在,如存在直接返回失败,不存在继续
Set,设置锁,key对应的value为1,并设置过期时间
Del,业务执行完毕释放锁
code如下:
    private static long expireTime = 20000;

    public static boolean getLock(String key) {
        String lock = RedisUtil.get(key);
        if (!"1".equals(lock)) {
            RedisUtil.set(key, "1");
            RedisUtil.expire(key, expireTime);
            return true;
        } else {
            return false;
        }
    }

    public static void releaseLock(String key) {
        String check = RedisUtil.get(key);
        if (check != null) {
            RedisUtil.del(key);
        }
    }
此方案明显存在的问题就是,多个线程同时请求get,不存在,同时set,所以会有多个线程进入的可能

二,
Setnx,key对应的value为1,判断返回结果,0为设置失败,已经存在key,直接返回失败,1设置成功,继续
Expire,设置过期时间
Del,业务执行完毕释放锁
code如下:
    private static long expireTime = 20000;

    public static boolean getLock(String key) {
        long lock = RedisUtil.setNX(key, "1");
        if (lock == 1) {
            RedisUtil.expire(key, expireTime);
            return true;
        } else {
            return false;
        }
    }

    public static void releaseLock(String key) {
        String check = RedisUtil.get(key);
        if (check != null) {
            RedisUtil.del(key);
        }
    }
此方法,如果在setnx后expire前宕机,那key就不会被删除也没有过期,别的线程永远也进不来了

然后说下目前同事用到的,以及网上找到的比较被认可的解决方案
Setnx,key对应的value为过期时间戳,判断设置结果,1设置成功,返回成功,0设置失败,key存在内容,继续
Get,取出key的值,判断过期,如没过期,返回失败,如已过期,或者key已经不存在了,继续
Getset,设置锁,同时拿到返回值为设置之前的值,判断是否与刚才取出的值相等,如相等,证明是自己设置的,返回成功,如不相等,证明不是自己设置的,返回失败
Del,业务执行完毕释放锁
code如下:
    private static long expireTime = 20000;

    public static boolean getLock(String key) {
        long lock = RedisUtil.setNX(key, "" + System.currentTimeMillis() + expireTime);
        if (lock == 1) {
            return true;
        } else {
            String check = RedisUtil.get(key);
            if (check != null && System.currentTimeMillis() < Long.valueOf(check)) {
                //key还没有过期
                return false;
            } else {
                //key过期或者已经不存在了
                String old = RedisUtil.getSET(key, "" + System.currentTimeMillis() + expireTime);
                if (old == null) {
                    //说明此时已经key已经不存在,获取锁成功
                    return true;
                } else {
                    // TODO 疑问点1,这里同事用的是否过期的判断,会不会存在风险?
                    if (check.equals(old)) {
                        //获取锁成功
                        return true;
                    } else {
                        //在getSET之  前已经被别的线程获取了锁,获取锁失败
                        return false;
                    }
                }
            }
        }
    }

    public static void releaseLock(String key) {
        String check = RedisUtil.get(key);
        if (check != null) {
            if (System.currentTimeMillis() < Long.valueOf(check)) {
                // TODO 疑问点2,有没有可能删除别的线程设置的key
                RedisUtil.del(key);
            } else {
                // TODO 疑问点3,过期了是否需要删除
            }
        } else {
        }
    }

    // TODO 疑问点4,expire正常来说是需要设置成一个业务一定在此时间内能执行完成的时间,那么,业务如果万一没有执行完成的时候,redis锁还安全么?
    // TODO 疑问点5,redis锁真的安全吗,多个线程错综复杂的执行顺序,如何能比较好的理解呢?
    // TODO 疑问点6,这里还有个疑问,是不是理论上每两行代码之间都可能会中断,比如宕机,比如卡死过了很长时间之后继续执行?这个疑问明显的体现出了基础的薄弱

由于没有找到合适的人讨论,所以这里暂时把自己的疑问记录下来,待以后有机会再理解透彻 


0 comments:

Post a Comment

Popular Posts

Copyright © 虫虫的成长历程 | Powered by Blogger Design by PWT | Blogger Theme by NewBloggerThemes.com