Redission源码详解

环境搭建

redis搭建

在本地搭建个redis,启动redis服务端和客户端。客户端是为了观察redis中锁的信息。

依赖

新建个SpringBoot项目,依赖Redission jar包

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson-spring-boot-starter</artifactId>
	<version>3.21.1</version>
</dependency>

工具类

@Service
public class RedisUtil {
    @Autowired
    private RedissonClient redissonClient;

    public  boolean tryLock(Long key){
        RLock rLock= redissonClient.getLock(String.valueOf(key));
        try {
            boolean lockResult=rLock.tryLock(10,30, TimeUnit.SECONDS);
            return lockResult;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }
}

测试Controller

package com.ludk.weixin.controller;

import com.ludk.weixin.common.tool.RedisUtil;
import com.ludk.weixin.entity.Product;
import com.ludk.weixin.service.IProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;

@Controller
@Slf4j
@RequestMapping("/product")
public class ProductController {
    @Autowired
    RedisUtil redisUtil;




    @RequestMapping("/myProduct")
    @ResponseBody
    Object myProduct(@RequestParam("userId") Long userId){
        long before=System.currentTimeMillis();
        log.error("获取锁之前:"+before);
        redisUtil.tryLock(userId);
        long after=System.currentTimeMillis();
        System.out.println("获取锁之后:"+after);
        System.out.println((after-before)/1000);
        return productService.getMyProduct(userId);

}

源码思路分析

首先需要知道Redission实现的分布式锁有哪些特点?

  • 支持锁的超时,避免死锁。通过对Redis key设置超时时间实现。
  • 支持可重入锁,同一个线程可以多次获取锁。Redis中通过存储hash类型,其中一个字段记录次数实现。
  • 当线程A获取到锁执行业务逻辑且未执行完,如果锁超时,可以通过看门狗续锁的时间,避免其他线程获取锁。通过看门狗实现。

源码分析

初始化锁对象

主要是初始化默认锁的释放时间和uuid,下面会用到。

加锁方法

org.redisson.api.RLock#tryLock方法参数如下:

  • long waitTime:当获取锁失败时,等待waitTime时间后返回获取锁失败。
  • long leaseTime:锁释放时间,当获取到锁以后,这个锁多久失效。也就是redis中存储的锁的key的有效时间。
  • TimeUnit unit:上面两个时间的单位。
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
	//将锁的等待时间转为毫秒数
    long time = unit.toMillis(waitTime);
    //当前时间,从现在起算锁的等待时间
    long current = System.currentTimeMillis();
    //当前线程id,因为是可重入锁,需要用threadId区别是否是当前线程。
    long threadId = Thread.currentThread().getId();
    //获取锁,如果ttl 为null则说明获取锁成功。否则ttl代表这个锁还有多久超时,超时则当前线程有获取到锁的可能
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        return true;
    }
    // System.currentTimeMillis()是当前时间,current是进入tryLock这个方法的时间,time=time-(System.currentTimeMillis() - current)表示还有多少时间获取锁超时,time<=0就说明超时
    time -= System.currentTimeMillis() - current;
    if (time <= 0) {//获取锁超时,返回false。
        acquireFailed(waitTime, unit, threadId);
        return false;
    }
    //记录当前时间
    current = System.currentTimeMillis();
    //订阅监听redis消息,并且创建RedissonLockEntry,其中RedissonLockEntry中比较关键的是一个 Semaphore属性对象,用来控制本地的锁请求的信号量同步,返回的是netty框架的Future实现。
    CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    try {
    	//添加锁的监听,如果锁释放,会进入监听类,将Semaphore释放
    	// 阻塞等待subscribe的future的结果对象,如果subscribe方法调用超过了time,说明已经超过了客户端设置的最大wait time,则直接返回false,取消订阅,不再继续申请锁了。
        subscribeFuture.get(time, TimeUnit.MILLISECONDS);
    } catch (TimeoutException e) {
        if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
                "Unable to acquire subscription lock after " + time + "ms. " +
                        "Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
            subscribeFuture.whenComplete((res, ex) -> {
                if (ex == null) {
                    unsubscribe(res, threadId);
                }
            });
        }
        acquireFailed(waitTime, unit, threadId);
        return false;
    } catch (ExecutionException e) {
        acquireFailed(waitTime, unit, threadId);
        return false;
    }

    try {
    	//经过上面的计算,time是还多长时间获取锁超时,time已经被减掉一部分了。这里继续把消耗的时间减掉。
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {//time<0,获取锁超时,返回false,获取锁失败。
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
    
        while (true) {//死循环,有两个点可以调出循环。获取锁成功跳出、获取锁超时(失败)跳出
            long currentTime = System.currentTimeMillis();
            ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
            // lock acquired
            if (ttl == null) {
                return true;
            }

            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }

            // waiting for message
            currentTime = System.currentTimeMillis();
            //下面的阻塞会在释放锁的时候,通过订阅发布及时获取锁
            if (ttl >= 0 && ttl < time) {//如果锁的超时时间小于等待时间,通过SemaphorerelaseryAcquire阻塞锁的释放时间
                commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {//否则,通过Semaphore的tryAcquire阻塞传入的最大等待时间
                commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
            }

            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        }
    } finally {//finally中取消订阅
        unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
    }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
}

org.redisson.RedissonLock#tryAcquire方法

private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync0(waitTime, leaseTime, unit, threadId));
}

org.redisson.RedissonLock#tryAcquireAsync0方法

    private RFuture<Long> tryAcquireAsync0(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        return getServiceManager().execute(() -> tryAcquireAsync(waitTime, leaseTime, unit, threadId));
    }

org.redisson.RedissonLock#tryAcquireAsync

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture<Long> ttlRemainingFuture;
    if (leaseTime > 0) {//如果leaseTime >0,则传入锁释放时间,
        ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {//否则用默认的锁释放时间30000ms
        ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }
    CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);
    ttlRemainingFuture = new CompletableFutureWrapper<>(s);

    CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
        // lock acquired
        if (ttlRemaining == null) {
            if (leaseTime > 0) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {
            	//看门狗续时间,在锁要超时,业务还未执行完这种情况续锁时间。需要注意,leaseTime>0的时候不会有看门狗。
                scheduleExpirationRenewal(threadId);
            }
        }
        return ttlRemaining;
    });
    return new CompletableFutureWrapper<>(f);
}

org.redisson.RedissonLock#tryLockInnerAsync
该方法和redis交互,是锁实现的核心。

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, command,
                "if ((redis.call('exists', KEYS[1]) == 0) " +
                            "or (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(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }

分析这个lua脚本的参数:

  • KEYS[1]:是大key,判断是否有其他线程获取到锁。
  • ARGV[1]:锁的释放时间,这个锁如果不释放,多久会超时。
  • ARGV[2]:uuid+线程id,用于实现可重入锁。

分析lua脚本的逻辑:
if ((redis.call(‘exists’, KEYS[1]) == 0)or (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]);

  • redis.call(‘exists’, KEYS[1]) == 0:说明大key不存在,也就是没有其他线程获取锁,可以之间获取锁成功了。
  • redis.call(‘hexists’, KEYS[1] ,ARGV[2]) == 1):redis这大key下,当前线程已经获取到了锁。
  • return redis.call(‘pttl’, KEYS[1]):如果不是上面两种情况,说明其他线程占用锁了,返回这个锁还有多长时间释放。

总结:如果没有其他线程获取锁,或者当前线程已经获取到锁,则锁加上超时时间、当前线程锁的次数加1、然后返回空;否则说明有其他线程已经获取到锁,返回这个锁还有多久超时。

其他

lock、tryLock方法区别,参考这个博客:https://blog.csdn.net/weixin_37684345/article/details/135815110

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redission 是一个基于 RedisJava 分布式锁框架。它提供了一种简单且可靠的方式来实现分布式锁,以确保在分布式环境中的并发操作的一致性和互斥性。 Redission 的锁实现主要依赖于 Redis 的原子操作和 Lua 脚本。下面是 Redission 锁的源码解析: 1. 首先,Redission 提供了 `RLock` 接口作为分布式锁的主要接口。它提供了一系列的方法来获取锁、释放锁以及判断当前线程是否持有锁。 2. 在实现上,Redission 使用了 `RedissonLock` 类来具体实现 `RLock` 接口。该类内部维护了一个 `RMap` 对象,用于存储锁的状态信息。 3. 在获取锁时,Redission 会调用 `tryLockInner` 方法。该方法首先会尝试使用 Redis 的 `setnx` 命令来设置一个 Key-Value 对,其中 Key 是锁的名称,Value 是当前线程的标识符。如果设置成功,则表示当前线程获取到了锁;否则,表示锁已经被其他线程获取。 4. 在释放锁时,Redission 会调用 `unlockInner` 方法。该方法会使用 Redis 的 `eval` 命令执行一段 Lua 脚本来判断当前线程是否持有锁,并进行相应的解锁操作。 5. 为了确保锁的可重入性,Redission 使用了一个计数器来记录当前线程获取锁的次数。在每次获取锁时,计数器加一;在每次释放锁时,计数器减一。只有当计数器归零时,锁才会真正被释放。 总体来说,Redission 的锁实现是基于 Redis分布式原子操作和 Lua 脚本的。它通过对 Redis 的 Key-Value 存储进行操作,实现了简单且可靠的分布式锁机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值