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

    为什么需要分布式锁

    应用服务无法通过本地锁来控制并发,故需要分布式锁来控制

    相关特性或原则
    • 互斥性。在任意时刻,只有一个客户端能持有锁。
    • 防止死锁。在一个获取锁的客户端获取锁发生故障后,也能自动释放掉锁,从而让其他客户端获取到锁
    • 原子性。加锁和解锁必须是同一个客户端
    • 可重入。同一个客户端获取后还能再次加锁
    jedis与redisson

    Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令,的支持;Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能,较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。

    Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

    实现
    自己使用jedis实现
    • 简单实现
        public static Boolean lock2(Jedis jedis,String key,String requestId){
            String result = null;
    
            long start = System.currentTimeMillis();
            do {
                result = jedis.set(key, requestId, "NX", "EX", expireTime);
                if(!"OK".equals(result)){
                    return true;
                }
                long cost = System.currentTimeMillis() - start;
                if(cost > 10){
                    return false;
                }
            } while (true);
        }
    
        public static Boolean unLock2(Jedis jedis,String key,String requestId){
            Long result = null;
            if(requestId.equals(jedis.get(key))){
                result = jedis.del(key);
            }
            return result != null && result == 0;
        }
    

    不可重入,无续期 过期时间,当A线程获取锁执行后,到达过期,B线程可以获取到锁

    • 优化版
    public static Long lock2(Jedis jedis,String key,String requestId,Long expireTime){
            Long result = null;
           do {
           	
             if(!jedis.exists(key)){//判断是否存在,若存在则代表有客户端获取到锁
                    jedis.hset(key, requestId,"1");//写入hash表中,1代表锁过一次
                    jedis.pexpire(key,expireTime);
                    break;
                }
                if(jedis.hexists(key,requestId)){//判断获取到锁的客户端是否是自己
                    jedis.hincrBy(key,requestId,1L);//若是自己则锁的次数加1
                    jedis.pexpire(key,expireTime);//刷新锁的时间
                    break;
                }
                result = jedis.pttl(key);//未获取到锁,则返回当前锁的过期时间
            } while (result != null);
    		
            return result;
        }
    
    public static Integer unLock2(Jedis jedis,String key,String requestId,Long expireTime){
        	
            if(!jedis.exists(key)){//判断是否存在,不存在则已解锁
                return 1;
            }
            if(!jedis.hexists(key,requestId)){//该请求是否持有锁,不存在则是其他请求持有锁
                return null;
            }
            if(jedis.hincrBy(key,requestId,-1) > 0){//减一后还大于0,则该请求还是持有锁,有过多次加锁
                jedis.pexpire(key,expireTime);
                return 0;
            }else {//解锁次数大于等于加锁次数,释放锁
                jedis.del(key);
                return 1;
            }
            
    }
    

    无续期机制

    使用redisson框架
    • 加锁机制
      发送一段lua脚本到redis上
    "if (redis.call('exists', KEYS[1]) == 0) then " +
                          "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return nil; " +
                      "end; " +
                      "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                          "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return nil; " +
                      "end; " +
                      "return redis.call('pttl', KEYS[1]);",
    

    通过封装在lua脚本中发送给redis,保证这段复杂业务逻辑执行的原子性

    • 释放锁机制
    "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; " +
                    "end;" +
                    "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                    "end; " +
                    "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                    "if (counter > 0) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                        "return 0; " +
                    "else " +
                        "redis.call('del', KEYS[1]); " +
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; "+
                    "end; " +
                    "return nil;",
    

    每次都对数据结构中的那个加锁次数减1,如果发现加锁次数是0了,说明这个客户端已经不再持有锁了

    • 看门狗续期
    "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                                "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                "return 1; " +
                            "end; " +
                            "return 0;"
    

    缺点:一个服务实例在master上获取到锁,如果master和salve同步的过程中,master宕机了,salve还没有同步到这把锁,就被切换成了master,另一个服务实例在新的master上获取到一把新锁,这时候就会出现俩台服务实例都持有锁

  • 相关阅读:
    [cf1097F]Alex and a TV Show
    [cf1097E]Egor and an RPG game
    2.2 物理层下面的传输媒体
    2.1 物理层的基本概念
    8 垃圾回收
    7 直接内存
    6 方法区
    1.5 计算机网络体系结构
    1.4 计算机网络的性能指标
    1.3 计算机网络的定义和分类
  • 原文地址:https://www.cnblogs.com/lantuanqing/p/14365338.html
Copyright © 2011-2022 走看看