Redis、Redission实现分布式锁

2023-11-06

Redis实现

使用spring-data-redis提供的接口实现redis分布式锁

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

一、利用SETNX设置KEY、VALUE并设置超时时间实现分布式锁

redis2.x版本中提供了不同参数的setIfAbsent方法,看下spring-data-redis源码

public Boolean setIfAbsent(K key, V value) {
  byte[] rawKey = rawKey(key);
  byte[] rawValue = rawValue(value);
  return execute(connection -> connection.setNX(rawKey, rawValue));
}


public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {
	byte[] rawKey = rawKey(key);
	byte[] rawValue = rawValue(value);
	Expiration expiration = Expiration.from(timeout, unit);
   return execute(connection -> connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent()));
}

手写lock

 public boolean redisLock(String key,String lock,long timeout){
        boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, lock);
        if(flag){
            /**
             * 拿到锁设置锁key的超时时间
             */
           stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
       }
    return flag;
}

public static Boolean lock(String key,String lock,long timeout){
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, lock, timeout,TimeUnit.SECONDS);
        if (flag) {
            log.info("获取锁成功");
        }
    return flag;
}

第一种方式:redisLock()方法是非原子性的,当执行完setIfAbsent方法后,服务器宕机或者重启,这时候key并没有设置过期时间,会导致死锁。

第二种方式:lock()方法是原子性的,调用setIfAbsent时同步设置key的过期时间

注意:这两种方式不可以用于多线程或者事务中,调用setIfAbsent返回的是null

二、SETNX设置kv(v为系统当前时间+超时时间)和GETSET(设置一个值并返回旧值)实现分布式锁

 public static Boolean redisDistributedLock(String key, long timeout) {
        //锁的值为当前系统时间+过期时间
        long lockValue = System.currentTimeMillis() + timeout;
        log.info("锁的初始值:{}",lockValue);
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, String.valueOf(lockValue))) {
            log.info("获取锁成功");
            return true;
        }else {
            log.info("我没有获取到锁");
        }
        //其他人没有获取到锁
        //获取当前锁的值
        String currentLockValue  = stringRedisTemplate.opsForValue().get(key);
        log.info("获取当前锁的值:{}",currentLockValue);
        //判断锁的值小于当前系统时间说明锁已经过期
        if(StringUtils.isNotEmpty(currentLockValue) && Long.parseLong(currentLockValue) < System.currentTimeMillis()){
            //getAndSet方法,返回旧的值并设置新值,线程安全所以只会有一个线程重新设置锁的新值
            String oldValue  = stringRedisTemplate.opsForValue().getAndSet(key, String.valueOf(lockValue));
            log.info("获取oldValue的值:{}",oldValue);
            //比较getAndSet方法获取最近的值和开始的值,如果不相等就证明已经被其他线程获取了
            if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentLockValue)){
                return true;
            }
        }
        return false;
}

这种方式没有设置key的过期时间,而是将value值设置为当前系统时间+过期时间。此方式要求服务器时间一致。

Redisson锁

1、redisson依赖

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.16.0</version>
</dependency>

2、配置类

@Configuration
public class RedissonConfig {
    
    @Bean
    public Redisson redisson() {
        // 单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        config.useSingleServer().setPassword("123456");
        return (Redisson) Redisson.create(config);
    }

}
 

3、redisson自动续期(看门狗)

public void task3() {
        //获取锁,没有获取到锁的会阻塞
        //redisson设置一个key的默认过期时间为30s
        //redisson会自动续期

        //设置lockey
        String lockKey = "taskLock";
        RLock lock = redisson.getLock(lockKey);
        //上锁
        /**
         * 处理业务执行时间大于锁的时间,自动续期
         * 不设置过期时间,默认锁的时间为30s,每1/3的时间就自动续期,业务处理完需要手动释放锁
         */
        lock.lock();
        //lock.lock(10,TimeUnit.SECONDS);  这种是10秒后锁自动过期,不会有自动续期的机制
        //boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
        try {
            //模拟业务执行
            Thread.sleep(40000);
            log.info("模拟业务执行了40s");
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            //释放锁
            lock.unlock();
        }

}

lock.lock(); 是阻塞式等待的,默认加锁时间是30s;如果业务超长,运行期间会自动续期到30s。不用担心业务时间长,锁自动过期被删掉;加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题;

也可以自己指定解锁时间lock.lock(10,TimeUnit.SECONDS),10秒钟自动解锁,自己指定解锁时间redis不会自动续期;

看门狗原理

源码分析

private void renewExpiration() {
        RedissonBaseLock.ExpirationEntry ee = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        if (ee != null) {
            Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                    RedissonBaseLock.ExpirationEntry ent = (RedissonBaseLock.ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
                    if (ent != null) {
                        Long threadId = ent.getFirstThreadId();
                        if (threadId != null) {
                            RFuture<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);
                            future.onComplete((res, e) -> {
                                if (e != null) {
                                    RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);
                                    RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());
                                } else {
                                    if (res) {
                                        RedissonBaseLock.this.renewExpiration();
                                    }

                                }
                            });
                        }
                    }
                }
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
            ee.setTimeout(task);
        }
    }

1、如果lock锁指定了过期时间,到期后锁自动释放,不会自动续期

2、当我们没有设置过期时间,默认的过期时间是30s,即 lockWatchdogTimeout = 30000ms。

自动续期时间: internalLockLeaseTime / 3 ,如果设置lockWatchdogTimeout = 60000ms,那么自动续期时间为60/3,即20秒自动续期。

解释一下:源码中 this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();

如果没有设置看门狗超时时间,默认30s,那么每隔10秒自动续期。

如果设置了看门狗超时时间,就是lockWatchdogTimeout/3秒自动续期

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis、Redission实现分布式锁 的相关文章

  • Spring RedisTemplate:8次调用后方法键挂起

    我使用 Spring RedisTemplate spring data redis 1 7 1 与 Redis 进行通信 我需要通过正则表达式获取然后删除键 例如 context user1 我用的方法 RedisTemplate key
  • Node.js 上通过套接字连接 Redis

    由于共享托管 目标主机上的我的 redis 服务器不在端口上运行 而是在非常特定的套接字上运行 可以通过套接字文件连接到该套接字 只有我的用户可以访问 但是 我还没有找到如何通过套接字指定连接node redis and connect r
  • Stackexchange.redis 缺乏“WAIT”支持

    我在客户端应用程序正在使用的负载均衡器后面有 3 个 Web API 服务器 我正在使用这个库来访问具有一个主服务器和几个从服务器的 Redis 集群 目前不支持 WAIT 操作 我需要此功能来存储新创建的用户会话并等待它复制到所有从属服务
  • Redis hash写入速度非常慢

    我面临一个非常奇怪的问题 使用 Redis 时 我的写入速度非常糟糕 在理想的情况下 写入速度应该接近 RAM 上的写入速度 这是我的基准 package redisbenchmark import redis clients jedis
  • redis-cli 重定向到 127.0.0.1

    我在PC1上启动Redis集群 然后在PC2上连接它 当需要重定向到另一个集群节点时 它会显示Redirected to slot 7785 located at 127 0 0 1 但应该显示Redirected to slot 7785
  • Redis 排序集和解决关系

    我正在使用 Redis 排序集来存储我正在处理的项目的排名 我们没有预料到 我们想要如何处理关系 Redis 按字典顺序对具有相同分数的条目进行排序 但我们想要做的是对具有相同分数的所有条目给予相同的排名 例如在以下情况 redis 127
  • Redis INCRBY 有限制

    我想知道是否有一种方法可以通过我的应用程序的单次往返在 Redis 中执行此操作 对于给定的键K 其可能值V是范围内的任意整数 A B 基本上 它有上限和下限 When an INCRBY or DECRBY发出命令 例如INCRBY ke
  • redis 阻塞直到 key 存在

    我是 Redis 新手 想知道是否有办法能够await get通过它的键来获取值 直到该键存在 最小代码 async def handler data await self fetch key async def fetch key ret
  • 如何将node.js管道传输到redis?

    我有很多数据要插入 SET INCR 到redis DB 所以我正在寻找pipeline http redis io topics pipelining 质量插入 http redis io topics mass insert通过node
  • 使用 Redis 命令 incr 和 expire 时的竞争条件

    根据redis文档 http redis io commands incr http redis io commands incr 在段落模式 速率限制器 2 较短的版本代码 value INCR ip IF value 1 THEN EX
  • socket.io 广播功能 & Redis pub/sub 架构

    如果有人能帮助我解决一个小疑问 我将不胜感激 使用socket io广播功能和在Redis上使用pub sub设计架构有什么区别 例如 在另一个示例中 node js 服务器正在侦听 socket io 针对 键 模型 todo 和值 数据
  • Lua中按字符分割字符串

    我有像这样的字符串 ABC DEF 我需要将它们分开 字符并将两个部分分别分配给一个变量 在 Ruby 中 我会这样做 a b ABC DEF split 显然Lua没有这么简单的方法 经过一番挖掘后 我找不到一种简短的方法来实现我所追求的
  • Redis Cluster 与 Pub/Sub 中的 ZeroMQ,用于水平扩展的分布式系统

    如果我要设计一个巨大的分布式系统 其吞吐量应随系统中的订阅者数量和通道数量线性扩展 哪个会更好 1 Redis集群 仅适用于Redis 3 0 alpha 如果是集群模式 您可以在一个节点上发布并在另一个完全不同的节点上订阅 消息将传播并到
  • Java 将字节转换为二进制安全字符串

    我有一些以字节为单位的数据 我想将它们放入Redis中 但是Redis只接受二进制安全字符串 而我的数据有一些二进制非安全字节 那么如何将这些字节转换为二进制安全字符串以便将它们保存到 Redis 中呢 Base64 对我有用 但它使数据更
  • 有没有办法用Lettuce自动发现Redis集群中新的集群节点IP

    我有一个Redis集群 3主3从 运行在一个库伯内斯簇 该集群通过Kubernetes 服务 Kube 服务 我将我的应用程序服务器连接到 Redis 集群 使用Kube 服务作为 URI 通过 Redis 的 Lettuce java 客
  • StackExchange.Redis Get 函数抛出 TimeoutException

    我在用着StackExchange Redis与 C 和StackExchangeRedisCacheClient Get函数抛出以下异常 myCacheClient Database StringGet txtKey Text myCac
  • redis dump.rdb / 保存小文件

    Context 我正在使用redis 数据库小于 100 MB 但是 我想进行每日备份 我也在 Ubuntu Server 12 04 上运行 当输入 redis cli save 我不知道 dump rdb 保存到哪里 因为 redis
  • 使用redis进行树形数据结构

    我需要为基于树的键值开发一个缓存系统 与Windows注册表编辑器非常相似 其中缓存键是字符串 表示树中到值的路径 可以是原始类型 int string bool double 等 或子树本身 例如 key root x y z w val
  • Spring Redis删除不删除key

    我正在尝试删除一个 Redis 键 但由于某种原因它没有删除 但也没有抛出异常 这是我要删除的代码 import com example service CustomerService import com example model Cu
  • 如何使用redis发布/订阅

    目前我正在使用node js和redis来构建应用程序 我使用redis的原因是因为发布 订阅功能 该应用程序只是在用户进入用户或离开房间时通知经理 function publishMsg channel mssage redisClien

随机推荐