美文网首页
3.【Redis系列】Redis的高级应用-分布式锁

3.【Redis系列】Redis的高级应用-分布式锁

作者: 773eeb0e0c48 | 来源:发表于2019-03-08 23:46 被阅读0次

    在进行分布式应用逻辑开发时,经常会遇到并发问题。
    比如我们在修改一个用户的信息,首先需要获取用户信息,再内存中修改后,再存回去。这个过程如果有其他线程同时操作,着就会产生并发问题,因为读取和存储都不是原子性的。我们需要通过分布式锁限制程序的并发执行。

    1.分布式锁

    分布式锁本质上就是在Redis里面占一个车位,当有新的车辆过来时,发现已经有一辆车停在车位上,只能是放弃或者稍后再来。

    我们用命令模拟下:

    > setnx lock:qqsir true
    OK
    ...do something ... 
    >del lock:qqsir 
    

    但是这样有一个问题,如果中间的逻辑出现问题,导致没有执行del,这样就会陷入死锁,锁永远不能被释放。

    改进1: 我们可以对key设置一个过期时间,这样即使后面逻辑报错,时间到期后也可以将锁是释放掉

    > setnx lock:qqsir true
    OK
    >expire lock:qqsir 5
    ...do something ... 
    >del lock:qqsir 
    

    以上的逻辑其实还是存在问题,假设setnx和expire之间突然断电,没有执行,这样锁还是无法释放。这个问题的根本原因就是setnx和expire的操作不是原子性的。

    为了解决这个问题,开源社区出现了一堆分布式锁的library。专门解决这个问题,实现逻辑非常复杂。

    为了治理这个乱象,Redis2.8版本加入了set的扩展参数,是的setnx和expire指令可以一起执行,彻底解决了分布式锁的乱象。

    > setnx lock:qqsir true ex 5 nx
    OK
    >expire lock:qqsir 5
    ...do something ... 
    >del lock:qqsir 
    

    以上就是setnx和expire的原子指令。

    超时问题

    上面加了个过期时间是5s,如果中间的逻辑执行大于5s,后面的逻辑将得不到执行,为了避免这个问题,Redis分布式锁的逻辑不要过长,如果真的偶尔出现了就需要人工介入了。

    可重入性

    可重入性就是在持有锁的情况下,再次请求加锁,如果一个锁支持同一个线程的多次加锁,那这个线程就是可重入性的。比如java语言中的ReentrantLock就是一种可重入的锁。Redis的可重入锁,需要对set方法进行封装,使用线程的ThreadLocal变量存储当前只有持有锁的计数。

    以下是java版本的可重入锁实现

    public class RedisWithReentrantLock {
    
     private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
    
     private Jedis jedis;
    
     public RedisWithReentrantLock(Jedis jedis) {
       this.jedis = jedis;
     }
    
     private boolean _lock(String key) {
       return jedis.set(key, "", "nx", "ex", 5L) != null;
     }
    
     private void _unlock(String key) {
       jedis.del(key);
     }
    
     private Map<String, Integer> currentLockers() {
       Map<String, Integer> refs = lockers.get();
       if (refs != null) {
         return refs;
       }
       lockers.set(new HashMap<>());
       return lockers.get();
     }
    
     public boolean lock(String key) {
       Map<String, Integer> refs = currentLockers();
       Integer refCnt = refs.get(key);
       if (refCnt != null) {
         refs.put(key, refCnt + 1);
         return true;
       }
       boolean ok = this._lock(key);
       if (!ok) {
         return false;
       }
       refs.put(key, 1);
       return true;
     }
    
     public boolean unlock(String key) {
       Map<String, Integer> refs = currentLockers();
       Integer refCnt = refs.get(key);
       if (refCnt == null) {
         return false;
       }
       refCnt -= 1;
       if (refCnt > 0) {
         refs.put(key, refCnt);
       } else {
         refs.remove(key);
         this._unlock(key);
       }
       return true;
     }
    
     public static void main(String[] args) {
       Jedis jedis = new Jedis();
       RedisWithReentrantLock redis = new RedisWithReentrantLock(jedis);
       System.out.println(redis.lock("codehole"));
       System.out.println(redis.lock("codehole"));
       System.out.println(redis.unlock("codehole"));
       System.out.println(redis.unlock("codehole"));
     }
    
    }
    

    相关文章

      网友评论

          本文标题:3.【Redis系列】Redis的高级应用-分布式锁

          本文链接:https://www.haomeiwen.com/subject/dhxjpqtx.html