Redisson 分布式锁深度解析:API 使用与底层源码探秘

简介: 本文深入解析Redisson分布式锁的使用与源码实现,涵盖可重入锁、公平锁、读写锁、红锁等核心API的应用场景与配置方法,并通过Lua脚本、Hash结构和看门狗机制剖析其原子性、重入性与自动续期原理,助力开发者高效安全地实现分布式并发控制。

Redisson 分布式锁深度解析:API 使用与底层源码探秘

在分布式系统中,并发控制是永恒的话题。单机环境下的synchronizedLock接口能轻松解决线程安全问题,但在多节点、跨进程的分布式场景中,传统锁机制完全失效。基于 Redis 的分布式锁是主流解决方案之一,而Redisson作为 Redis 的 Java 客户端,不仅封装了原生 Redis 的分布式锁实现,还解决了原子性、重入性、超时续期等痛点,提供了丰富的锁类型和简洁的 API。

本文将从实际应用出发,详细讲解 Redisson 分布式锁核心 API 的使用方法,并深入剖析RLock的底层源码,带你理解 Redisson 分布式锁的实现原理。

一、Redisson 环境搭建

在使用 Redisson 分布式锁前,需先完成基础环境的搭建。我们以 Spring Boot 项目为例,演示快速集成 Redisson 的步骤。

1.1 引入 Maven 依赖

pom.xml中添加 Redisson 的 Spring Boot Starter 依赖(推荐使用官方适配版本,避免 Redis 版本冲突):

<!-- Redisson Spring Boot Starter -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.5</version>
</dependency>

<!-- Redis客户端依赖(若已引入可省略) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.2 配置 Redisson 客户端

application.yml中配置 Redis 连接信息(以单机模式为例,集群 / 哨兵模式可参考官方文档):

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    database: 0

# Redisson配置(可选,默认使用Spring Redis配置)
redisson:
  singleServerConfig:
    address: redis://127.0.0.1:6379
    password: 123456
    database: 0

1.3 注入 RedissonClient

Redisson 的 Starter 会自动创建RedissonClient实例,直接通过@Autowired注入即可使用:

import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RedissonLockDemo {
   

    @Autowired
    private RedissonClient redissonClient;

}

二、Redisson 分布式锁核心 API 使用

Redisson 提供了多种分布式锁实现,包括可重入锁、公平锁、读写锁、联锁、红锁等,满足不同业务场景的需求。其中,RLock(可重入锁)是最基础、最常用的锁类型,其他锁均基于其扩展。

2.1 可重入锁(RLock)

可重入锁是指同一个线程可以多次获取同一把锁,不会导致死锁。Redisson 的RLock实现了 Java 的java.util.concurrent.locks.Lock接口,使用方式与原生ReentrantLock几乎一致。

基本使用示例

import org.redisson.api.RLock;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class ReentrantLockService {
   

    @Autowired
    private RedissonClient redissonClient;

    // 定义锁名称
    private static final String LOCK_KEY = "distributed:lock:demo";

    public void doBusiness() {
   
        // 1. 获取锁实例
        RLock lock = redissonClient.getLock(LOCK_KEY);

        try {
   
            // 2. 获取锁:支持超时自动释放、可中断
            // lock.lock(); // 无参:默认30秒过期,看门狗自动续期
            // lock.lock(10, TimeUnit.SECONDS); // 指定过期时间,无看门狗
            boolean isLocked = lock.tryLock(3, 10, TimeUnit.SECONDS); // 尝试获取锁,3秒等待,10秒过期

            if (isLocked) {
   
                // 3. 执行业务逻辑(分布式并发控制)
                System.out.println("获取锁成功,执行核心业务...");
                Thread.sleep(5000); // 模拟业务耗时
            } else {
   
                System.out.println("获取锁失败,业务执行中断");
            }
        } catch (InterruptedException e) {
   
            Thread.currentThread().interrupt();
            throw new RuntimeException("线程被中断", e);
        } finally {
   
            // 4. 释放锁:必须在finally中执行,避免死锁
            if (lock.isHeldByCurrentThread()) {
   
                lock.unlock();
                System.out.println("释放锁成功");
            }
        }
    }
}

关键方法说明

  • lock():无参获取锁,默认过期时间 30 秒,看门狗(Watch Dog) 会自动续期,直到锁被释放。
  • lock(long leaseTime, TimeUnit unit):指定锁的过期时间,此时看门狗失效,锁到期后自动释放。
  • tryLock(long waitTime, long leaseTime, TimeUnit unit):尝试获取锁,最多等待waitTime,获取成功后锁有效期为leaseTime,超时未获取则返回false
  • unlock():释放锁,必须由持有锁的线程调用,否则会抛出IllegalMonitorStateException
  • isHeldByCurrentThread():判断当前线程是否持有该锁,避免误释放其他线程的锁。

2.2 公平锁(RFairLock)

公平锁保证多个线程按请求顺序获取锁,避免 “饥饿” 问题。Redisson 的RFairLock实现了公平锁机制,使用方式与RLock一致,仅需通过getFairLock获取锁实例:

public void fairLockDemo() {
   
    // 获取公平锁实例
    RLock fairLock = redissonClient.getFairLock("distributed:fair:lock");

    try {
   
        fairLock.lock(10, TimeUnit.SECONDS);
        System.out.println("公平锁获取成功,执行业务...");
    } finally {
   
        if (fairLock.isHeldByCurrentThread()) {
   
            fairLock.unlock();
        }
    }
}

2.3 读写锁(RReadWriteLock)

读写锁适用于读多写少的场景,遵循 “读共享、写独占” 原则:

  • 读锁:多个线程可同时获取,写锁被阻塞。
  • 写锁:仅一个线程可获取,读锁和其他写锁均被阻塞。

使用示例:

public void readWriteLockDemo() {
   
    // 获取读写锁实例
    RReadWriteLock rwLock = redissonClient.getReadWriteLock("distributed:rw:lock");
    RLock readLock = rwLock.readLock();
    RLock writeLock = rwLock.writeLock();

    // 读锁使用
    try {
   
        readLock.lock(5, TimeUnit.SECONDS);
        System.out.println("读锁获取成功,执行读操作...");
    } finally {
   
        if (readLock.isHeldByCurrentThread()) {
   
            readLock.unlock();
        }
    }

    // 写锁使用
    try {
   
        writeLock.lock(5, TimeUnit.SECONDS);
        System.out.println("写锁获取成功,执行写操作...");
    } finally {
   
        if (writeLock.isHeldByCurrentThread()) {
   
            writeLock.unlock();
        }
    }
}

2.4 红锁(RedissonRedLock)

红锁适用于 Redis 集群主从切换的场景,解决单主节点宕机导致的锁失效问题。红锁通过同时向多个独立的 Redis 节点(至少 3 个)申请锁,只有超过半数节点获取成功,才认为锁获取成功。

使用示例:

public void redLockDemo() {
   
    // 连接多个独立的Redis节点
    RLock lock1 = redissonClient.getLock("redlock:1");
    RLock lock2 = redissonClient.getLock("redlock:2");
    RLock lock3 = redissonClient.getLock("redlock:3");

    // 创建红锁实例
    RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

    try {
   
        boolean isLocked = redLock.tryLock(3, 10, TimeUnit.SECONDS);
        if (isLocked) {
   
            System.out.println("红锁获取成功,执行核心业务...");
        }
    } catch (InterruptedException e) {
   
        Thread.currentThread().interrupt();
    } finally {
   
        if (redLock.isHeldByCurrentThread()) {
   
            redLock.unlock();
        }
    }
}

三、RLock 核心源码解析

Redisson 的分布式锁核心是RLock,其底层基于 Redis 的Lua 脚本保证操作的原子性,通过Hash 结构实现可重入,通过定时任务实现看门狗续期。本节将从lock()方法入手,深入剖析RLock的实现原理。

3.1 RLock 的核心实现类

Redisson 的RLock接口由RedissonLock类实现,该类继承自RedissonBaseLock,核心依赖:

  • commandExecutor:Redis 命令执行器,负责发送 Lua 脚本和 Redis 命令。
  • id:当前 Redisson 客户端的唯一标识(格式:客户端ID:线程ID),用于区分不同线程。
  • internalLockLeaseTime:锁的默认过期时间(30 秒),即看门狗的基础续期时间。

3.2 获取锁的核心流程(lock () 方法)

调用lock()方法时,最终会执行RedissonLocklock(long leaseTime, TimeUnit unit, boolean interruptibly)方法,核心逻辑如下:

步骤 1:入口方法

@Override
public void lock(long leaseTime, TimeUnit unit) {
   
    try {
   
        lock(leaseTime, unit, false);
    } catch (InterruptedException e) {
   
        Thread.currentThread().interrupt();
        return;
    }
}

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
   
    long threadId = Thread.currentThread().getId();
    // 1. 尝试获取锁
    Long ttl = tryAcquire(leaseTime, unit, threadId);
    // 2. 获取成功:直接返回
    if (ttl == null) {
   
        return;
    }
    // 3. 获取失败:订阅锁释放事件,阻塞等待
    RFuture<RedissonLockEntry> future = subscribe(threadId);
    if (interruptibly) {
   
        commandExecutor.syncSubscriptionInterrupted(future);
    } else {
   
        commandExecutor.syncSubscription(future);
    }

    try {
   
        // 4. 循环重试获取锁
        while (true) {
   
            ttl = tryAcquire(leaseTime, unit, threadId);
            if (ttl == null) {
   
                break;
            }
            // 5. 等待锁释放(根据ttl休眠)
            if (ttl > 0) {
   
                try {
   
                    future.getNow().getLatch().await(ttl, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
   
                    if (interruptibly) {
   
                        throw e;
                    }
                    Thread.currentThread().interrupt();
                }
            } else {
   
                if (interruptibly) {
   
                    LockSupport.parkNanos(100000);
                } else {
   
                    LockSupport.parkNanos(100000);
                }
            }
        }
    } finally {
   
        // 6. 取消订阅
        unsubscribe(future, threadId);
    }
}

步骤 2:尝试获取锁(tryAcquire 方法)

tryAcquire是获取锁的核心方法,返回锁的剩余过期时间(null表示获取成功),其内部调用tryAcquireAsync实现异步获取:

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
   
    // 1. 指定了过期时间:直接获取锁
    if (leaseTime != -1) {
   
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    // 2. 未指定过期时间:启动看门狗,默认30秒过期
    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
   
        if (e != null) {
   
            return;
        }
        // 3. 获取锁成功,启动看门狗续期
        if (ttlRemaining == null) {
   
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

步骤 3:Lua 脚本实现原子性(tryLockInnerAsync 方法)

tryLockInnerAsync是获取锁的最终实现,通过Lua 脚本保证 “判断锁是否存在→设置锁→设置过期时间” 的原子性:

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisCommand<T> command) {
   
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
            // Lua脚本核心逻辑
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('hincrby', 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(getName()), // KEYS[1]:锁名称
            internalLockLeaseTime, // ARGV[1]:锁过期时间(毫秒)
            getLockName(threadId) // ARGV[2]:线程标识(客户端ID:线程ID)
    );
}

Lua 脚本逻辑解析

  1. 若锁(KEYS [1])不存在:通过hincrby创建 Hash 结构的锁,field为线程标识,value为 1(重入次数),并设置过期时间,返回nil(表示获取成功)。
  2. 若锁已存在且当前线程持有该锁:将重入次数 + 1,重置过期时间,返回nil(表示重入成功)。
  3. 若锁已存在且被其他线程持有:返回锁的剩余过期时间(pttl),表示获取失败。

可重入实现原理:通过 Redis 的 Hash 结构存储锁,field为线程标识,value为重入次数,同一线程再次获取锁时仅需递增value

步骤 4:看门狗续期(scheduleExpirationRenewal 方法)

当未指定锁的过期时间时,Redisson 会启动看门狗定时任务,每隔internalLockLeaseTime / 3(10 秒)续期一次,将锁的过期时间重置为 30 秒:

private void scheduleExpirationRenewal(long threadId) {
   
    ExpirationEntry entry = new ExpirationEntry();
    ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
    if (oldEntry != null) {
   
        oldEntry.addThreadId(threadId);
    } else {
   
        entry.addThreadId(threadId);
        // 启动续期任务
        renewExpiration();
    }
}

private void renewExpiration() {
   
    ExpirationEntry entry = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (entry == null) {
   
        return;
    }
    // 定时任务:每隔10秒执行一次
    Timeout task = commandExecutor.getConnectionManager().newTimeout(timerTask -> {
   
        ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ent == null) {
   
            return;
        }
        Long threadId = ent.getFirstThreadId();
        if (threadId == null) {
   
            return;
        }
        // 执行续期Lua脚本
        RFuture<Boolean> future = renewExpirationAsync(threadId);
        future.onComplete((res, e) -> {
   
            if (e != null) {
   
                log.error("Can't update lock " + getName() + " expiration", e);
                return;
            }
            if (res) {
   
                // 续期成功,递归调用自身,继续续期
                renewExpiration();
            }
        });
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
    entry.setTimeout(task);
}

看门狗续期的 Lua 脚本(renewExpirationAsync 方法):

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

仅当当前线程持有锁时,才会重置过期时间,避免为其他线程的锁续期。

3.3 释放锁的核心流程(unlock () 方法)

释放锁的核心是unlockAsync方法,同样通过 Lua 脚本保证原子性,逻辑如下:

@Override
public RFuture<Void> unlockAsync(long threadId) {
   
    RPromise<Void> result = new RedissonPromise<>();
    // 执行释放锁的Lua脚本
    RFuture<Boolean> future = unlockInnerAsync(threadId);

    future.onComplete((opStatus, e) -> {
   
        // 取消看门狗续期任务
        cancelExpirationRenewal(threadId);

        if (e != null) {
   
            result.tryFailure(e);
            return;
        }
        // 锁未被当前线程持有,抛出异常
        if (opStatus == null) {
   
            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                    + id + " thread-id: " + threadId);
            result.tryFailure(cause);
            return;
        }
        result.trySuccess(null);
    });
    return result;
}

释放锁的 Lua 脚本(unlockInnerAsync 方法):

"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;"

Lua 脚本逻辑解析

  1. 若当前线程未持有锁,返回nil,触发IllegalMonitorStateException
  2. 若当前线程持有锁,将重入次数 - 1。
  3. 若重入次数 > 0:重置过期时间,返回 0(表示重入锁未完全释放)。
  4. 若重入次数 = 0:删除锁,发布锁释放事件,返回 1(表示锁完全释放)。

四、Redisson 分布式锁最佳实践

  1. 必须在 finally 中释放锁:避免业务异常导致锁无法释放,引发死锁。
  2. 避免手动指定过期时间过短:若业务耗时超过过期时间,锁会提前释放,导致并发问题;建议使用无参lock(),依赖看门狗自动续期。
  3. 禁止跨线程释放锁:仅能由持有锁的线程调用unlock(),可通过isHeldByCurrentThread()做前置判断。
  4. 合理选择锁类型:读多写少用读写锁,避免饥饿用公平锁,集群场景用红锁。
  5. 设置锁的等待时间:使用tryLock指定等待时间,避免线程无限阻塞。

五、总结

Redisson 作为 Redis 的高级 Java 客户端,将分布式锁的实现封装得极为优雅,解决了原生 Redis 分布式锁的原子性、重入性、超时续期等痛点。其核心RLock通过 Lua 脚本保证操作原子性,通过 Hash 结构实现可重入,通过看门狗机制解决锁超时问题,通过订阅 / 发布机制实现锁的阻塞等待。

在实际开发中,我们无需重复造轮子,只需根据业务场景选择合适的锁类型,遵循最佳实践即可。而深入理解其底层源码,能帮助我们更好地排查问题、优化性能,让分布式锁的使用更安全、更高效。

目录
相关文章
|
1天前
|
云安全 人工智能 自然语言处理
AI说的每一句话,都靠谱吗?
阿里云提供AI全栈安全能力,其中针对AI输入与输出环节的安全合规挑战,我们构建了“开箱即用”与“按需增强”相结合的多层次、可配置的内容安全机制。
|
5天前
|
存储 人工智能 安全
AI 越智能,数据越危险?
阿里云提供AI全栈安全能力,为客户构建全链路数据保护体系,让企业敢用、能用、放心用
|
8天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
2天前
|
消息中间件 安全 NoSQL
阿里云通过中国信通院首批安全可信中间件评估
近日,由中国信通院主办的 2025(第五届)数字化转型发展大会在京举行。会上,“阿里云应用服务器软件 AliEE”、“消息队列软件 RocketMQ”、“云数据库 Tair”三款产品成功通过中国信通院“安全可信中间件”系列评估,成为首批获此认证的中间件产品。此次评估覆盖安全可信要求、功能完备性、安全防护能力、性能表现、可靠性与可维护性等核心指标,标志着阿里云中间件产品在多架构适配与安全能力上达到行业领先水平。
296 192
|
2天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
311 164
|
1天前
|
开发者
「玩透ESA」ESA启用和加速-ER在加速场景中的应用
本文介绍三种配置方法:通过“A鉴权”模板创建函数并设置触发器路由;在ESA上配置回源302跟随;以及自定义响应头。每步均配有详细截图指引,帮助开发者快速完成相关功能设置,提升服务安全性与灵活性。
291 2
|
7天前
|
数据采集 人工智能 自然语言处理
3分钟采集134篇AI文章!深度解析如何通过云无影AgentBay实现25倍并发 + LlamaIndex智能推荐
结合阿里云无影 AgentBay 云端并发采集与 LlamaIndex 智能分析,3分钟高效抓取134篇 AI Agent 文章,实现 AI 推荐、智能问答与知识沉淀,打造从数据获取到价值提炼的完整闭环。
453 93

热门文章

最新文章