当前位置: 首页 > news >正文

【如何使用Redis实现分布式锁详解讲解】

Redis 实现分布式锁:原理与功能点解析(类似 Redisson 实现)

在分布式系统中,是保证多个节点访问共享资源时数据一致性的关键工具。Redis 因其单线程、高性能和原子操作特性,被广泛用于实现 分布式锁。本文将从功能点出发,讲解 Redis 如何实现一把完整的分布式锁,并与 Redisson 的实现机制对齐。

前言

要实现使用Redis实现一个分布式锁不是简单的使用 setnx命令就可以实现的,还需要从可重入性死锁避免、解锁时候释放的是自己锁而不是其他线程的、业务还在执行锁时间过期,锁释放,需要保证锁续期机制。以及集群模式,如何保证Redis宕机后在其他 Redis 实例中锁还是存在的问题。

一、功能目标

一个高质量的分布式锁需要具备以下特性:

  1. 互斥性:同一时间只有一个客户端持有锁。
  2. 可重入性:持有锁的客户端可以多次获取而不会阻塞。
  3. 死锁检测/避免:锁不能因客户端异常而永久无法释放。
  4. 解锁正确性:只有持有锁的客户端可以释放锁。
  5. 续期机制:当业务执行时间超过锁的 TTL,可以延长锁的有效期。
  6. 容错性:在分布式环境下保证可靠性,防止 Redis 宕机或主从切换导致数据不一致。

二、核心实现思路

Redis 实现分布式锁的关键是利用 原子操作 + Lua 脚本 + TTL + Hash + 发布订阅机制,结合客户端逻辑完成完整功能。

流程如下:

+------------------+
|   客户端线程A     |
+------------------+|| lock()v
+------------------+
|   Redis Lua脚本   |
+------------------+|| key不存在?/ \是    否/       \
+------------------+      +------------------+
| 设置锁 key       |      | key存在,ownerId?|
| HSET ownerId=1  |      | = 当前线程?       |
| PEXPIRE TTL     |      +------------------+
+------------------+            ||                       |v                       v返回 "OK"                  重入 HINCRBY + PEXPIRE返回 "REENTER"|v客户端持有锁|| <业务执行期间>v看门狗定时续期renew.lua 刷新 TTL|vTTL 持续有效,防止过期|v
+------------------+
| unlock() 调用    |
+------------------+|| ownerId匹配?/ \否    是/       \
返回 NOT_OWNER   HINCRBY -1|| 重入计数>0?/ \是   否/       \PEXPIRE TTL   DEL key + 发布 unlock 消息

1. 互斥性(Mutual Exclusion)

目标:同一时间只能有一个客户端获取锁。

实现方式

  • 使用hsetnx+pexpire命令保证互斥和过期。

  • Redis 单线程保证 Lua 脚本原子执行。

  • 第一次获取锁时,Lua 脚本检查 key 是否存在:

    • 不存在 → 成功加锁
    • 已存在 → 返回锁剩余 TTL,表示锁被占用
if redis.call('exists', KEYS[1]) == 0 thenredis.call('hset', KEYS[1], ARGV[2], 1)redis.call('pexpire', KEYS[1], ARGV[1])return "OK"
end
return "BUSY"

原子性保证只有一个客户端可以首次成功加锁,从而实现互斥。


2. 可重入性(Reentrancy)

目标:同一线程或客户端可以多次获取锁。

实现方式

  • 使用 Hash 结构 存储锁信息:

    • field = ownerId(如 processUUID:threadId,其中processUUID每个JVM实例都是唯一的,threadId代表每个线程ID)
    • value = 重入计数
  • 同一 ownerId 再次加锁时:

    • HINCRBY 增加计数
    • 刷新 TTL
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 thenredis.call('hincrby', KEYS[1], ARGV[2], 1)redis.call('pexpire', KEYS[1], ARGV[1])return "REENTER"
end

3. 死锁检测/避免(Deadlock Prevention)

目标:防止锁因异常未释放导致死锁。

实现方式

  • TTL 自动释放:锁超时自动删除,防止宕机导致永久占用。
  • 看门狗机制:锁持有者仍在执行业务时,周期性刷新 TTL,防止锁被意外释放。
  • 硬上限(可选):设定锁最长持有时间,超时触发告警或拒绝续期。

4. 解锁正确性(Correct Unlock)

目标:只有锁持有者能解锁,避免误删。

实现方式

  • Lua 脚本检查 ownerId 是否匹配:

    • 不匹配 → 返回 NOT_OWNER

    • 匹配

      • 重入计数 >1 → 减少计数,锁仍保留
      • 重入计数 =0 → 删除 key,并发布 unlock 消息通知等待者
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 thenreturn "NOT_OWNER"
endlocal counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)
if counter > 0 thenredis.call('pexpire', KEYS[1], ARGV[2])return "STILL_HELD"
elseredis.call('del', KEYS[1])redis.call('publish', KEYS[2], 'unlock')return "UNLOCKED"
end

5. 续期机制(Lease Renewal)

目标:防止业务执行时间超过锁 TTL 时,导致锁释放触发的线程安全问题,从而延长锁有效期。

实现方式

  • 开启一个守护线程看门狗定时任务,每 lease/3 调用 renew.lua 刷新 TTL:
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 thenreturn redis.call('pexpire', KEYS[1], ARGV[1])
end
return 0
  • 只有锁持有者续期,避免其他线程意外延长锁。

6. 容错性(Fault Tolerance)

目标:在分布式环境保证锁可靠性。

实现方式

  1. Redis 高可用

    • Cluster 或主从 + 哨兵,保证节点故障不会丢失锁。
    • 集群模式下,需要通过RedLock防止"脑裂"问题。
  2. 持久化

    • AOF 或 RDB,防止 Redis 重启丢失锁。
  3. 栅栏令牌(Fencing Token)

    • 每次成功加锁发放单调递增 token,用于业务防止旧锁写入覆盖新锁。
  4. 客户端容错

    • 看门狗续期失败重试
    • 异常捕获与日志告警,保证锁状态可观测

七、ownerId 生成与可重入保证

  • ownerId = processUUID + “:” + threadId

    • processUUID:JVM 启动时生成一次,标识进程唯一性
    • threadId:线程唯一标识
  • 同一线程多次加锁 → ownerId 不变 → 支持可重入

  • 跨节点或线程冲突 → ownerId 不同 → 保证互斥

public class LockClient {private static final String PROCESS_UUID = UUID.randomUUID().toString();private String getOwnerId() {return PROCESS_UUID + ":" + Thread.currentThread().getId();}
}

八、总结

Redis 实现分布式锁的核心是:

  1. 多个Redis命令都是通过 Lua 脚本保证各个操作的原子性,防止出现线程安全问题。
  2. ownerId + Hash 计数保证可重入性
  3. TTL + 看门狗避免死锁
  4. 解锁前校验 ownerId 保证安全性
  5. 周期续期机制应对长业务执行
  6. 高可用部署 + 栅栏令牌 + 客户端容错保证分布式可靠性

这种设计机制与 Redisson 基本一致,适合生产环境使用。

http://www.dtcms.com/a/348705.html

相关文章:

  • [快乐数](哈希表)
  • 解决编译osgEarth中winsocket2.h找不到头文件问题
  • 基于Spark的热门旅游景点数据分析系统的设计-django+spider
  • Spring Boot测试陷阱:失败测试为何“传染”其他用例?
  • 【追涨抄底关注】副图指标 紫色主力线上行表明资金介入明显 配合价格突破时可靠性更高
  • deepseek连接solidworks设计一台非标设备 (part1)
  • 阿里云ECS服务器搭建ThinkPHP环境
  • 互联网大厂AI/大模型应用开发工程师面试剧本与解析
  • 阿里云云数据库RDS PostgreSQL管控功能使用
  • 基于SpringBoot的婚纱影楼服务预约平台【2026最新】
  • Spring AI 学习笔记(2)
  • GitHub 热榜项目 - 日榜(2025-08-24)
  • Wireshark USRP联合波形捕获(下)
  • windows上如何实现把指定网段的流量转发到指定的端口,有哪些界面化的软件用来配置完成,类似于 Linux中的iptables规则实现
  • 6.1Element UI布局容器
  • 【Luogu】P2602 [ZJOI2010] 数字计数 (数位DP)
  • 基于大模型的对话式推荐系统技术架构设计-- 大数据平台层
  • 07 - spring security基于数据库的账号密码
  • window11无法连接Fortinet SSL VPN
  • Elasticsearch如何确保数据一致性?
  • 『深度编码』操作系统-进程之间的通信方法
  • 记录一下TVT投稿过程
  • 阿里云大模型应用实战:从技术落地到业务提效
  • Dify 从入门到精通(第 53/100 篇):Dify 的分布式架构(进阶篇)
  • 兑换汽水瓶
  • 关于并查集
  • 数字营销岗位需要具备的能力有哪
  • Java 内存模型(JMM)与并发可见性:深入理解多线程编程的基石
  • Flink session cluster与Flink per-job cluster区别
  • Zynq开发实践(Verilog、仿真、FPGA和芯片设计)