Redis分布式锁实战:从入门到生产级方案

目录

一、为什么需要分布式锁?

二、Redis分布式锁核心特性

三、实现方案与代码详解

方案1:基础版 SETNX + EXPIRE

原理

代码示例

问题

方案2:Redisson框架(生产推荐)

核心特性

代码示例

优势

方案3:RedLock算法(Redis集群)

适用场景

实现步骤

代码片段

方案4:Lua脚本原子化操作

解决基础版问题

Lua脚本示例

Java调用方式

四、高级场景与解决方案

场景1:公平锁(按顺序获取)

实现思路

代码逻辑

场景2:可重入锁

实现机制

Redisson实现

五、避坑指南与最佳实践

1. 锁误删问题

2. 锁续期问题

3. 主从一致性问题

4. 性能优化

六、总结与选型建议


一、为什么需要分布式锁?

在微服务、多机部署场景中,多个进程可能同时竞争同一资源(如库存、订单)。传统JVM锁仅作用于本地,无法保证分布式环境的互斥访问。例如:

  • 电商库存超卖:两个服务实例同时查询库存>0,均执行扣减操作。
  • 定时任务重叠:多节点触发相同任务(如对账),导致数据混乱。

Redis凭借高性能、原子操作和丰富数据结构,成为分布式锁的首选方案。

二、Redis分布式锁核心特性

  1. 互斥性:仅一个客户端能持有锁
  2. 防死锁:锁需自动过期(如SET EX)
  3. 容错性:节点故障不影响锁机制
  4. 可重入性(可选):同一线程可多次加锁
  5. 公平性(可选):按申请顺序分配锁

三、实现方案与代码详解

方案1:基础版 SETNX + EXPIRE

原理
  • SET key value NX EX seconds:原子设置键值并设置过期时间
  • 若返回 true 则获取锁,否则重试
代码示例
String lockKey = "product_stock_lock";
String lockValue = UUID.randomUUID().toString(); // 唯一标识// 尝试加锁
Boolean success = jedis.set(lockKey, lockValue, "NX", "EX", 30);
if (success != null && success) {// 获取锁成功,执行业务逻辑try {// 扣减库存操作} finally {// 释放锁if (lockValue.equals(jedis.get(lockKey))) {jedis.del(lockKey);}}
} else {// 获取锁失败,重试或返回
}
问题
  • 锁误删:业务未完成时锁过期,其他线程可能删除当前线程的锁
  • 非原子操作set 和 get 存在竞态条件

方案2:Redisson框架(生产推荐)

核心特性
  • 可重入锁:同一线程可多次加锁,计数器管理
  • 看门狗机制:默认每10秒续期锁至30秒,防止业务超时
  • 异步兼容:支持 tryLock() 阻塞等待和 isLocked() 状态检查
代码示例
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);RLock lock = redisson.getLock("order_lock");
try {// 尝试加锁(最多等待10秒,锁过期30秒)if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {// 处理订单逻辑} else {log.warn("获取锁失败");}
} catch (InterruptedException e) {Thread.currentThread().interrupt();
} finally {lock.unlock(); // 必须在finally中释放
}
优势
  • 自动处理锁续期、可重入、跨进程兼容
  • 支持主从切换(通过Redis主从复制)

方案3:RedLock算法(Redis集群)

适用场景
  • Redis集群环境(至少3个独立节点)
  • 需容忍半数节点故障仍保持锁可用
实现步骤
  1. 向多数节点(N/2+1)发送加锁请求
  2. 所有节点设置相同键值和过期时间
  3. 成功加锁后,锁有效时间为 T - 网络延迟
代码片段
// 配置多个Redisson客户端连接不同节点
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://node1:6379");
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://node2:6380");
// 创建RedLock对象
RedissonRedLock redLock = new RedissonRedLock(Redisson.create(config1),Redisson.create(config2)
);// 加锁逻辑
try {boolean locked = redLock.tryLock(10, 30, TimeUnit.SECONDS);if (locked) {// 业务逻辑}
} finally {redLock.unlock();
}

方案4:Lua脚本原子化操作

解决基础版问题
  • 原子验证+删除:通过Lua脚本确保操作原子性
  • 唯一标识:用UUID区分锁归属
Lua脚本示例
-- 释放锁脚本(判断键值是否匹配)
if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
elsereturn 0
end
Java调用方式
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, 1, lockKey, lockValue);

四、高级场景与解决方案

场景1:公平锁(按顺序获取)

实现思路
  • 使用 List 存储等待队列
  • 通过 ZSet 记录申请时间戳,优先分配最早请求
代码逻辑
// 申请锁时加入队列
jedis.lpush("lock_queue", clientId);
jedis.zadd("lock_timestamp", System.currentTimeMillis(), clientId);// 检查队列头部是否是自己
String head = jedis.lrange("lock_queue", 0, 0).get(0);
if (head.equals(clientId) && currentTimeMatch(jedis.zscore("lock_timestamp", head))) {// 获取锁成功
}

场景2:可重入锁

实现机制
  • 使用 Hash 存储线程ID和重入次数
  • 加锁时递增计数器,解锁时递减(归零后删除锁)
Redisson实现
RLock lock = redisson.getLock("reentrant_lock");
lock.lock(); // 第一次加锁
// 嵌套调用同一锁
lock.lock(); // 重入计数+1
...
lock.unlock(); // 计数-1,归零后删除锁

五、避坑指南与最佳实践

1. 锁误删问题

  • 原因:锁过期后,其他线程删除了当前线程的锁
  • 解决方案
    • 使用唯一标识(如UUID+线程ID)作为锁值
    • 释放锁前通过Lua脚本校验归属

2. 锁续期问题

  • 看门狗机制:Redisson自动续期,业务长时间运行时需显式指定超时时间
  • 手动续期:调用 lock.expire(seconds) 延长锁时间

3. 主从一致性问题

  • 症状:主节点写锁后宕机,从节点未同步锁信息
  • 解决方案
    • 使用RedLock算法(需多数节点加锁成功)
    • 开启Redis主从复制的WAIT命令(Redis 7+)

4. 性能优化

  • 减少锁粒度:按业务ID(如订单号)细化锁范围
  • 避免嵌套锁:同一线程内多次加锁需谨慎处理重入
  • 监控指标:锁获取成功率、平均等待时间、超时次数

六、总结与选型建议

方案适用场景优点缺点
SETNX + EXPIRE快速原型、单节点场景简单高效需手动处理细节
RedissonJava项目、生产环境功能完善,自动续期依赖第三方库
RedLockRedis集群、高容错需求强一致性,容错性强性能较低,实现复杂
Lua脚本需严格原子操作的场景彻底解决非原子问题需维护脚本
发布/订阅高并发减少轮询节省资源消息可靠性需保障

最佳实践

  • 优先使用Redisson框架(生产环境)
  • 单节点快速实现可选SETNX+Lua脚本
  • 集群环境采用RedLock算法
  • 锁名称统一规范(如resource_type:id
  • 超时时间设为业务平均耗时的2倍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87329.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】StringRedisTemplate 和 RedisTemplate 的区别

StringRedisTemplate 和 RedisTemplate 是 Spring Data Redis 提供的两种用于操作 Redis 的模板类,它们的核心区别在于 序列化方式 和 操作的数据类型。以下是两者的主要区别和使用建议: ✅ 1. 数据类型支持 类名支持的数据类型说明RedisTemplate支持所…

docker-compose快速搭建redis集群

目录结构 redis-cluster/ ├── config/ │ ├── master.conf │ ├── slave1.conf │ └── slave2.conf └── docker-compose.yml配置文件内容 1. config/master.conf # Redis主节点配置 port 6379 bind 0.0.0.0 protected-mode no logfile "redis-mas…

SpringCloud系列(39)--SpringCloud Gateway常用的Route Predicate

前言:在上一节中我们实现了SpringCloud Gateway的动态路由 ,而在本节中我们将着重介绍各种Route Predicate的作用。 1、可以到官方文档里查看常用的Route Predicate的种类 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.REL…

渐变色的进度条控件

近日,用VB.net2003重写了一个渐变色的进度条控件。主要有以下功能: 支持自定义进度条分段数量,可拆分为多个步骤;每个步骤可独立显示完成百分比及渐变色效果。 每个步骤均可配置任务名称和描述;运行时能实时显示当前执…

【DICOM后处理】qt+vs 实现DICOM数据四视图显示

目录 1、DICOM四视图2、vtkImageViewer2 实现二维平面图显示3、vtkVolume实现三维体数据显示4、实现界面图 1、DICOM四视图 DICOM四视图通常指同时显示医学影像的四个不同平面或视角,用于全面分析三维数据(如CT、MRI等)。 标准四视图布局&a…

Google Maps 安装使用教程

一、Google Maps 简介 Google Maps 是谷歌提供的地图服务,通过其 JavaScript API,开发者可以在网页中嵌入地图,添加标记、路径、地理编码、路线导航等功能,适用于位置展示、物流追踪、LBS 应用等场景。 二、获取 Google Maps API…

Nginx+Keepalived实现前台服务高可用

现阶段项目开发往往采用前后台分离,前台常用的技术有vue、react等,前台代码部署在nginx中,代码中配置了后台服务的网关地址,由网关向后台分发服务请求,架构示意图如下: 在上述架构图中,如果Ngin…

Gradio全解13——MCP协议详解(5)——Python包命令:uv与uvx实战

Gradio全解13——MCP协议详解(5)——Python包命令:uv与uvx实战 第13章 MCP协议详解13.5 Python包命令:uv与uvx实战13.5.1 uv核心亮点与常用命令1. uv介绍2. 安装与项目管理3. 脚本与工具4. Python版本与pip接口 13.5.2 uv核心指令…

OD 算法题 B卷【求最小步数】

文章目录 求最小步数 求最小步数 求从坐标零点到坐标点n的最小步数,一次只能沿着横坐标轴向左或向右移动2或3;途经的坐标点可以为负数; 输入描述: 坐标点n 输出描述: 从坐标零点移动到坐标点n的最小步数 n在【1,10^9】 示例1 输入&#xf…

Elasticsearch 集群升级实战指引—7.x 升级到 8.x

升级Elasticsearch集群从7.x到8.x是一项复杂且关键的任务,涉及重大版本变更(如API调整、配置变更、安全功能强制启用等),可能影响集群的性能和稳定性。结合您提到的业务量增长导致索引写入变慢的问题,本指引不仅提供详…

JWT学习总结

文章目录 前置知识Authorization头部和 CookieCRSF攻击 JWT概念JWT认证流程使用Springboot整合JWTJwtUtil JWT案例控制器JWT拦截器注册拦截器结果 session VS Jwt 前置知识 Authorization头部和 Cookie Authorization 头部和 Cookie 是 HTTP 协议中两种不同的身份认证 / 信息…

阿里云消息队列 Apache RocketMQ 创新论文入选顶会 ACM FSE 2025

近日,由阿里云消息团队发表的 Apache RocketMQ 创新论文被 CCF-A 类软件工程顶级会议 FSE 2025 Industry Track 录用。 ACM FSE(The ACM International Conference on the Foundations of Software Engineering)是享有盛誉的国际学术会议&…

定制WordPress管理后台

WordPress作为全球最流行的建站工具,因其灵活性和易用性受到广泛欢迎。许多服务器提供商都支持一键安装WordPress,例如Hostease,使新手用户也能轻松搭建属于自己的网站。然而,后台的默认设置可能无法完全满足不同用户的需求。定制…

REST API设计与Swagger:构建高效、易用的Web服务

引言 在现代Web开发中,REST API已成为不同系统间通信的标准方式。一个设计良好的REST API不仅能提高开发效率,还能改善用户体验。而Swagger(现称为OpenAPI)作为API文档和测试的强大工具,已经成为API开发中不可或缺的一…

一个非对齐访问的问题

1、引言 最近在编写代码时,出现了这样一个 bug。程序一跑,系统就崩溃了,报错是 bus error。 目标平台:ARM32 最终定位到出错的代码片段: *((uint32_t *)ptr) id;这里的 ptr 是一个非 4 字节对齐的地址!&a…

【构造】P8976 「DTOI-4」排列|普及+

本文涉及知识点 构造 P8976 「DTOI-4」排列 题目背景 Update on 2023.2.1:新增一组针对 yuanjiabao 的 Hack 数据,放置于 #21。 Update on 2023.2.2:新增一组针对 CourtesyWei 和 bizhidaojiaosha 的 Hack 数据,放置于 #22。…

多路I/O转接服务器(select、poll、epoll)

多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。 IO 多路转接方式比较: 常见的 IO 多路转接方式有:select、poll、epoll,他…

最新临时文件快传系统源码 轻量化 带后台

简介: 最新临时文件快传系统源码 轻量化 带后台 首发 轻松上传文件并生成提取码分享给他人,无需注册,方便快捷。 图片:

MyBatis多数据源动态连接工具类实现

这个DatabaseService工具类提供了动态创建MyBatis SqlSession的能力,可以灵活地连接到不同的数据库,非常适合需要动态切换数据源的场景。 package com.cmes.immp.device.utils;import lombok.SneakyThrows; import org.apache.commons.dbcp2.BasicDataS…

用亮数据 MCP 驱动 Trae 智能体:打造高效亚马逊商品采集与分析助手

本文适合希望快速构建数据驱动型智能体的开发者、数据工程师及 AI 产品设计者阅读 并非广告,希望本文可以帮助有需求的同学,祝大家天天开心 在数字时代,数据是决策与洞察趋势的关键。但移动互联网数据获取不易,传统爬虫技术面对复…