首页 > 其他 > 详细

redis分布式锁

时间:2021-02-26 19:19:24      阅读:22      评论:0      收藏:0      [点我收藏+]
为什么需要分布式锁

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

相关特性或原则
  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 防止死锁。在一个获取锁的客户端获取锁发生故障后,也能自动释放掉锁,从而让其他客户端获取到锁
  • 原子性。加锁和解锁必须是同一个客户端
  • 可重入。同一个客户端获取后还能再次加锁
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上获取到一把新锁,这时候就会出现俩台服务实例都持有锁

redis分布式锁

原文:https://www.cnblogs.com/lantuanqing/p/14365338.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!