Redis分布式锁的一点小理解

1、在分布式系统中,我们使用锁机制只能保证同一个JVM中一次只有一个线程访问,但是在分布式的系统中锁就不起作用了,这时候就要用到分布式锁(有多种,这里指 redis)

2、在 redis当中可以使用命令 setnx(key, value)来实现分布式锁

  setnx:当key不存在的时候设置成功,返回1,若存在的话返回0表示失败。使用这个命令的话要搭配 expire(key, time)来设置过期时间,但是这种组合存在问题,如下:

   try {
        if (redisclient. setnx(key 1) == 1) { //1
            redisClient.expire(key, 1000);//2
        }
    } finally {
        redisClient.del(key);
    }

  如上代码,看上去没什么问题,但是极端情况下如果在1处执行完毕2处还没执行这时候这台机器宕机了,那么这个锁将是无期限的,且不会被删除,也就是说设置setnx和expire是两个命令,不具备原子性 针对这个问题,可以使用 redis2.6版本之后的命令set(key, value, expirefime,NX)来解决,这个命令跟 setnx一样,但是多了过期时间,可以很好的解决这个问题。

3、解决这个问题之后,还存在着一个问题:如果在过期时间内程序代码没执行完,那么其他其他机器线程获得这个锁,这样会造成同时有两个线程执行一段代码,并且A机器(过期还没执行完)中fiηa11y会删除key,导致误删到B机器锁(当前获得锁的机器)的情况

  1、此时我们可以在相同的机器上开一个守护线程(如上面例子就在A机器再开一个守护线程),这个线程主要作用是在key快过期的时候进行续命操作,保证代码执行完毕。 2、关于误删,我们可以把value设成线程id,删除前判断一下是否是自己的线程ID,是的话再执行删除,如下面代码

     try {
            ....
        } finally {
            if (threadId equals(redisclient get(key)) {//1
                redisclient.del(key);//2
            }
        }

  这时一般情况都没问题,但是这里的1和2又跟前面的问题类似---不具备原子性,所以还是有出错的可能,但是 redis中没有支持获取删除的原子性命令,该怎么解决呢? 我们可以通过工Lua脚本来解决,例如本例中可以像下面这么写

     String luascript = "if redis call('get, KeY[1])==ARGV[l] then return redis.call('del', KEY[1]) else return 0 end";
        redisClient.eval(luaScript, Collection.singletonList(key), Collection.singletonlist(threadId));

4、到这里,基本就没什么问题了,最终的代码如下

  try {
        String luascript = "if redis call('get, KeY[1])==ARGV[l] then return redis.call('del', KEY[1]) else return 0 end";
        string threadId = Thread.currentThread() getId();
        while (redisclient set(key, threadId, 1000, NX) == 1) {
         // dosomething()
        }
    } finally {
        redisClient.eval(luaScript, Collection.singletonList(key), Collection.singletonlist(threadId));
    }

 

猜你喜欢

转载自www.cnblogs.com/zhangweicheng/p/11495971.html