Yang's blog Yang's blog
首页
后端开发
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xiaoyang

尽人事,听天命
首页
后端开发
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • SpringCloud

    • 微服务架构介绍
    • SpringCloud介绍
    • Spring Cloud:生产者与消费者
    • Spring Cloud Eureka:构建可靠的服务注册与发现
    • Spring Cloud Ribbon:负载均衡
    • Spring Cloud Fegin:服务调用
    • Spring Cloud Hystrix:熔断器
    • Spring Cloud Zuul:统一网关路由
    • Spring Cloud Config:配置中心
  • Java后端框架

    • LangChain4j

      • 介绍
      • 快速开始
      • Chat and Language Models
      • Chat Memory
      • Model Parameters
      • Response Streaming
      • AI Services
      • Agent
      • Tools (Function Calling)
      • RAG
      • Structured Outputs
      • Classification
      • Embedding (Vector) Stores
      • Image Models
      • Quarkus Integration
      • Spring Boot Integration
      • Kotlin Support
      • Logging
      • Observability
      • Testing and Evaluation
      • Model Context Protocol
  • 八股文

    • 操作系统
    • JVM介绍
    • Java多线程
    • Java集合框架
    • Java反射
    • JavaIO
    • Mybatis介绍
    • Spring介绍
    • SpringBoot介绍
    • Mysql
    • Redis
    • 数据结构
    • 云计算
    • 设计模式
    • 计算机网络
    • 锁核心类AQS
    • Nginx
    • 面试场景题
  • 前端技术

    • 初识Vue3
    • Vue3数据双向绑定
    • Vue3生命周期
    • Vue-Router 组件
    • Pinia 集中式状态存储
  • 中间件

    • RocketMQ
  • 源码分析

    • 线程池源码解析
    • Redisson源码分析
      • 一、分布式锁——为什么、什么场景、面试常问点
        • 为什么要分布式锁
        • 面试可能问的点
      • 二、Redis 实现分布式锁的基本思路
        • 基本思路
        • 需要考虑的问题
      • 三、Redisson 的角色 + 支持特性(为什么生产中常用)
        • Redisson 是什么
        • 为什么常用
      • 四、Redisson 中分布式锁的源码分析
        • 4.1 客户端/线程唯一标识设计
        • 4.2 加锁(获取锁)
        • 概述
        • 核心 Lua 脚本
        • 为什么用 hash 而不是简单的 string
        • 默认 leaseTime 和续租机制
        • 获取锁失败/等待逻辑
        • 4.3 可重入机制
        • 4.4 Watch-dog 机制(自动续租)
        • 为什么需要续租?
        • 源码关键流程
        • 面试可问点
        • 4.5 解锁机制
        • 概述
        • 核心 Lua 脚本
        • 解锁后续逻辑
        • 面试可问点
        • 4.6 判断锁持有情况/其他操作
        • 4.7 小结表格:Redisson 锁各模块对应机制
      • 五、设计权衡 & 注意事项
        • 5.1 锁超时、续租、死锁风险
        • 5.2 多实例 Redis 模式、容错、可用性
        • 5.3 锁粒度、性能、误用风险
        • 5.4 锁的安全性 vs 可用性权衡
        • 5.5 版本兼容、线程/客户端释放差异、误用场景
      • 六、面试重点问答+常见误区
        • 问答集
  • 开发知识

    • 请求参数注解
    • 时间复杂度和空间复杂度
    • JSON序列化与反序列化
    • Timestamp vs Datetime
    • Java开发中必备能力单元测试
    • 正向代理和反向代理
    • 什么是VPN
    • 后端服务端主动推送消息的常见方式
    • 正则表达式
    • SseEmitter vs Flux 的本质区别与底层原理解析
    • Function Calling与MCP区别
    • 分布式事务
  • 后端开发
  • 源码分析
xiaoyang
2025-11-13
目录

Redisson源码分析

# Redisson分布式锁源码分析

# 一、分布式锁——为什么、什么场景、面试常问点

# 为什么要分布式锁

在传统单机、多线程环境中,我们常用 synchronized、ReentrantLock 等来保证同一 JVM 内线程之间互斥。但在微服务或者分布式环境下:

  • 多个服务实例(不同进程/机器)同时访问共享资源(数据库记录、缓存 key、外部接口等)
  • 若无协调机制,可能出现竞态条件、超卖、重复 执行、数据不一致等问题。 (Medium (opens new window))
  • 于是就出现“分布式锁”——一种跨进程/跨机器的互斥机制:确保某一资源在任意时刻只有一个客户端/线程在操作。

# 面试可能问的点

  • 什么是分布式锁?什么时候用?
  • 分布式锁与数据库悲观锁/乐观锁、单机锁区别?
  • 实现分布式锁有哪些方式(如 Zookeeper、etcd、Redis、数据库、消息队列)?
  • Redis 实现分布式锁时需要考虑哪些问题?(原子性、超时、节点失败、释放锁、续租等)
  • 有哪些经典算法?例如 Redlock 算法。 (SoByte (opens new window))
  • 在实际生产中,使用哪种客户端库(如 Redisson)?其原理如何?
  • 在源码层面:加锁、解锁可重入、超时机制、续租机制、失败恢复机制、线程安全、错误使用该怎么办?
  • 锁的粒度、性能、死锁可能、续租失败、业务误用 etc.

# 二、Redis 实现分布式锁的基本思路

为了让后续理解 Redisson 源码有基础,先从更基础的 Redis 分布式锁思路讲起。

# 基本思路

  • 使用 Redis 作为“锁存储中心”(键-值存储)。

  • 客户端尝试“加锁”时:往 Redis 写入一个 lock key(资源标识),并设置一个 唯一标识(例如 UUID + 线程 ID)作为 value,同时设置一个 TTL(租期),保证即使客户端 crash 也不会导致锁一直不能释放。

  • 加锁操作必须是原子性的:典型用法 SET key value NX PX ttl。

  • 客户端释放锁时,只有拥有该锁的客户端才能删除这个 key(防止释放别人的锁)——常见做法是:

    if redis.call("get",KEYS[1]) == ARGV[1] then
       return redis.call("del",KEYS[1])
    else
       return 0
    end
    
    1
    2
    3
    4
    5
  • TTL 到了锁自动释放,从而防止死锁。

  • 如果要更强的容错(多 Redis 节点),可以采用 Redlock 算法:在 N 个独立 Redis 实例上尝试加锁,然后获得 > N/2 实例的锁才算成功。 (Medium (opens new window))

# 需要考虑的问题

  • 原子性:加锁 + 设置 TTL 要么都成功,要么失败。Lua 脚本或 Redis 自身 SET NX PX 支持。
  • 客户端持有锁后超时被自动释放,这可能导致持锁客户端还在执行但锁被释放,造成其他客户端进入关键区——潜在风险。
  • 解锁必须保证“是锁的持有者才能解锁”。
  • 重入锁:同一个客户端/线程多次加锁如何实现?
  • 自动续约(Watch-dog):如果业务执行很久怎么办?不能仅靠 TTL。
  • 多节点应用/Redis 多实例模式的可用性、分区容错。

# 三、Redisson 的角色 + 支持特性(为什么生产中常用)

# Redisson 是什么

  • Redisson 是一个基于 Redis 的 Java 客户端(和扩展库),提供了不仅仅是基本的 Redis 操作,还封装了许多分布式对象 & 服务(如 RLock、RReadWriteLock、RSemaphore、RCountDownLatch、RMap 等) 。
  • 在生产系统中,中小型互联网公司常用 Redisson 来实现分布式锁、限流、分布式集合/队列等。
  • 它支持多种 Redis 模式:单实例、哨兵(Sentinel)、主从、Redis Cluster。你的参考中也指出:Redisson 支持 redis 单实例、哨兵、master-slave、cluster。

# 为什么常用

  • 较为成熟、社区活跃、使用方便。
  • 对锁机制封装良好(支持同步和异步 API、可重入锁、自动续租、超时机制)——你示例 lock.tryLock(100,10,TimeUnit.SECONDS) 就是典型。
  • 在面试题中,讲 “基于 Redis 的分布式锁 + Redisson 源码” 是比较常见且有技术深度的点。
  • Redisson 内部对 Redis 操作、Lua 脚本、线程唯一标识、续租机制、锁释放机制实现较好,可以作为源码分析的好范例。

# 四、Redisson 中分布式锁的源码分析

下面重点从源码层面分析 Redisson 的分布式锁实现机制。为了便于理解,我会分成几个模块:加锁(获取锁)、可重入机制、续租(watch dog)机制、解锁、判断锁持有情况、客户端/线程标识设计。每个模块,我先描述设计目的,再讲源码关键点/Lua 脚本解释。


# 4.1 客户端/线程唯一标识设计

在分布式锁中,必须区分“哪个客户端(甚至哪个线程)”持有锁。Redisson 的实现如下:

  • 在 RedissonLock 构造函数中:

    this.id = commandExecutor.getConnectionManager().getId();
    this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
    this.entryName = id + ":" + name;
    
    1
    2
    3

    你的参考已给出。

  • getLockName(threadId) = id + ":" + threadId。也就是说,锁 key 对应的 Hash 结构里面,field 是 “客户端 UUID:线程Id”。

  • 这样,客户端在多个线程中使用锁时,每条线程也有唯一标识;不同客户端也不冲突。

这个设计使得:

  • 支持可重入:同一个客户端线程再次加锁,可以检测 hexists(KEY, thisThreadField)。
  • 解锁必须是原持有线程才能解锁(否则 IllegalMonitorStateException)。你参考里也提到了 stackoverflow 上的问题。 (Stack Overflow (opens new window))

理解这一点是深入源码的基础。


# 4.2 加锁(获取锁)

# 概述

当调用 lock()、tryLock() 等 API 时,底层调用的是 tryLockInnerAsync(...) 方法(对应你的参考中代码段)。其主要流程:

  1. 构造 Lua 脚本并通过 evalWriteAsync() 执行(保证原子性)。
  2. 脚本逻辑:如果 key 不存在,则 set hset + pexpire;如果 hash 中已有当前 thread’s field,则为可重入,hincrby + pexpire;否则返回当前 key 的剩余 TTL(用作返回值用于做等待/重试逻辑) 。
  3. 如果用户指定 leaseTime(不是 −1 表示默认方式),则用该 lease;否则使用 default internalLockLeaseTime(例如 30 000 毫秒)并触发续租(watch­dog)机制。

# 核心 Lua 脚本

如下(正如你参考中所展示):

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]);
1
2
3
4
5
6
7
8
9
10
11

解读:

  • KEYS[1] = lockName (即你传入的 name)
  • ARGV[1] = leaseTime (ms)
  • ARGV[2] = lock field = clientId:threadId
  • 第一分支:如果 key 不存在,则表示锁尚未被任何客户端持有 → 创建 hash,field=当前线程,value=1(表示锁计数(可重入计数)) + 设置 TTL。返回 nil 表示成功。
  • 第二分支:如果该 key 已存在,而且当前线程(field) 已经在 hash 中(即就是重入) → 将 value++,并 pexpire TTL。返回 nil 表示成功。
  • 否则:锁已经被别的线程/客户端持有,返回 pttl(key) 表示剩余过期时间。客户端可以据此决定等待重试。

# 为什么用 hash 而不是简单的 string

  • 使用 hash 的好处在于:可重入计数(value 存持有次数) + 支持多个客户端 field ?(不过实际逻辑里并允许多个客户端持有)
  • 若用 String,重入时无法区分线程是否是自己、无法计数。
  • Redisson 这种设计结合线程 ID 使得「同一线程重入」成为可能。

# 默认 leaseTime 和续租机制

  • 如果用户调用 lock()(无 leaseTime 参数)或指定 leaseTime = −1,则 Redisson 使用默认 internalLockLeaseTime(通常 30 秒)并在获取锁成功后启动“续租线程”(watch-dog),以防业务执行时间超出 TTL。你的参考里也提到了。
  • 如果用户调用 tryLock(waitTime, leaseTime, unit) 并指定 leaseTime > 0,则不会启动续租机制,而是直接按 leaseTime 到期释放。

# 获取锁失败/等待逻辑

  • 如果脚本返回一个长期的 TTL (即别人持锁),客户端会判断等待时间、重试逻辑(比如 spin/wait 内容)——源码中 lock(long leaseTime, TimeUnit unit, boolean interruptibly) 里有等待逻辑。你可以在资料中找到。 (devpress.csdn.net (opens new window))
  • 在阻塞获取环境情况下(比如 lock()),Redisson 会循环尝试获取锁,在失败时订阅 channel 等待通知(见下一节)。

# 4.3 可重入机制

如上脚本所示,第二分支 hexists(KEY, ARGV[2]) == 1就是判断当前线程已经是锁的持有者(field 存在)→ 那就执行 hincrby(...,1) 计数++,然后重设 TTL。这样就实现了可重入。
对应面试问点:为什么要可重入?例如在同一线程内部调用 lock() 后,再调用其他锁保护的方法、递归调用等场景。

此外,释放时也会根据计数值决定是否真正释放(见解锁部分)。


# 4.4 Watch-dog 机制(自动续租)

# 为什么需要续租?

如果锁 TTL 设置为固定,如 30 秒,但业务执行可能超过 30 秒,那么就有两种潜在风险:

  • 锁过期释放后,另一个客户端获得锁,之前的持有线程还在执行,引起并发错误。
  • 若 TTL 太长,则持锁线程 crash/机器宕机后,锁长时间不能释放 → 资源被锁定。

Redisson 采用“看门狗”机制:如果用户没有显式指定 leaseTime(默认机制),Redisson 会在后台每隔 internalLockLeaseTime/3 时间执行一次续租(即延长 TTL)直到释放。你的参考也提到了。

# 源码关键流程

在 tryAcquireAsync(...) 中:

if (leaseTime == -1) {
    // 使用默认 TTL,并执行 tryLockInnerAsync(...), 然后
    ttlRemainingFuture.addListener(new FutureListener<Long>() {
        @Override
        public void operationComplete(Future<Long> future) throws Exception {
            if (!future.isSuccess()) {
                return;
            }
            Long ttlRemaining = future.getNow();
            // lock acquired if ttlRemaining == null
            if (ttlRemaining == null) {
                scheduleExpirationRenewal(threadId);
            }
        }
    });
    return ttlRemainingFuture;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

scheduleExpirationRenewal(threadId) 方法会:

  • 检查是否已有续租任务(expirationRenewalMap 用于标记)

  • 利用 commandExecutor.getConnectionManager().newTimeout(...) 创建定时任务,周期约为 internalLockLeaseTime/3。

  • 在任务执行时调用 renewExpirationAsync(threadId),其 Lua 脚本逻辑如下:

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

    即:如果 hash 中当前线程 field 存在,则延长 TTL 为 ARGV[1]=internalLockLeaseTime (ms),返回 true。否则返回 false。

  • 如果续租失败(或当前线程 field 不存在),则取消任务。

这样就让锁在业务执行期间一直保持,不会因为 TTL 到期而被自动释放。值得注意的是,这依赖于客户端所在 JVM/线程存活且续租任务正常执行。如果客户端挂了或网络中断,则续租无法执行,锁会在原 TTL 到期后自动释放,避免死锁。

# 面试可问点

  • “Watch-dog”机制如何实现?
  • 当客户端宕机/网络断开/线程被阻塞超过续租周期,会出现怎样的问题?
  • 为什么设定为 internalLockLeaseTime/3 作为续租周期?(保守续租多于 TTL)
  • 是否会导致锁的无限延续?(是的,但前提客户端还在)
  • 是否有续租失败导致锁释放但业务还在做的问题?(有可能,要在业务中做好超时保护)

# 4.5 解锁机制

# 概述

当持有线程完成业务后,需要调用 unlock() 释放锁。Redisson 的解锁逻辑也较为严谨,主要包括以下几个方面:

  • 只有锁的持有线程才能解锁,否则抛出 IllegalMonitorStateException。
  • 对于重入锁,如果计数 > 1,则只是将计数减 1,并重设 TTL;如果计数 == 1,则删除 key,并通知等待者。
  • 发布解锁通知(利用 Redis 的 Pub/Sub 机制)以便等待锁的线程可以被唤醒。
  • 取消续租任务。

# 核心 Lua 脚本

如下:

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

对应参数说明:

  • KEYS[1] = lockName
  • KEYS[2] = channelName(用于发布解锁事件)
  • ARGV[1] = unlockMessage
  • ARGV[2] = internalLockLeaseTime
  • ARGV[3] = lock field = clientId:threadId

解读:

  1. 如果 key 不存在(可能超时被删) → 发布解锁通知(防止别人一直等待) → 返回 1。
  2. 如果 hash 中不存在当前线程的 field(不是当前线程持有) → 返回 nil(上层会抛异常:非法释放锁)。
  3. 否则,执行 hincrby(field, -1),把计数-1。
    • 如果计数 > 0(表示还有重入层数未释放) → 重新设置 TTL → 返回 0(表示锁未完全释放,只减少一次层数)。
    • 否则(counter ≤ 0)→ 删除 key → 发布解锁通知 → 返回 1(完全释放)。
  4. 返回 nil 时上层会触发 IllegalMonitorStateException。

# 解锁后续逻辑

  • 上层 unlockAsync(threadId) 会拿到上述脚本的返回结果,并在回调中调用 cancelExpirationRenewal(threadId) 取消续租任务。
  • 若发布了解锁通知,等待该锁的线程会通过订阅 channel 被唤醒,从而减少自旋/轮询开销。

# 面试可问点

  • 为什么要先判断 owner 才允许解锁?
  • 为什么要发布通知?(减少等待开销)
  • 重入锁的释放为什么做计数减法?
  • 如果持有线程宕机/未释放锁怎么办?(TTL 自动释放 + watch-dog 机制)
  • 是否存在不能释放锁或别人错误释放锁的风险?(要看“线程唯一标识”设计)

# 4.6 判断锁持有情况/其他操作

Redisson 还提供判断当前线程是否持有锁、是否被锁住、尝试获取锁等操作。
例如:

@Override
public boolean isHeldByCurrentThread() {
    return isHeldByThread(Thread.currentThread().getId());
}

@Override
public boolean isHeldByThread(long threadId) {
    final RFuture<Boolean> future = commandExecutor
        .writeAsync(getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, getName(), getLockName(threadId));
    return get(future);
}
1
2
3
4
5
6
7
8
9
10
11

即:查 hash 内是否存在 clientId:threadId 这种 field。
这样的判断在面试中也可能被问到,比如“如何判断当前线程是否持有锁?”、“为什么不能用 isLocked() 来判断自己持有锁?”(比如你的参考中提到一个 GitHub issue:使用 isLocked() 错误释放锁会出问题)。 (GitHub (opens new window))


# 4.7 小结表格:Redisson 锁各模块对应机制

模块 关键机制 源码/脚本点
客户端标识 clientId = UUID + threadId id = ... getId() + getLockName(threadId)
加锁 Lua 脚本:不存在→创建;已持有→重入;否则返回 TTL tryLockInnerAsync(...)
可重入 hash field value++ + 重设 TTL Lua 第二分支
默认 lease + 续租 若用户无 leaseTime 则启动 watch-dog 机制 scheduleExpirationRenewal(threadId)
解锁 Lua 脚本:判断 owner,计数>1则减一重设 TTL,否则删 key + 发布通知 unlockInnerAsync(long threadId)
判断持有 HEXISTS hash field isHeldByThread(threadId)

# 五、设计权衡 & 注意事项

在面试中,除了讲清楚机制,还经常会问“在实际系统中怎么办”“有哪些坑”“为什么要这么设计”这类问题。下面整理一些常见的设计权衡和使用时的注意事项。

# 5.1 锁超时、续租、死锁风险

  • 如果 TTL 太短而业务执行时间较长,可能导致锁自动释放但持锁线程还在执行 → 竞态。
  • 如果 TTL 太长且没有续租机制或客户端 crash,则可能造成锁“卡住”资源。
  • Redisson 的 watch-dog 机制是折中方案:给一个合理默认 TTL(30 秒),并且在业务执行中自动续租。前提是客户端存活且续租任务能跑。
  • 但如果客户端所在 JVM 卡死/线程饥饿/网络中断,续租失败,锁最终还是会被释放(通过 TTL)——这是设计保证。
  • 因此,业务层也应做好 “锁获取失败/超时” 的处理逻辑。

# 5.2 多实例 Redis 模式、容错、可用性

  • 如果只是单 Redis 实例,虽然实现容易,但存在单点故障风险。
  • Redisson 支持:单实例、Sentinel、主从、Cluster。你面临生产环境时要考虑 Redis 节点的高可用性。
  • 如果使用多个独立 Redis 实例来采用 Redlock 算法,需要满足“多数节点”获得锁才成功。注意这是一个更复杂、更强容错但也更有挑战的模型。 (SoByte (opens new window))
  • 在 Redis Cluster 模式下,由于 key 哈希槽分布,不同节点可能存储不同 key,Redisson 内部会根据 key 决定执行在哪个节点。你面试时也可能被问 “Redis Cluster 下锁机制是怎样的?”
  • 网络分区、节点故障、延迟不一致、复制滞后——这些都可能影响锁安全。Redisson 虽然封装了很多机制,但并不能完全避免所有风险。

# 5.3 锁粒度、性能、误用风险

  • 锁不要粒度太粗:锁保护的业务逻辑越大/执行越慢,持锁时间越长,性能瓶颈越大。
  • 避免锁里执行大量阻塞/IO 操作;尽量在锁里做少量快速操作。
  • 重入锁虽然方便,但也可能隐藏问题(比如不小心重复加锁而忘了解锁)。
  • 解锁必须在 finally 块中执行,否则可能锁无法释放。Redisson 的 API 也推荐如此。
  • 使用 tryLock(timeout, leaseTime) 时,要设置合理的等待时间及租期。
  • 使用 isHeldByCurrentThread() 判断再释放比 isLocked() 更准确(在多人环境中)——你的参考中已提到。

# 5.4 锁的安全性 vs 可用性权衡

  • 安全性(仅一个客户端持锁、释放正确) vs 可用性(即使节点失败也能恢复)之间存在权衡。
  • 单实例 Redis 模式 + TTL 是可用但安全性弱(节点故障可能导致锁“丢失”或“被误持”)。
  • Redlock 算法增强安全性但更复杂、延迟更高。
  • 业务场景不同:如果不是极关键资源,有时简单方案足够;若强一致要求高,则可能选择 Zookeeper、etcd 之类。

# 5.5 版本兼容、线程/客户端释放差异、误用场景

  • 注意不同版本 Redisson 的行为可能略有差异。
  • 不要跨线程/跨客户端释放锁(会抛 IllegalMonitorStateException)——如 StackOverflow 问题所示。 (Stack Overflow (opens new window))
  • 如果使用异步任务/线程池,要确认释放锁的线程是持锁线程或使用 Redisson 提供的线程Id 参数方法。
  • 在高并发环境,要监控锁失败、超时、重试、等待队列情况。

# 六、面试重点问答+常见误区

# 问答集

Q1:为什么用 Redis 实现分布式锁?优点有哪些?
A:Redis 是内存数据库,响应快、支持 TTL、支持原子操作(如 Lua 脚本、SET NX PX)、部署方便,适合作为分布式锁的协调中心。 (Developer Playground | Giri's Place (opens new window))

Q2:Redis 分布式锁需要考虑哪些场景?
A:主要包括原子性、TTL 超时、锁释放安全(持有者才能释放)、客户端宕机/崩溃、网络分区、多 Redis 实例容错、锁撤销/续租机制。

Q3:Redisson 的可重入锁是怎么实现的?
A:Redisson 用 hash 类型存储锁状态:key = lockName,field = clientId:threadId,value = 重入计数。Lua 脚本中如果 hexists(KEY, field)==1 则认为当前线程已持锁,做 hincrby(field,1) 并重设 TTL。你的参考中也有这部分。

Q4:什么是 Watch-dog 机制?为什么要有?
A:当业务执行可能超过锁初始 TTL 时,如果没有续租,锁会被自动释放,但持锁线程可能还在执行,造成并发问题。Watch-dog 机制就是 Redisson 在获取锁成功后、若未指定 leaseTime,则会定时续租(延长 TTL)直到释放。这样减少因业务超时导致锁提前释放的问题。

Q5:解锁时,为什么要判断是否是当前线程持有?
A:如果任意线程都可以释放锁,那么就可能 A 加锁后 B 释放锁,A 还在执行,C 又获得锁 → 导致并发错误。Redisson 将 “客户端 ID + 线程 ID” 作为唯一标识,仅持有线程才能解锁。否则抛 IllegalMonitorStateException。

Q6:面试常见误区?

  • 误区:用 DEL key 简单删除锁。问题是:可能删除了别人的锁,因为没有判断持有者。
  • 误区:TTL 很长就万无一失。其实客户端 crash 或网络分区仍可能导致锁无法释放或误释放。
  • 误区:只用 Redis Cluster 模式就等同于 Redlock。实际上,Redis Cluster 是为了分片和可用性,不等同于 Redlock 提出的 “多个独立 Redis 实例 + 多数原则”模型。 (Developer Playground | Giri's Place (opens new window))
  • 误区:release 锁可以跨线程、跨客户端。正确是只能由持锁线程释放。
编辑 (opens new window)
上次更新: 2025/11/13, 02:50:51

← 线程池源码解析 请求参数注解→

最近更新
01
线程池源码解析
10-31
02
分布式事务
10-27
03
面试场景题
08-22
更多文章>
Theme by Vdoing | Copyright © 2023-2025 xiaoyang | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式