临高县网站建设_网站建设公司_服务器维护_seo优化
2025/12/28 21:44:17 网站建设 项目流程

本文 的 原文 地址

原始的内容,请参考 本文 的 原文 地址

本文 的 原文 地址

尼恩说在前面

年底,大厂的hc越来越多, 反而 机会越混越多。 以前的金九银十, 现在变成 黄金年底。

在45岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格。

前两天一个 小伙伴面 阿里,遇到的一个基础面试题:

redisson分布式锁 ,为什么用HINCRBY 自增 减一?

小伙伴没有做过系统化梳理,有一句没一句的说了几嘴, 没有回答好, 挂了。

回来后 求助尼恩。

这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V176版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书

redisson分布式锁 ,为什么用HINCRBY 自增 减一?

在分布式系统中,多个服务实例可能同时访问共享资源。为保证数据一致性,需使用分布式锁进行同步控制。

然而,传统单机锁(如Java中的synchronizedReentrantLock)无法跨JVM生效,因此必须引入分布式协调机制。

Redisson 是一个基于 Redis 的 Java 客户端,提供了丰富的分布式对象和服务,其中分布式锁(如 RLock)是其核心功能之一。

在实现可重入的分布式锁时,Redisson 的RLock 使用了 Redis 的哈希结构(Hash)和 HINCRBY 命令来管理锁的持有状态与重入次数。

尼恩 聚焦于 Redisson 如何利用 HINCRBY 实现重入锁的计数控制,为大家 深入分析为何必须使用该命令而非简单的 SET/DEL 操作,并总结其设计原理、技术优势及最佳实践。

一、 分布式锁4大的挑战 & 可重入性核心 问题

高并发场景 多个服务实例并发访问 一个 分布式锁 ,若缺乏对“同一客户端重复加锁”行为 有效识别与处理 ,极易导致一个严重问题: 分布式锁的自我阻塞、甚至死锁。

尼恩个大家梳理一下,分布式锁4大的挑战 如下:

4大挑战 解法 HINCRBY 的作用
如何识别同一客户端的多次加锁? 使用线程唯一标识作为 Hash 字段(如 UUID:threadId 提供独立命名空间,实现不同线程间的状态隔离
如何实现重入计数? 将 Value 设为整型,记录进入次数 支持通过 HINCRBY 安全累加(+1)或递减(-1)
如何保证并发安全? 直接依赖 Redis 单线程模型下的原子命令 HINCRBY 天然避免竞态条件,无需额外同步机制
如何优雅释放锁? 只有当计数归零时才删除 Key 利用 -1 操作后判断结果值,确保不误删其他重入的锁

1.1 分布式锁4大的挑战

分布式锁的设计与实现需解决客户端身份识别、重入性、并发安全、锁释放四大核心挑战,每一个挑战都对应分布式场景下的独特问题,且直接决定锁的可用性与安全性:

挑战 1:客户端 / 线程身份隔离 —— 识别同一客户端的多次加锁

分布式环境中,同一客户端(如微服务实例)的不同线程、或不同客户端的线程可能同时竞争同一把锁.

核心问题是无法区分 “同一客户端的重复加锁” 和 “不同客户端的竞争加锁”,易导致 “误判重入为竞争” 或 “不同线程锁状态串用”。

  • 核心痛点:若仅用简单的 Key-Value(如lock:order=123)存储锁状态,无法识别加锁的具体线程,可能出现 “线程 A 加锁后,线程 B 误释放线程 A 的锁”,或 “同一线程重入时被判定为锁竞争”。
  • 解法逻辑:借助 Redis Hash 结构,将 “锁 Key” 作为 Hash 的 Key,“客户端 / 线程唯一标识(如 UUID:threadId)” 作为 Hash 的字段,实现不同线程 / 客户端的状态隔离,让锁能精准识别 “是否是同一主体的多次加锁”。

挑战 2:锁的重入性 —— 实现安全的重入计数

重入性是指 “持有锁的线程再次请求加锁时,无需等待, 可直接获取”,

分布式场景下的核心问题是无法安全记录重入次数,易导致 “重入时锁被阻塞” 或 “释放时提前解锁”

  • 核心痛点:若仅用 “存在 / 不存在” 标识锁状态,同一线程第二次加锁会被判定为 “锁已占用” 而阻塞;若简单释放锁,会导致 “一次释放就删除锁”,忽略重入次数(如线程重入 3 次,仅释放 1 次就丢失锁)。
  • 解法逻辑:将 Redis Hash 中字段的 Value 设为整型,代表 “重入计数”;通过HINCRBY原子命令实现计数的安全累加(加锁时 + 1)和递减(释放时 - 1),既支持重入,又能精准记录进入次数。

挑战 3:并发安全 —— 避免竞态条件

分布式锁的核心价值, 是解决多节点并发竞争资源的问题,但锁本身的操作(加锁、释放、计数)若存在并发风险,会导致 “锁失效”(如多个线程同时获取锁),核心问题是加锁 / 计数操作的原子性无法保证

  • 核心痛点:若用 “先查值 + 再修改” 的非原子操作(如先 GET 判断锁状态,再 SET 设置),高并发下会出现竞态条件(多个线程同时查到 “锁未占用”,进而同时加锁),导致分布式锁失效。
  • 解法逻辑:依赖 Redis 单线程模型下HINCRBY的天然原子性 —— 无论多少线程并发调用HINCRBY,Redis 都会串行执行,确保计数的累加 / 递减操作无冲突,无需额外的同步机制即可保证并发安全。

挑战 4:锁的优雅释放 —— 避免误删与提前解锁

分布式锁释放的核心问题是 “释放时机” 和 “释放权限” 的双重校验:既要避免 “重入次数未归零时提前删除锁”,也要避免 “非加锁线程误释放锁”,否则会导致锁失效或资源竞争。

  • 核心痛点:若释放锁时直接删除 Key(如 DEL 命令),会出现两种问题:① 重入场景下,一次释放就删除锁,后续重入的线程失去锁保护;② 线程 A 的锁未过期,但线程 B 误删线程 A 的锁,导致资源被非法访问。
  • 解法逻辑:释放锁时先通过HINCRBY key field -1将重入计数减 1,仅当计数结果为 0(表示重入次数完全归零)时,才删除对应的 Hash 字段 / Key;同时结合 Hash 的字段(线程标识)校验,确保只有加锁的线程能操作自身的计数,避免跨线程误释放。

1.2 可重入性核心 问题

设想以下场景: 某服务中的方法A加了分布式锁,在执行过程中调用了同属该服务的另一个加锁方法B。

如果锁不具备可重入性,尽管是同一个线程发起的调用,也会因为“锁已被占用”而被阻塞——即使这个“占用者”就是自己。

这会带来两个严重后果:

  • 自我死锁:线程永远等待自己释放锁,程序卡死。
  • 开发复杂度上升:开发者必须手动规避所有可能的重入路径,牺牲代码可读性和复用性。

因此,在真实业务场景中,尤其是存在模块化设计或递归逻辑的服务里,支持可重入是分布式锁能否落地的关键门槛

若不解决锁的持有者识别重入次数的原子维护问题,将导致锁被错误释放、重入失效或死锁,严重威胁系统一致性。

1.2 .1. 什么是重入锁(Reentrant Lock)?

重入锁允许同一个线程多次获取同一把锁,而不会造成死锁。

每次成功加锁后必须对应一次解锁操作,只有当所有加锁都被释放后,锁才会被彻底归还给系统。

典型场景示例:


void methodA() {lock.lock();     // 第一次加锁try {methodB();   // 内部再次请求同一把锁} finally {lock.unlock();}}void methodB() {lock.lock();     // 同一线程再次加锁 —— 必须允许try {// 执行业务逻辑} finally {lock.unlock();}}

如果 不支持重入,那么, 当 methodB() 尝试获取已被当前线程持有的锁时,会因无法识别“自己已持有锁”而导致阻塞或失败,进而引发死锁或运行时异常。

这种需求在实际开发中非常常见,例如:

  • 递归调用的方法链;
  • AOP 切面中多个方法共用同一锁;
  • 分层调用中上层方法加锁、下层方法复用锁。

因此,一个健壮的分布式锁必须像 JVM 中的 ReentrantLock 一样,支持可重入特性。

1.2.2. 分布式环境下重入的挑战

在单机环境中,JVM 可以通过 Thread.currentThread().getId() 轻松判断是否为同一线程,从而决定是否允许重入。

但在分布式系统中,情况变得复杂:

  • 多个微服务实例可能同时竞争同一资源;
  • 同一客户端的不同请求可能由不同线程处理;
  • 锁的状态存储在远程 Redis 中,无法依赖本地线程上下文。

这就带来了三个关键问题:

问题 描述
锁归属识别 如何唯一标识是哪个客户端、哪个线程持有锁?
重入计数 如何记录该线程已加锁多少次,确保正确释放?
原子性操作 加锁/解锁过程必须原子执行,避免并发干扰导致状态错乱

这些问题的本质在于:如何在无共享内存的分布式环境中,模拟出类似本地可重入锁的行为?

若仅使用简单的 SET/DEL 操作,无法记录重入次数,极易导致“误释放”——即一个线程释放了不属于自己的锁,或在未完全释放所有重入层级时就提前删除锁,破坏了锁的安全性与可重入语义。

1.3、Redisson 数据结构设计

Redisson 是一个基于 Redis 的 Java 客户端,提供了丰富的分布式对象和服务,其中分布式锁(如 RLock)是其核心功能之一。

为了实现可重入锁,即同一个线程可以多次获取同一把锁而不被阻塞,Redisson 并未采用简单的键值存储方式,而是引入了更精细的状态隔离机制。

Redisson 通过 Redis 的哈希结构(Hash)结合原子命令 HINCRBY,将锁的持有者作为字段(field),重入次数作为值(value),利用 HINCRBY的原子自增与自减能力,精确控制重入计数。

Redisson 将每个锁表示为一个 Redis Key,类型为 Hash:


KEY: "redisson_lock:{mylock}"
VALUE: Hash 结构Field: "UUID:threadId" → 表示锁的持有者Value: 重入次数(整数)

具体来说,Redisson 利用 Redis 的哈希结构(Hash)来存储锁的持有信息:

  • Key 表示锁名称(例如 "myLock"

  • Field 表示唯一客户端标识(包含线程 ID 等信息)

  • Value 表示该客户端对该锁的重入次数

例如:


HSET redisson_lock:{mylock} "c3f4a5b6-1234-5678:12" 2

表示 UUID 为 c3f4a5b6... 的客户端、线程 ID 为 12 的进程持有该锁,且已重入两次。

Redisson 这一设计以“单个哈希键管理多客户端状态 + 原子操作保障一致性”为核心,实现了高效、安全的可重入机制。

Redisson 的分布式锁设计中,HINCRBY命令成为关键操作:

  • 当线程首次加锁时,执行 HINCRBY key clientID 1,设置初始计数为 1
  • 同一线程再次加锁时,自动执行 HINCRBY key clientID 1,计数递增
  • 每次释放锁时,则执行 HINCRBY key clientID -1,计数递减
  • 当计数归零时,才真正通过 DEL 删除整个锁键,允许其他客户端竞争

这种方式确保了只有加锁的客户端才能进行减一操作,且必须完全匹配重入次数才能最终释放锁,从根本上避免了误释放问题。

Redisson 相较于非重入锁方案(如 SETNX + EXPIRE),这种设计虽然略微增加数据结构复杂度,但换来了更高的安全性与编程友好性,特别适用于递归调用、嵌套同步块等常见业务场景。

1.3.1 HINCRBY 是那三个单词的缩写 ?

HINCRBY 不好记忆,其完整单词拆解及缩写逻辑如下:

缩写片段 完整单词 含义
H Hash 哈希(Redis 的哈希类型)
INCR Increment 增量 / 增加(动词)
BY By 以… 为单位(介词)

HINCRBY 本质是 Hash Increment By 的缩写(核心三个单词),字面意为:对哈希(Hash)中的字段值,按指定数值(BY 后跟的参数)进行增量(INCR)操作

逻辑上是 “Hash + Increment + By” 的组合,其中 INCR 本身是 Increment 的缩写(Redis 中大量命令采用此类缩写,如 INCR/INCRBY);

HINCRBY 功能:HINCRBY key field increment → 对 key 对应的哈希中 field 字段的数值,增加 increment(整数),若字段不存在则先初始化为 0 再增量。

类似的 Redis 命令缩写逻辑:

  • INCRBY = Increment By(普通键的数值增量)
  • HINCRBYFLOAT = Hash Increment By Float(哈希字段的浮点型增量)

二 :Redisson 分布式锁结构: “唯一标识 + 哈希字段计数”

Redisson 通过“唯一标识 + 哈希字段计数”的组合策略, 以 Hash 结构记录锁的持有者及其重入次数。

另外,Redisson 还 借助 Lua 脚本封装 HINCRBY原子操作,实现对加锁、重入和解锁全过程的安全控制,确保高并发下锁状态的一致性与可重入语义的正确性。

1. 数据结构设计

Redisson 将每个分布式锁,设计为一个 Redis Hash 类型 Key:


KEY: "redisson_lock:{mylock}"
VALUE: Hash 结构Field: "UUID:threadId" → 锁的唯一持有者标识Value: 整数 → 当前重入次数

这种设计, 结构清晰,且具备天然的字段隔离能力。

例如,使用 下面的操作 可以 设置 客户端 c3f4a5b6... 的线程 12 正持有该锁,并已成功重入两次。


HSET redisson_lock:{mylock} "c3f4a5b6-1234-5678:12" 2

Redis Hash 类型 设计的关键优势在于:

优势一:线程隔离

通过 "UUID:threadId" 唯一确定锁的持有者,避免误删他人锁。

优势二:支持重入

hash 里边的 "UUID:threadId" 对应的value 整数值记录重入深度,而非 简单的存在与否,真正模拟 JVM 级重入行为。

优势三:高效操作

Hash 内部字段 里边的 "UUID:threadId" 对应的value 可独立更新,无需读取整个结构, 即可修改 持有者的计数。

2. 使用 HINCRBY命令 加锁 解锁

Redisson 使用 HINCRBY命令 加锁 解锁 , 先复习一下 HINCRBY 命令

(1) HINCRBY 命令 回顾

HINCRBY是 Redis 提供的一个原子命令,用于对哈希表中指定字段的值进行整数增减:

  • 若key字段不存在,则自动创建key, 并设为 increment
  • 操作全程原子执行,不受并发干扰;
  • 支持正负增量,既可用于加一(+1),也可用于减一(-1)。

这使得它成为实现“条件性递增/递减”的理想工具,尤其适用于需要精确控制状态变化的场景——比如分布式锁的重入计数。

为什么必须是 HINCRBY

普通先读再写的方式存在竞态条件:两个线程同时读到 count=1,各自加一后写回为 2,实际应为 3。

而 HINCRBY在服务端完成计算,彻底杜绝此类问题。

(2)在加锁中的应用

Redisson首次加锁 & 后续的重入 , 使用 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
else-- 锁已被占用,检查是否为自己持有if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then-- 是自己:重入,计数 +1redis.call('hincrby', KEYS[1], ARGV[2], 1)redis.call('pexpire', KEYS[1], ARGV[1])return nilend
end
-- 其他人持有锁,返回剩余过期时间
return redis.call('pttl', KEYS[1])

参数说明:

  • KEYS[1]: 锁名,如 "redisson_lock:{mylock}"
  • ARGV[2]: 客户端标识 "UUID:threadId"
  • ARGV[1]: 锁超时时间(毫秒)

流程解析:

(1) 若锁不存在 → 创建 Hash 并设置初始计数为 1;

(2) 若锁存在且为当前客户端持有 → 使用 HINCRBY实现重入 +1;

(3) 否则 → 返回 TTL,表示加锁失败。

关键点:HINCRBY实现了重入次数的原子累加,保障多线程或多节点并发重入时不丢不乱。

(3)在解锁中的应用

Redisson 解锁 , 同样 使用 Lua 脚本 完成 保证原子性 ,确保判断与操作不可分割:


-- 解锁脚本(简化版)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then-- 不是当前客户端持有的锁,拒绝操作return nil
end
-- 计数减一
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1)
if (counter > 0) then-- 仍处于重入状态,刷新过期时间redis.call('pexpire', KEYS[1], ARGV[1])
else-- 计数归零,彻底释放锁redis.call('del', KEYS[1])publish(pubsubKey, 'unlock')  -- 通知等待者
end
return 0

关键逻辑:

  • 使用 HINCRBY field -1 安全减一;
  • 若结果大于 0:说明还有未退出的调用栈,仅刷新 TTL;
  • 若等于 0:删除整个 Key,并发布消息唤醒阻塞线程。

45岁老架构师尼恩提示::减一也必须原子执行。若分开读写,可能导致多个线程同时看到旧值 1,各自减一后都以为可以删除 Key,造成重复释放或数据错乱。

核心流程图

Mermaid

通过“唯一身份标识 + 原子计数增减”的核心思路,结合 Redis 的 HINCRBY与 Lua 脚本,完整实现了可重入分布式锁的安全闭环。

不仅解决了传统分布式锁无法重入的问题,更在高并发下保持了状态一致性,是构建可靠微服务系统的底层基石之一。

三:如果不用 HINCRBY, 有哪些替代方案 ?

在实现分布式重入锁时,必须保证同一个客户端、同一线程能多次获取锁(重入),同时避免不同客户端之间的计数混淆。

关键挑战:如何在一个高并发、分布式场景, 实现 每个线程的加锁过程的 计数读 & 计数更新 两个操作原子性。

如果没有原子性,极易导致数据错乱或锁状态不一致。

如果不用 HINCRBY, 替代方案 有哪些呢?

方案 缺陷 是否可行
先 GET 再 INCR 再 SET 非原子操作,多线程下可能覆盖彼此结果,造成计数丢失或重复增加 不可用
使用 INCR on String 所有线程共享同一计数器,无法区分不同客户端或线程,彻底破坏重入语义 不支持重入
使用多个 Key 记录每线程计数 每个线程一个 key,管理复杂,难以追踪和清理过期锁,内存开销大 效率低
使用 WATCH + MULTI + EXEC 依赖乐观锁机制,高并发冲突时频繁重试,性能急剧下降 ️ 可行但非最优

这些方案, 要么牺牲了原子性,要么破坏了上下文隔离(即无法识别“谁加的锁”),要么带来高昂的运维与性能成本。

这些方案, 因根本性缺陷,均无法在生产环境中可靠支撑 原子重入语义。

Redisson 使用 Redis 的 HINCRBY命令,在一个 Hash 字段中以线程标识为 key,原子性地增减重入计数。

HINCRBY 天然支持原子自增/自减、可返回负值用于边界判断,且结构紧凑,完美契合分布式重入锁的核心需求。

HINCRBY的优势:

(1) 原子性保障:对某个线程的计数进行增减是单条命令完成,不会被中断;

(2) 高性能执行:作为 Redis 原生命令,底层高度优化,响应迅速;

(3) 结构简洁清晰:用一个 Hash 存储所有线程的重入次数,key 小、结构紧;

(4) 支持负数返回:解锁时先减一再判断是否归零,即使误解锁也能及时发现异常;

正是因为 HINCRBY支持返回负数,Redisson 才能安全执行“先减一,再判断是否等于 0”的释放逻辑。若结果为 0,则真正释放锁;若仍大于 0,说明仍是重入状态;若小于 0,则表明解锁次数过多,属于非法操作。

核心流程图解

Mermaid

图解说明:通过 HINCRBY统一处理加锁时的计数递增,无论是首次获取还是重入,都由同一个原子操作完成,确保逻辑一致性与线程安全性。

综上所述,HINCRBY不仅解决了重入场景下的原子性和身份隔离问题,还兼顾了性能与实现简洁性,是构建 Redis 分布式重入锁的最优且必要选择

四:Redisson 核心 API 使用示例

核心痛点:

在分布式系统中,多个服务实例可能同时操作共享资源,传统单机锁(如 synchronized)无法跨进程生效,导致数据不一致或重复执行问题。更复杂的是,当同一个线程需要多次获取同一把锁时(例如递归调用或嵌套方法),必须支持可重入性,否则将造成死锁。

核心方案:

Redisson 基于 Redis 实现了分布式可重入锁,通过 RLock 接口和底层的 HINCRBY原子操作,既保证了跨实例的互斥访问,又支持同一线程内的重复加锁与自动释放,彻底解决分布式环境下的并发安全问题。

4.1. 引入依赖(Maven)

使用 Redisson 的第一步是引入其 Maven 依赖。确保版本稳定且兼容你的 Redis 环境:


<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.24.1</version>
</dependency>

45岁老架构师尼恩提示::生产环境中建议锁定具体版本,并结合 Spring Boot Starter(如 redisson-spring-boot-starter)简化集成。

4.2. 基本使用代码

以下是一个典型的 Redisson 分布式可重入锁使用示例,展示了如何安全地控制临界区资源访问。


Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);RLock lock = redisson.getLock("mylock"); // 获取指定名称的锁try {// 尝试加锁:最多等待10秒,加锁成功后30秒自动过期(防止宕机未解锁)boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);if (isLocked) {try {System.out.println("获得锁,执行业务...");anotherMethod(lock); // 可重入调用} finally {lock.unlock(); // 解锁:内部触发 HINCRBY -1}} else {System.out.println("未能获取锁,跳过执行");}
} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("线程被中断,放弃获取锁");
} finally {redisson.shutdown(); // 关闭客户端连接
}// 模拟嵌套方法调用 —— 同一线程可重复进入
void anotherMethod(RLock lock) {lock.lock(); // 同一个线程再次加锁:计数器 +1try {System.out.println("在 anotherMethod 中再次获得锁");// 执行额外逻辑} finally {lock.unlock(); // 计数器 -1,直到为0才真正释放锁}
}

为什么需要 HINCRBY自增 和 减一?

Redisson 的可重入锁机制依赖 Redis 的哈希结构(Hash)来存储锁的状态,其中关键操作就是 HINCRBY

  • 当线程首次获取锁时,Redisson 会执行:

    
    HINCRBY mylock thread_id 1

    表示该线程持有锁,重入次数设为 1。

  • 若同一线程再次请求锁,则执行:

    
    HINCRBY mylock thread_id 1  # 值变为2、3...

    锁的持有次数递增,实现“可重入”。

  • 每次调用 unlock() 时,执行:

    
    HINCRBY mylock thread_id -1

    持有次数减一;只有当值降为 0 时,才会真正删除锁(触发 DEL mylock),并通知其他等待者。

优势总结

  • 原子性:HINCRBY是 Redis 的原子命令,避免并发修改冲突。
  • 高性能:无需频繁读写整个锁状态。
  • 安全释放:只有锁持有者才能对其执行减一操作,防止误删。

核心痛点: 分布式环境下无法使用本地锁,且需支持同一线程多次进入同一临界区。

核心方案: 利用 Redisson 的 RLock,基于 Redis 的 HINCRBY实现原子化的重入计数管理,做到跨节点互斥 + 线程内可重入。

加锁时自增,解锁时减一,仅当计数归零才真正释放锁 —— 简洁、高效、安全。

核心流程图(基于 Redisson 重入锁机制)

Mermaid

图解说明:

  • HINCRBY是实现重入的关键,既记录持有者身份,也维护重入深度;
  • 看门狗持续守护活跃锁,确保其不会因短暂延迟而被误释放。

4.3. 合理设置锁超时时间

为防止线程异常、网络抖动等情况导致锁无法释放,必须显式设定租约时间(leaseTime),避免无限持有。

  • 不要使用永久锁:无超时的锁一旦客户端崩溃,将导致其他节点永久等待,形成死锁。
  • 推荐使用 tryLock(waitTime, leaseTime, unit):明确指定等待时间和持有时间,提升可控性。
  • leaseTime 应略大于正常业务耗时:建议设置为平均执行时间的 1.5~2 倍,留出缓冲空间,防止误释放。

示例:若业务通常耗时 8 秒,可设 leaseTime = 15秒,兼顾安全与效率。

4.4. 使用看门狗机制(Watchdog)

在高并发分布式场景下,Redisson 分布式重入锁若使用不当,极易引发两类关键问题:一是锁未及时释放导致资源被长期占用,进而引发服务雪崩或死锁;二是业务执行时间波动大,固定超时机制容易误释放仍在运行的锁,造成数据不一致。

通过“智能续期 + 安全释放”双机制保障锁的可用性与安全性.

所谓智能续期 ,就是 利用看门狗自动延长活跃锁的有效期,避免过早失效;

所谓安全释放,就是 严格匹配加锁/解锁次数,并结合异步解耦设计降低锁持有时间,从根本上规避长时间占锁和异常释放风险。

Redisson 内建的 Watchdog 能自动为仍在执行的锁“续命”,是实现可靠锁生命周期管理的核心组件。


// 默认行为:未指定 leaseTime 时启用看门狗
RLock lock = redisson.getLock("mylock");
lock.lock(); // 默认 leaseTime = 30秒,看门狗每 10秒 续期一次
  • 看门狗默认每 leaseTime / 3 时间检查一次锁状态(如 30 秒锁则每 10 秒检查);

  • 若发现线程仍在持有锁,则自动将 TTL 重置为原始 leaseTime;

    45岁老架构师尼恩提示::看门狗生效的前提,仅当未显式传入 leaseTime 时。一旦调用 lock(10, SECONDS),则关闭自动续期,需自行确保操作在时限内完成。

45岁老架构师尼恩提示::对执行时间不确定的任务,优先依赖看门狗机制,避免因超时中断关键逻辑。

4.5. 正确处理 unlock()

解锁操作看似简单,却是最容易出错的环节之一。

错误调用可能导致锁提前释放或异常抛出。

务必放在 finally 块中执行


RLock lock = redisson.getLock("mylock");
try {if (lock.tryLock(1, 30, TimeUnit.SECONDS)) {// 执行业务逻辑}
} finally {lock.unlock(); // 保证无论是否异常都能释放
}

加锁与解锁次数必须匹配

  • Redisson 支持重入,每次 lock() 会 HINCRBY 计数器 +1;
  • 每次 unlock() 则减一,直到归零才真正删除锁;
  • unlock() 次数超过 lock(),将抛出 IllegalMonitorStateException,防止误操作。

原理说明:HINCRBY的存在正是为了支持重入能力——同一个线程多次获取同一把锁时,不会阻塞自己,而是通过哈希字段中的计数器记录嵌套层级。

4.6. 监控锁竞争情况

锁的争用程度直接影响系统性能与稳定性,应建立可观测性体系进行实时感知。

可通过以下命令查看锁内部状态:


# 查看锁的持有者及重入次数
HGETALL redisson_lock:{mylock}
# 返回示例:
#   "UUID:threadId:1" -> "2"   # 表示该线程已重入两次# 查看剩余有效期(毫秒)
PTTL redisson_lock:{mylock}
  • 结合 APM 工具(如 SkyWalking、Prometheus + Redis Exporter)采集指标并设置告警;
  • 关注高频锁争用、长等待队列、频繁续期等异常信号;
  • 可视化展示热点锁分布,辅助定位瓶颈模块。

建议监控维度:

  • 锁等待时间 P99 > 1s 触发预警;
  • 单个锁日均争抢次数 > 1万次考虑优化架构。

4.7. 避免长时间持有锁

分布式锁本质是串行化工具,不适合保护耗时较长的操作,否则将成为系统性能瓶颈。

不推荐:直接用锁包裹整个文件解析、远程调用等耗时任务;

推荐做法:

1、抢占任务权 + 异步执行:使用锁仅判断“谁可以执行任务”,成功者提交到异步线程池处理;


if (lock.tryLock()) {try {taskExecutor.submit(() -> processLongRunningTask());} finally {lock.unlock();}
}

2、引入分布式任务队列:如 Kafka、RabbitMQ,由锁触发消息投递,消费端独立执行,彻底解耦。

核心思想:锁只用于决策“谁能做”,而不用于“怎么做”,最大限度缩短临界区范围。

五、Redi源码解析:从加锁到解锁的核心逻辑解析

......... 略5000字+

...................由于平台篇幅限制, 剩下的内容(5000字+),请参参见原文地址

原始的内容,请参考 本文 的 原文 地址

本文 的 原文 地址

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询