Redisson 分布式锁底层原理实现详解

devtools/2025/3/4 4:26:14/

1、概述

Redisson 是基于 Redis 实现的分布式锁,其核心思想是利用 Redis 的 SET NX(SET if Not eXists)+ PX(过期时间) 来实现锁的互斥性,同时通过 Lua 脚本 处理加锁、解锁、续期等原子操作,保证分布式环境下的安全性。

2、主要过程

2.1、加锁(tryLock)

定义: 通过 SET NX 实现锁的互斥功能。

  • 命令: SET key value NX PX expireTime
  • 作用: 使用 SETNX 保证互斥性,使用 PX 设置过期时间防止死锁
  • 数据结构:
    • key: lock:{name},如 lock:order:123
    • value: 存储唯一标识(UUID + 线程ID),用于区分不同客户端的锁
  • 特点:
    如果 key 不存在,创建并返回 OK,表示加锁成功。
    如果 key 存在,返回 nil,表示加锁失败。

在这里插入图片描述

2.2、锁续约(Watchdog 机制)

在这里插入图片描述

定义:在tryLock未设置 leaseTime 时候会启动看门狗机制,定期(默认 10s)延长30s锁的过期时间。
实现方式:
Redisson 在获取RFuture时候,如果获取锁成功,就会执行scheduleExpirationRenewal()方法,执行对应的代码

在这里插入图片描述
scheduleExpirationRenewal代码如下:

java">// 续期锁的过期时间
private void renewExpiration() {// 从过期续期映射中获取锁的过期条目ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return; // 如果找不到条目,说明没有锁需要续期}// 创建一个定时任务,用于定期续期锁的过期时间Timeout task = commandExecutor.getServiceManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {// 重新获取过期条目ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return; // 如果条目已被移除,结束任务}// 获取持有锁的线程 IDLong threadId = ent.getFirstThreadId();if (threadId == null) {return; // 如果没有线程 ID,说明没有线程持有该锁,结束任务}// 异步续期锁的过期时间CompletionStage<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) {// 如果续期过程中发生错误,记录日志并移除续期条目log.error("Can't update lock {} expiration", getRawName(), e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// 如果续期成功,重新调度续期任务renewExpiration();} else {// 如果续期失败,取消续期操作cancelExpirationRenewal(null);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 定时任务每 internalLockLeaseTime / 3 毫秒执行一次// 设置定时任务到过期条目中ee.setTimeout(task);
}// 启动续期操作,首次获取锁时会调用此方法
protected void scheduleExpirationRenewal(long threadId) {// 创建新的过期条目ExpirationEntry entry = new ExpirationEntry();// 尝试将新的条目加入到续期映射中ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {// 如果条目已存在,说明已有其他线程在续期,添加当前线程 ID 到条目中oldEntry.addThreadId(threadId);} else {// 如果是首次添加,开始进行续期操作entry.addThreadId(threadId);try {// 启动锁过期续期任务renewExpiration();} finally {// 如果当前线程被中断,取消续期操作if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId);}}}
}

2.3、解锁(unlock)

命令: 通过Lua 脚本确保原子性

2.4、可重入性

实现方式: Redisson 维护一个 可重入计数器**(Hash 结构)**,如果同一线程再次获取锁,计数器递增。
存储结构:
key: lock:{name}
value: UUID:threadId 作为唯一标识
hash 结构(可重入计数器)

HINCRBY lock:{name} UUID:threadId 1

解锁时,只有计数器 == 0,才真正删除 Redis 锁

2.5、失败重试

策略:
线程获取锁失败时,不会立即放弃,而是进入 自旋等待。

主要机制:
在这里插入图片描述

通过计算计算减去锁消耗时间得到的锁最大等待时间,如果 >0 执行对应的订阅功能,订阅其他获取锁的信号,如果其他线程释放锁会发布信号,收到订阅后再执行重试获取锁机制,,等待订阅最大时间是 waitime ,从而不是直接进行循环,导致性能的损耗,最后进行 while(true) 直至获取锁成功为止。


http://www.ppmy.cn/devtools/164363.html

相关文章

qt-C++笔记之QToolButton和QPushButton的区别

qt-C笔记之QToolButton和QPushButton的区别 code review! 文章目录 qt-C笔记之QToolButton和QPushButton的区别1.运行2.main.cpp3.main.pro 1.运行 QToolButton 适用于工具栏或需要较紧凑、图标化显示的场合。通过 setAutoRaise(true) 与 setToolButtonStyle(Qt::ToolButtonTe…

【力扣】堆相关总结

priority_queue std::priority_queue 是 C 标准库中的一个容器适配器&#xff0c;提供了堆&#xff08;Heap&#xff09;数据结构的功能。它通常用于实现优先队列&#xff0c;允许你高效地插入元素和访问最大或最小元素。 头文件 #include <queue> 基本定义 std::pri…

创建一个简单的spring boot+vue前后端分离项目

一、环境准备 此次实验需要的环境&#xff1a; jdk、maven、nvm和node.js 开发工具&#xff1a;idea或者Spring Tool Suite 4&#xff0c;前端可使用HBuilder X&#xff0c;数据库Mysql 下面提供maven安装与配置步骤和nvm安装与配置步骤&#xff1a; 1、maven安装与配置 1…

python量化交易——金融数据管理最佳实践——使用qteasy管理本地数据源

文章目录 统一定义的金融历史数据表最重要的数据表数据表的定义交易日历表的定义&#xff1a;交易日历表: trade_calendar qteasy是一个功能全面且易用的量化交易策略框架&#xff0c; Github地址在这里。使用它&#xff0c;能轻松地获取历史数据&#xff0c;创建交易策略并完…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_conf_t

ngx_conf_t 定义在src/core/ngx_core.h typedef struct ngx_conf_s ngx_conf_t;ngx_conf_s 定义在 src/core/ngx_conf_file.h struct ngx_conf_s {char *name;ngx_array_t *args;ngx_cycle_t *cycle;ngx_pool_t *po…

有道云数据下载导出到本地结合Typora-v1.9.5 解锁版解压版构建本地笔记库

1、下载python 导出脚本 脚本下载&#xff1a;yodaonote-pull 2、安装python 依赖包 3、获取有道云cookies 通过有道云网页版登录获取cookies 方式一&#xff1a;浏览器F12 方式二&#xff1a;chrome 浏览器插件Cookie-copy 查看 4、配置导出路径 配置cookies.json {…

Linux——计算机网络

一.历史 网络产生 二战结束&#xff0c;世界迅速进入了美苏冷战对抗状态。1957年&#xff0c;苏联成功发射了第一颗人造卫星“sputnik”&#xff0c;震惊了整个西方世界&#xff0c;极大的刺激了美国。为了防止对美国不利的震惊技术再次出现&#xff0c;1958年&#xff0c;美…

ES如何打印DSL

看了一下官网版本已经来到了8.17 正常打印似乎不行&#xff0c;突破的地方则是 藏在JsonpDeserializable 这个注解上&#xff1b; JsonpDeserializable public class SearchRequest 因此只有反序列化出来之后才能打印&#xff0c;似乎就这么简单&#xff0c;看源码或许能更快…