源码篇--Redisson 分布式锁lock的实现

这篇具有很好参考价值的文章主要介绍了源码篇--Redisson 分布式锁lock的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

我们知道Redis 缓存可以使用setNx来作为分布式锁,但是我们直接使用setNx 需要考虑锁过期的问题;此时我们可以使用Redisson 的lock 来实现分布式锁,那么lock 内部帮我们做了哪些工作呢。


一、Redisson 分布式锁的实现:

1.1 引入redis 和 redisson jar

  <!-- redis jar-->
<dependency>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-pool2</artifactId>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.9.1</version>
</dependency>

1.2 redis 客户端配置:

RedisConfig.java

package com.example.springredisms.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig {

    @Value("${spring.redis.host:localhost}")
    private String host;

    @Value("${spring.redis.port:6379}")
    private int port;

    @Value("${spring.redis.database:0}")
    private int db;

    @Value("${spring.redis.password:null}")
    private String password;
    /**
     * 配置lettuce连接池
     *
     * @return
     */
    @ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
    public GenericObjectPoolConfig redisPool() {
        return new GenericObjectPoolConfig<>();
    }
    /**
     * 配置第二个数据源
     *
     * @return
     */
    public RedisStandaloneConfiguration redisConfig() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port);
        redisStandaloneConfiguration.setDatabase(db);
        if (password != null && !"".equals(password) && !"null".equals(password)){
            redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        }
        return redisStandaloneConfiguration;
    }
    public LettuceConnectionFactory factory(GenericObjectPoolConfig redisPool, RedisStandaloneConfiguration redisConfig) {
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
        LettuceConnectionFactory connectionFactory  = new LettuceConnectionFactory(redisConfig, clientConfiguration);
        connectionFactory.afterPropertiesSet();
        return connectionFactory;
    }

    /**
     * 配置第一个数据源的RedisTemplate
     * 注意:这里指定使用名称=factory 的 RedisConnectionFactory
     *
     * @param
     * @return
     */
    @Bean("redisTemplate")
    public RedisTemplate<String, Object> redisTemplate() {
        RedisConnectionFactory factory1 = factory(redisPool(),redisConfig());
        return redisTemplate(factory1);
    }

    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //  使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 配置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 值采用json序列化
        redisTemplate.setValueSerializer(serializer);
        // 设置hash key 和value序列化模式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    /***
     * stringRedisTemplate默认采用的是String的序列化策略
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }
}

1.3 业务实现:

@Autowired
private RedissonClient redisson;

@Autowired
private RedisTemplate redisTemplate;

public String lockTest() {

   String lockKey ="good:123";
   	// 初始化 lock
    RLock lock = redisson.getLock(lockKey);
    // 阻塞获取锁
    lock.lock();
    // 获取锁成功执行扣减库存逻辑
    try{
        Thread.sleep(5000);
        String stockKey ="good:stock:123";
        int stock  = (Integer) redisTemplate.opsForValue().get(stockKey);
        if (stock >0){
            stock-=1;
            redisTemplate.opsForValue().set(stockKey,stock);
        }else{
            return "商品已经卖完了";
        }

    }catch (Exception ex){

    }
    finally {
    	// 释放锁
        lock.unlock();
    }

    return "success";
}

二、Redisson lock 实现原理

2.1 lock.lock():

lock.lock() 阻塞获取 redis 锁,获取到锁之后继续向下执行业务逻辑;

public void lock() {
  try {
  		// 相应中断的获取锁
        this.lockInterruptibly();
    } catch (InterruptedException var2) {
        Thread.currentThread().interrupt();
    }

}

lockInterruptibly():

public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    // tryAcquire 获取锁,如果获取成功 ttl 为null ,如果获取失败,说明当前有线程在持有锁,返回的是锁的剩余时间
    Long ttl = this.tryAcquire(leaseTime, unit, threadId);
    if (ttl != null) {
    	// 当前线程没有获取到锁,订阅一个与锁相关联的 Redis 频道
    	// 当锁被释放时,持有锁的线程会向相关联的 Redis 频道发布一条消息。
    	// 那些订阅了这个频道并正在等待锁释放的线程会接收到这个消息,
    	// 并会再次尝试获取锁。这个机制使得等待锁的线程能立刻知道锁何时被释放。
        RFuture<RedissonLockEntry> future = this.subscribe(threadId);
        this.commandExecutor.syncSubscription(future);

        try {
            while(true) {
            	// 循环去获取锁
                ttl = this.tryAcquire(leaseTime, unit, threadId);
                if (ttl == null) {
                	// 获取到锁直接返回
                    return;
                }
				// 如果锁的剩余时间还有很多
                if (ttl >= 0L) {
                	// 尝试去获取锁,如果失败则加入到等待的双向链表节点中,同时park 挂起当前线程
                    this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                	// 如果锁已经没有剩余时间了,锁到期了,则取获取锁
                    this.getEntry(threadId).getLatch().acquire();
                }
            }
        } finally {
        	// 获取锁成功后 当前线程取消订阅这把锁
            this.unsubscribe(future, threadId);
        }
    }
}

tryAcquire() 尝试去获取锁 :

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
   return (Long)this.get(this.tryAcquireAsync(leaseTime, unit, threadId));
}
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
   if (leaseTime != -1L) {
   		// 如果 不需要进行锁续命则 直接尝试去获取锁
        return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
    	// 尝试去获取锁
        RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        ttlRemainingFuture.addListener(new FutureListener<Long>() {
            public void operationComplete(Future<Long> future) throws Exception {
                if (future.isSuccess()) {
                    Long ttlRemaining = (Long)future.getNow();
                    if (ttlRemaining == null) {
                    	// 获取锁成功,开启一个定时任务 默认每隔10 s 完成一次锁续命
                        RedissonLock.this.scheduleExpirationRenewal(threadId);
                    }

                }
            }
        });
        return ttlRemainingFuture;
    }
}

tryLockInnerAsync 获取锁:

 <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, 
        "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]);", 
         Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    }

这里完成了获取锁和重入锁的实现:

  • 如果当前锁没有被线程占有,则设置当前当前线程 占有这把锁,并设置锁的过期时间为30s,返回null;
  • 如果当前 锁已经存在,并且是相同线程占用,则 设置将锁的重入次数+1,并设置锁的过期时间为30ss,返回null;
  • 如果锁已经被其它线程占有 ,则返回锁的过期时间;

scheduleExpirationRenewal :开启锁续命的定时任务

private void scheduleExpirationRenewal(final long threadId) {
    if (!expirationRenewalMap.containsKey(this.getEntryName())) {
    	// 延迟 internalLockLeaseTime / 3L 默认值是 30/3 =10s 后开启任务
        Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            public void run(Timeout timeout) throws Exception {
            	// redis 锁续期
                RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
                future.addListener(new FutureListener<Boolean>() {
                    public void operationComplete(Future<Boolean> future) throws Exception {
                        RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
                        if (!future.isSuccess()) {
                        	//  当前线程没有获取获取到锁,进行延期则直接报错
                            RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());
                        } else {
                        	// 续期成功后,继续进行下一次调用
                            if ((Boolean)future.getNow()) {
                                RedissonLock.this.scheduleExpirationRenewal(threadId);
                            }

                        }
                    }
                });
            }
        }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
        if (expirationRenewalMap.putIfAbsent(this.getEntryName(), new ExpirationEntry(threadId, task)) != null) {
            task.cancel();
        }

    }
}

renewExpirationAsync 锁续期:

 protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
         "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
         then redis.call('pexpire', KEYS[1], ARGV[1]); 
         return 1; end; return 0;", 
         Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    }
  • 如果这把锁还存在,则设置过期时间(默认值是30s)并且返回true
  • 这把锁不存在返回false;

2.2 锁释放 lock.unlock():

在业务执行完毕之后 通过lock.unlock(); 释放锁

public void unlock() {
   try {
   		// 释放 锁
        this.get(this.unlockAsync(Thread.currentThread().getId()));
    } catch (RedisException var2) {
        if (var2.getCause() instanceof IllegalMonitorStateException) {
            throw (IllegalMonitorStateException)var2.getCause();
        } else {
            throw var2;
        }
    }
}

unlockAsync 释放锁:

public RFuture<Void> unlockAsync(final long threadId) {
    final RPromise<Void> result = new RedissonPromise();
    // 释放锁
    RFuture<Boolean> future = this.unlockInnerAsync(threadId);
    future.addListener(new FutureListener<Boolean>() {
        public void operationComplete(Future<Boolean> future) throws Exception {
            if (!future.isSuccess()) {
                RedissonLock.this.cancelExpirationRenewal(threadId);
                result.tryFailure(future.cause());
            } else {
            	// 
                Boolean opStatus = (Boolean)future.getNow();
                if (opStatus == null) {
                    IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + RedissonLock.this.id + " thread-id: " + threadId);
                    result.tryFailure(cause);
                } else {
                    if (opStatus) {
                    	// 释放锁成功 则取消锁延期
                        RedissonLock.this.cancelExpirationRenewal((Long)null);
                    }

                    result.trySuccess((Object)null);
                }
            }
        }
    });
    return result;
}

unlockInnerAsync 释放锁:

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
        "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;", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId)});
    }
  • 如果当前锁已经不存在,则发布锁释放消息,让其他阻塞的现车去抢占锁,返回true;
  • 如果锁还存在,则将锁的重入次数-1 后返回锁的重入次数;
  • 如果重入次数大于0 说明当前线程还需要绩效持有锁,重新设置锁的过期时间,返回false;
  • 如果重入次数等于0 ,则删除这个key 相当于是否了锁,然后发布锁释放消息,让其他阻塞的现车去抢占锁,返回true;

总结

锁过期自动续时的触发条件是tryLock设置的锁到期时间leaseTime == -1;自动续时原理就是创建一个定时任务,每internalLockLeaseTime / 3时间触发一次,如果发现持有锁未释放,就把锁过期时间更新为internalLockLeaseTime(internalLockLeaseTime的值取得是lockWatchdogTimeout默认是30s);过期时间更新成功后,再次递归调用renewExpiration(),创建下一次定时任务;默认每次都延期30s;
这种机制主要是为了避免在没来得及解锁的情况下系统就挂了,导致该锁在redis中一直都被占用,其他线程永远都无法获取锁,也就是死锁的情况;文章来源地址https://www.toymoban.com/news/detail-824435.html

到了这里,关于源码篇--Redisson 分布式锁lock的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • redisson+aop实现分布式锁

    基于注解实现,一个注解搞定缓存 Aop:面向切面编程,在不改变核心代码的基础上实现扩展,有以下应用场景 ①事务 ②日志 ③controlleradvice+expetcationhandle实现全局异常 ④redissson+aop实现分布式锁 ⑤认证授权 Aop的实现存在与bean的后置处理器beanpostprocessAfterinitlazing 注解的定义仿照

    2024年01月19日
    浏览(69)
  • SpringBoot结合Redisson实现分布式锁

    🧑‍💻作者名称:DaenCode 🎤作者简介:啥技术都喜欢捣鼓捣鼓,喜欢分享技术、经验、生活。 😎人生感悟:尝尽人生百味,方知世间冷暖。 📖所属专栏:SpringBoot实战 以下是专栏部分内容,更多内容请前往专栏查看! 标题 一文带你学会使用SpringBoot+Avue实现短信通知功能

    2024年02月08日
    浏览(42)
  • 图解Redisson如何实现分布式锁、锁续约?

    使用当前(2022年12月初)最新的版本:3.18.1; 案例 案例采用redis-cluster集群的方式; redission支持4种连接redis方式,分别为单机、主从、Sentinel、Cluster 集群;在分布式锁的实现上区别在于hash槽的获取方式。 具体配置方式见Redisson的GitHub(https://github.com/redisson/redisson/wiki/2.-%E9

    2023年04月16日
    浏览(45)
  • Redis分布式锁及Redisson的实现原理

    Redis分布式锁 在讨论分布式锁之前我们回顾一下一些单机锁,比如synchronized、Lock 等 锁的基本特性: 1.互斥性:同一时刻只能有一个节点访问共享资源,比如一个代码块,或者同一个订单同一时刻只能有一个线程去支付等。 2.可重入性: 允许一个已经获得锁的线程,在没有释

    2024年02月06日
    浏览(45)
  • Spring Boot 集成 Redisson 实现分布式锁

            Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库,可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面,例如分布式锁、分布式集合、分布式事件发布和订阅等。本篇

    2024年02月08日
    浏览(39)
  • 微服务系列文章之 Redisson实现分布式锁

    当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。 1、互斥 在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。 2、防止死

    2024年02月15日
    浏览(99)
  • 微服务系列文章之 Redisson实现分布式锁(3)

    1、技术架构 项目总体技术选型 2、加锁方式 该项目支持  自定义注解加锁  和  常规加锁  两种模式 自定义注解加锁 常规加锁 3、Redis部署方式 该项目支持四种Redis部署方式 该项目已经实现支持上面四种模式,你要采用哪种只需要修改配置文件 application.properties ,项目代码

    2024年02月16日
    浏览(40)
  • 微服务系列文章之 Redisson实现分布式锁(2)

    1、概念 很明显RLock是继承Lock锁,所以他有Lock锁的所有特性,比如lock、unlock、trylock等特性,同时它还有很多新特性:强制锁释放,带有效期的锁,。 2、RLock锁API 这里针对上面做个整理,这里列举几个常用的接口说明 RLock相关接口,主要是新添加了  leaseTime  属性字段,主要是

    2024年02月16日
    浏览(33)
  • spring boot 实现Redisson分布式锁及其读写锁

    分布式锁,就是控制分布式系统中不同进程共同访问同一共享资源的一种锁的实现。 1、引入依赖 2、配置文件 3、配置类 4、测试代码 5、理解 一、时间设置 默认 lock() 小结 lock.lock (); (1)默认指定锁时间为30s(看门狗时间) (2)锁的自动续期:若是业务超长,运行期间自

    2024年02月12日
    浏览(38)
  • redis实战-redis实现分布式锁&redisson快速入门

    前言 集群环境下的并发问题  分布式锁 定义 需要满足的条件 常见的分布式锁 redis实现分布式锁 核心思路 代码实现 误删情况 逻辑说明 解决方案 代码实现 更为极端的误删情况 Lua脚本解决原子性问题 分布式锁-redission redisson的概念 快速入门 总结 在前面我们已经实现了单机

    2024年02月09日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包