image.png

## 核心问题:Redisson分布式锁为何用HINCRBY自增减一
- 分布式锁4大挑战
  - 身份隔离:需识别同一客户端/线程多次加锁
  - 可重入性:需安全记录重入次数
  - 并发安全:避免竞态条件
  - 优雅释放:防止误删/提前解锁
- HINCRBY核心作用
  - 原子性:Redis单线程模型保障操作无冲突
  - 计数管理:加锁+1、解锁-1,记录重入次数
  - 状态隔离:结合Hash结构,以UUID:threadId为字段
  - 释放判断:计数归零才删除锁Key
## Redisson实现原理
- 数据结构:Redis Hash类型
  - Key:锁名称(redisson_lock:{mylock})
  - Field:客户端/线程唯一标识(UUID:threadId)
  - Value:重入次数(整数)
- 核心操作
  - 加锁:Lua脚本+ HINCRBY +1,首次加锁初始化计数
  - 解锁:Lua脚本+ HINCRBY -1,计数归零则删Key
  - 看门狗:未指定leaseTime时自动续期
## 替代方案缺陷
- 先GET再INCR再SET:非原子操作,计数混乱
- String类型INCR:无法区分不同客户端
- 多Key记录:管理复杂,内存开销大
- WATCH+MULTI+EXEC:高并发下重试频繁,性能差
## 实践应用
- 依赖引入:Maven依赖(redisson 3.24.1)
- 核心API:RLock接口(tryLock/unlock)
- 关键配置:合理设置超时时间、启用看门狗
- 注意事项:加解锁次数匹配、避免长时间持锁

分布式锁

在分布式系统中,多个服务实例可能同时访问共享资源。为保证数据一致性,需使用分布式锁进行同步控制。然而,传统单机锁(如Java中的synchronizedReentrantLock)无法跨JVM生效,因此必须引入分布式协调机制。

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

HINCRBY

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

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

核心流程

image.png

graph TB
    Start[客户端调用 lock/tryLock] --> CheckLeaseTime{是否指定 leaseTime?}
    
    CheckLeaseTime -->|是| TryLock1[执行 Lua 加锁脚本]
    CheckLeaseTime -->|否| TryLock2[使用 watchdog 默认时间]
    
    TryLock1 --> EvalLua1[Lua: 检查锁是否存在]
    TryLock2 --> EvalLua1
    
    EvalLua1 --> LuaCheck{锁状态?}
    
    LuaCheck -->|不存在或当前线程持有| Acquire[HINCRBY 增加计数<br/>PEXPIRE 设置过期]
    LuaCheck -->|被其他线程持有| ReturnTTL[返回剩余 TTL]
    
    Acquire --> AcquireSuccess{加锁成功}
    
    AcquireSuccess -->|指定 leaseTime| NoWatchdog[不启动看门狗]
    AcquireSuccess -->|未指定 leaseTime| StartWatchdog[启动看门狗任务]
    
    StartWatchdog --> WatchdogSchedule[每 internalLockLeaseTime/3<br/>执行续期]
    
    WatchdogSchedule --> RenewCheck{锁还持有?}
    RenewCheck -->|是| RenewLua[Lua: HEXISTS + PEXPIRE<br/>延长到 watchdogTimeout]
    RenewCheck -->|否| StopWatchdog[停止看门狗]
    
    RenewLua --> WatchdogSchedule
    
    ReturnTTL --> Subscribe[订阅解锁通知 channel]
    Subscribe --> Wait[等待信号或超时]
    
    Wait --> Retry{是否超时?}
    Retry -->|否| TryAgain[重新尝试获取锁]
    Retry -->|是| Failed[获取锁失败]
    
    TryAgain --> EvalLua1
    
    NoWatchdog --> Unlock[业务执行完毕]
    StopWatchdog --> Unlock
    
    Unlock --> UnlockLua[执行 Lua 解锁脚本]
    UnlockLua --> UnlockCheck{是否当前线程持有?}
    
    UnlockCheck -->|否| Error[抛出 IllegalMonitorStateException]
    UnlockCheck -->|是| DecrCount[HINCRBY 减少计数]
    
    DecrCount --> CountCheck{计数 > 0?}
    CountCheck -->|是| ResetExpire[PEXPIRE 重置过期<br/>可重入锁未完全释放]
    CountCheck -->|否| DelLock[DEL 删除锁<br/>PUBLISH 通知等待者]
    
    DelLock --> CancelWatchdog[取消看门狗任务]
    CancelWatchdog --> End[解锁完成]
    
    ResetExpire --> End
    Failed --> End
    Error --> End

看门狗机制

redisson中的看门狗机制总结

graph LR
    A[加锁成功<br/>未指定 leaseTime] --> B[scheduleExpirationRenewal]
    B --> C[LockRenewalScheduler.renewLock]
    C --> D[创建/获取 LockTask]
    D --> E[添加到 name2entry Map]
    E --> F[启动定时任务]
    F --> G[每 watchdogTimeout/3 执行]
    G --> H[批量续期 Lua 脚本]
    H --> I{锁还持有?}
    I -->|是| J[延长过期时间]
    I -->|否| K[从任务中移除]
    J --> G
    K --> L[解锁时取消任务]

公平锁非公平锁

特性 非公平锁 (RedissonLock) 公平锁 (RedissonFairLock)
获取策略 抢占式,先到先得 FIFO 队列,严格按申请顺序
数据结构 1 个 Hash 3 个结构:Hash + List + ZSet
性能 高,吞吐量大 相对较低,需维护队列
公平性 无保证,可能饥饿 严格保证顺序
Redis 命令 HEXISTS, HINCRBY, PEXPIRE 额外使用 LPUSH, RPUSH, ZADD, ZREM
适用场景 高并发、对顺序无要求 需要严格顺序的场景
锁类型 Redis Key 用途 内容
非公平锁 {lockName} 锁本身 Hash: {threadId: 重入次数}
非公平锁 redisson_lock__channel:{lockName} 通知channel 所有线程订阅
公平锁 {lockName} 锁本身 Hash: {threadId: 重入次数}
公平锁 redisson_lock_queue:{lockName} 等待队列 List: 按顺序存储等待的threadId
公平锁 redisson_lock_timeout:{lockName} 超时集合 ZSet: {threadId: timeout}
公平锁 redisson_lock__channel:{lockName}:{threadId} 专属通知channel 每个线程独立订阅

根据超时时间唤醒,或者订阅唤醒