zoukankan      html  css  js  c++  java
  • redis分布式锁实践

    分布式锁三种方式:

    • 基于 DB 的唯一索引  insert或for update

    • 基于 ZK 的临时有序节点。

    • 基于 Redis 的 NX EX 参数。

    https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247486492&idx=1&sn=d1bebca555cea270be26bc7db71f2d97&chksm=fa4973adcd3efabb1a2cc3e097de22c8c5137cd949f427a7f1ea30e3a213b6138e139fb056fb&mpshare=1&scene=1&srcid=0313WPoUGx6APIcVMCaR0DUx&key=457de6603f21716caa3b880bf1a7c2aa26b2fe04e8526f69f10e08c49adb7964dc3aeb7d3b0ed049b894aa238709303ca4f9c56a65a1f75cfb23abe1e75d43f68e72c77855e01d76ee45ee6938abaafe&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=Df4mpQKFjK%2B%2FQ2Kl9UoFADONX%2BtF5g2YxwepTbhb05L1i3wKFR6V227W5KJtz%2FxH

    一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)

    1.

    tryLock(){  
        SETNX Key 1
        EXPIRE Key Seconds
    }
    release(){  
      DELETE Key
    }

    加锁后挂掉死锁,这个情况决定了必须expire锁(无论是硬的还是软的)

    2.

    tryLock(){  
        SETNX Key 1 Seconds
    }
    release(){  
      DELETE Key
    }

    既然expire锁了,多久实效好,有3个问题:

    2.1 如果业务处理10s,锁5s自动释放了,就产生并发问题,解决:时间长一点,或另开一个线程定期watch锁的过期时间,不足时加时间

    2.2 A可能干掉B获取的锁

    2.3 持有锁的进程宕机或断网(这种情况下finaly中释放锁都没有,造成其他等待获取锁的进程长时间的无效等待?解决:只能等自动过期,所以时间要短一些(汗)

    3.

    tryLock(){  
        SET Key UniqId Seconds
    }
    release(){  
        EVAL(
          //LuaScript
          if redis.call("get",KEYS[1]) == ARGV[1] then
              return redis.call("del",KEYS[1])
          else
              return 0
          end
        )
    }

    这个方案通过指定Value为时间戳,并在释放锁的时候检查锁的Value是否为获取锁的Value,

    要确保释放的是自己加的锁,且必须原子:

    解决了2.2的问题

    这种解锁代码乍一看也是没问题,甚至我之前也差点这样实现,与正确姿势差不多,唯一区别的是分成两条命令去执行,代码如下:
    
    public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
    
        // 判断加锁与解锁是不是同一个客户端
        if (requestId.equals(jedis.get(lockKey))) {
            // 若在此时,这把锁突然不是这个客户端的,则会误解锁
            jedis.del(lockKey);
        }
    
    }
    如代码注释,问题在于如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。那么是否真的有这种场景?答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
    

    redison 单机分布式锁就是基于这套原理来实现的:http://www.imooc.com/article/284859 ,追加了可重入的特性

    4.

    tryLock(){  
        SET Key UniqId Seconds

      new Thread{if(Key.get == UniqId, expire more time}.每隔几秒
    }
    release(){  
        EVAL(
          //LuaScript
          if redis.call("get",KEYS[1]) == ARGV[1] then
              return redis.call("del",KEYS[1])
          else
              return 0
          end

        shutdown newThread关闭续命,无论释放成功没有
        )
    }

    解决了2.1 2.3的问题

    5.

    tryLock(){  
        SET Key UniqId Seconds

      threadlocal<Key>.set

      new Thread{if(Key.get == UniqId, expire more time}.每隔几秒
    }
    release(){  
        EVAL(
          //LuaScript
          if redis.call("get",KEYS[1]) == ARGV[1] then
              return redis.call("del",KEYS[1])
          else
              return 0
          end

        threadlocal<Key>.remove

        shutdown newThread关闭续命,无论释放成功没有
        )
    }

    解决了可重入

    5. 分布式redis集群?

    • 在Redis的master节点上拿到了锁;
    • 但是这个加锁的key还没有同步到slave节点;
    • master故障,发生故障转移,slave节点升级为master节点;
    • 导致锁丢失。

    https://blog.csdn.net/zl1zl2zl3/article/details/93968446

    解决方案,5台redis集群,获取3个则获取锁

    2019.8.2

    参照:https://www.cnblogs.com/linjiqin/p/8003838.html?from=message&isappinstalled=0  实践:

    import redis.clients.jedis.Jedis;
    
    import java.util.Collections;
    
    /**
     * Created by sunyuming on 19/8/2.
     */
    public class RedisLock {
    
        private static final String LOCK_SUCCESS = "OK";
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
    
        /**
         * 尝试获取分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
    
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
    
        }
    
        private static final Long RELEASE_SUCCESS = 1L;
    
        /**
         * 释放分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @return 是否释放成功
         */
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
    
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
    
        }
    
        public static void main(String [] f) {
    
            final String lockValue1 = "sun1";
            final String lockValue2 = "sun2";
            final String lockKey = "templock1346";
    
            // 10s redis 连接超时
            Jedis jedis = new Jedis("10.161.228.88", 6379, 10000);
            jedis.auth("test");
            Boolean lock = null;
            Boolean unlock = null;
            while (true) {
                // 锁20s
                lock = tryGetDistributedLock(jedis, lockKey, lockValue1, 20000);
                if(lock) {
                    System.out.println("取得锁");
    
                    // 此句模拟视图释放其它线程的锁
                //    jedis.set(lockKey, lockValue2);
                    unlock = releaseDistributedLock(jedis, lockKey, lockValue1);
                    if(unlock) {
                        System.out.println("释放成功");
                    } else {
                        System.out.println("释放失败");
                    }
                    break;
                } else {
                    System.out.println("未获取锁");
                }
                
                // 100ms轮训自旋
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
    
        }
    }
    

    2020.8.11 再次实践,解决续命,可重入

  • 相关阅读:
    swift运算符使用_02_swift基本数据类型
    OSChina-01Swift终端命令行
    开源框架汇总-01-第三方
    修改App名称-01-修改项目中APP名
    NSAttributedString.h 中文本属性key的说明-06
    SQLite总结-05
    Linux系统判断gcc是否安装
    翻译文件结构规范
    并行SVN版本控制
    页面设计规范
  • 原文地址:https://www.cnblogs.com/silyvin/p/9106569.html
Copyright © 2011-2022 走看看