zoukankan      html  css  js  c++  java
  • 如何设计一个分布式锁?如何对锁性能进行优化?

    如何设计一个分布式锁?如何对锁性能进行优化?

    分布式锁的本质:就是在所有进程都能访问到的一个地方(如数据库、zookeeper、Redis),设置一个锁资源,让这些进程都来竞争锁资源。通常对于分布式锁,会要求响应快、性能高、与业务无关。

    Redis实现分布式锁

    • SETNX key value :NX表示NOT EXIST,当key不存在时就将key设置为value,并返回1,如果key不存在则直接返回0。
    • EXPIRE key locktime :设置key的有效时长。
    • DEL key:删除key
    • GETSET key value: 先GET再SET,即先返回key对应的值(如果没有就返回空),然后将key设置成value。
    1. 最简单的分布式锁:SETNX加锁,DEL解锁。存在的问题:如果获取到锁的进程执行失败,他就永远不会主动解锁,那这个锁就被锁死了。
    public boolean tryLock(RedisConnect conn){
      if(conn.SETNX("mykey", "1")==1){
         return true;
      }else{
         return false;
      }
    }
    
    1. 解决方案:给锁设置过期时间。存在的问题:SETNX和EXPIRE并不是原子性的,所以获取到锁的进程有可能还没有执行EXPIRE指令,就挂了,这样锁还是会被锁死。
    public boolean tryLock(RedisConnect conn){
      if(conn.SETNX("mykey", "1")==1){
         // 给锁设置过期时间
         conn.EXPIER("mykey", 1000);
         return true;
      }else{
         return false;
      }
    }
    
    1. 解决方案:将锁的内容设置为过期时间(客户端时间+过期时长),SETNX获取锁失败时,拿过期时间跟当前时间比对,如果是过期锁,就先删除锁,再重新上锁。存在的问题:高并发场景下,会产生多个进程同时拿到锁的情况。
    public boolean tryLock(RedisConnect conn){
      long nowTime = System.currentTimeMillis();
      long expireTime = nowTime+1000;
      if(conn.SETNX("mykey", "expireTime)==1){
         // 给锁设置过期时间
         // conn.EXPIER("mykey", 1000);
         return true;
      }else{
        // SETNX获取锁失败时,拿过期时间跟当前时间比对,如果是过期锁,就先删除锁,再重新上锁。
        long val = conn.get("mykey");
        if(val!=null&&val<nowTime){
          // 此处直接覆盖锁
           conn.SET("mykey", expireTime);
           return true;
            }
         return false;
      }
    }
    
    1. 解决方案:SETNX失败后,获取锁上面的时间戳,然后用GETSET,将自己的过期时间更新上去,并获取旧值。如果这个旧值,跟之前获得的时间戳是不一样的,就表示这个锁已经被其他进程占用了,自己就要放弃竞争锁。
    public boolean tryLock(RedisConnect conn){
      // 将锁的内容设置为过期时间(客户端时间+过期时长)
      long nowTime = System.currentTimeMillis();
      long expireTime = nowTime+1000;
      if(conn.SETNX("mykey", "expireTime)==1){
         conn.EXPIER("mykey", 1000);
         return true;
      }else{
        // SETNX失败后,获取锁上面的时间戳
        long oldVal = conn.get("mykey");
        if(oldVal!=null&&oldVal<nowTime){
            // 然后用GETSET,将自己的过期时间更新上去,并获取旧值
            long currentVal = conn.GETSET("mykey", expireTime);
            if(oldVal==currentVal){
                conn.EXPIRE("mykey", 1000);
                return true;
            }
            // 如果这个旧值,跟之前获得的时间戳是不一样的,就表示这个锁已经被其他进程占用了,自己就要放弃竞争锁。
            return false;
         }
         return false;
      }
    }
    
    1. 上面各种优化的根本问题在于:SETNX和EXPIRE两个指令无法保证原子性。
      Redis2.6提供了直接执行lua脚本的方式,通过lua脚本来保证原子性。redission
  • 相关阅读:
    没有项目种类分配到科目XXX
    编制总账科目凭证技巧(贷方)
    设置雇员容差组
    设定容差组
    英语-20210226
    如何流利说英语
    英语-20210225
    EPS(每股盈余)
    大本力量训练法
    拓端tecdat|R语言逻辑回归分析连续变量和分类变量之间的“相关性“
  • 原文地址:https://www.cnblogs.com/pangqianjin/p/14616932.html
Copyright © 2011-2022 走看看