目录

1. 什么是分布式锁?

分布式锁的核心要求

2. 基于Redis的分布式锁实现方案

(1)基础方案:SETNX + EXPIRE

(2)优化方案:SET NX PX(原子性加锁)

(3)进阶方案:RedLock(Redis官方推荐)

3. 实战案例

案例1:防止重复下单

1. 加锁阶段

2. 业务逻辑阶段

3. 释放锁阶段

案例2:秒杀库存扣减

案例3:分布式定时任务调度

4. 常见问题与解决方案

(1)锁过期但业务未执行完?

(2)锁被其他客户端误删?

(3)Redis主从切换导致锁丢失?

5. 总结


1. 什么是分布式锁?

在分布式系统中,多个服务实例可能同时访问共享资源(如数据库、缓存等),为了避免并发问题(如超卖、重复提交等),我们需要一种跨JVM的锁机制——分布式锁

分布式锁的核心要求

  1. 互斥性:同一时刻只有一个客户端能持有锁。

  2. 防死锁:即使客户端崩溃,锁也能自动释放。

  3. 高可用:锁服务必须高可用(如Redis集群)。

  4. 可重入性(可选):同一个客户端可以多次获取同一把锁。


2. 基于Redis的分布式锁实现方案

Redis因其高性能和原子性操作(如SETNX),成为实现分布式锁的常用方案。

(1)基础方案:SETNX + EXPIRE

// 加锁(错误示范,非原子性)
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order123", "1");
if (locked) {redisTemplate.expire("lock:order123", 10, TimeUnit.SECONDS);  // 设置过期时间// 执行业务逻辑...redisTemplate.delete("lock:order123");  // 释放锁
}

问题SETNXEXPIRE不是原子操作,如果加锁后客户端崩溃,锁永远不会释放!


(2)优化方案:SET NX PX(原子性加锁)

Redis 2.6+ 支持SET命令的NX(不存在才设置)和PX(毫秒级过期时间)参数:

// 正确方式:原子性加锁 + 设置过期时间
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order123", "client1", 10, TimeUnit.SECONDS
);if (locked) {try {// 执行业务逻辑...} finally {// 释放锁(需判断是否是自己加的锁)if ("client1".equals(redisTemplate.opsForValue().get("lock:order123"))) {redisTemplate.delete("lock:order123");}}
}

关键改进

  • 使用SET NX PX保证原子性。

  • 设置唯一标识(如client1),避免误删其他客户端的锁。


(3)进阶方案:RedLock(Redis官方推荐)

如果单点Redis不可靠,可以使用RedLock算法(需多个独立Redis实例):

// RedLock示例(使用Redisson客户端)
RLock lock1 = redissonClient1.getLock("lock:order123");
RLock lock2 = redissonClient2.getLock("lock:order123");
RLock lock3 = redissonClient3.getLock("lock:order123");RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {if (redLock.tryLock(10, 30, TimeUnit.SECONDS)) {  // 最多等待10秒,锁30秒后自动过期// 执行业务逻辑...}
} finally {redLock.unlock();
}

适用场景:对一致性要求极高的场景(如金融交易)。


3. 实战案例

案例1:防止重复下单

public String createOrder(String userId, String productId) {String lockKey = "lock:order:" + userId + ":" + productId;String clientId = UUID.randomUUID().toString();  // 唯一标识try {// 尝试加锁(等待5秒,锁10秒后自动释放)Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);if (!locked) {throw new RuntimeException("操作太频繁,请稍后再试!");}// 检查是否已下单if (orderService.hasOrder(userId, productId)) {throw new RuntimeException("请勿重复下单!");}// 创建订单...return orderService.create(userId, productId);} finally {// 释放锁(需校验clientId)if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}}
}

这段代码实现了一个防并发重复下单的订单创建逻辑,核心是使用Redis分布式锁来保证同一用户对同一商品的订单操作是串行化的。下面逐部分解析:


1. 加锁阶段
String lockKey = "lock:order:" + userId + ":" + productId;
String clientId = UUID.randomUUID().toString();
  • lockKey:锁的键,格式为lock:order:{userId}:{productId},确保不同用户或不同商品的锁互不影响。

  • clientId:生成唯一标识(UUID),用于后续校验锁的归属,防止误删其他客户端的锁。

Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS
);
  • setIfAbsent:Redis的SETNX命令(原子性操作),如果lockKey不存在则加锁,并设置:

    • clientId(锁的持有者标识)。

    • 过期时间:10秒(防止死锁)。

  • 返回值true表示加锁成功,false表示锁已被占用。

if (!locked) {throw new RuntimeException("操作太频繁,请稍后再试!");
}
  • 如果加锁失败,直接抛出异常,提示用户"操作太频繁"(类似秒杀场景的限流)。


2. 业务逻辑阶段
if (orderService.hasOrder(userId, productId)) {throw new RuntimeException("请勿重复下单!");
}
  • 检查是否已下单:在锁的保护下查询订单系统,防止重复下单(即使通过了前端校验,仍需后端保证幂等性)。

return orderService.create(userId, productId);
  • 创建订单:执行业务逻辑(如扣减库存、生成订单等)。


3. 释放锁阶段
finally {if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}
}
  • finally:确保锁一定会被释放,即使业务逻辑抛出异常。

  • 校验clientId

    • 只释放自己加的锁(避免误删其他客户端的锁)。

    • 如果锁已自动过期(10秒后),get操作返回null,不会执行删除。

  • 原子性问题

    • 这里的getdelete是两步操作,非原子性,极端情况下可能误删锁(如锁过期后,其他客户端加锁成功,但当前线程仍执行删除)。

    • 改进方案:使用Lua脚本保证原子性(见下文补充)。


案例2:秒杀库存扣减

public boolean seckill(Long productId, Long userId) {String lockKey = "lock:seckill:" + productId;String stockKey = "stock:" + productId;String clientId = UUID.randomUUID().toString();try {// 加锁(防止超卖)Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 3,  // 锁3秒(避免长时间阻塞)TimeUnit.SECONDS);if (!locked) {return false;  // 抢锁失败}// 检查库存Integer stock = Integer.valueOf(redisTemplate.opsForValue().get(stockKey));if (stock <= 0) {return false;  // 已售罄}// 扣减库存(原子操作)redisTemplate.opsForValue().decrement(stockKey);// 生成订单...orderService.createSeckillOrder(userId, productId);return true;} finally {// 释放锁if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}}
}

案例3:分布式定时任务调度

多个服务实例同时运行定时任务时,需确保只有一个实例执行:

@Scheduled(cron = "0 */5 * * * ?")  // 每5分钟执行一次
public void scheduledTask() {String lockKey = "lock:scheduled:report";String clientId = "server-" + System.getProperty("server.port");  // 用服务实例标识try {// 尝试加锁(锁5分钟)Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 5, TimeUnit.MINUTES);if (!locked) {return;  // 其他实例已执行}// 执行业务逻辑(生成报表...)reportService.generateDailyReport();} finally {// 释放锁if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.delete(lockKey);}}
}

4. 常见问题与解决方案

(1)锁过期但业务未执行完?

  • 问题:锁自动释放后,其他客户端可能获取锁,导致并发问题。

  • 解决方案:使用看门狗机制(如Redisson的lockWatchdogTimeout),自动续期锁。

(2)锁被其他客户端误删?

  • 问题:客户端A释放了客户端B的锁。

  • 解决方案:加锁时设置唯一标识(如UUID),释放时校验。

(3)Redis主从切换导致锁丢失?

  • 问题:主节点加锁后崩溃,从节点晋升但未同步锁数据。

  • 解决方案:使用RedLock(多Redis实例)或ZooKeeper替代。


5. 总结

方案优点缺点适用场景
SETNX + EXPIRE简单高效非原子性,可能死锁低并发场景
SET NX PX原子操作单点故障一般分布式系统
RedLock高可用实现复杂金融级高一致性场景

最佳实践

  • 优先使用SET NX PX + 唯一标识。

  • 高可用场景选择Redisson的RLock或RedLock。

  • 结合业务设置合理的锁超时时间。

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

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

相关文章

【能碳建设1】用AI+开源打造物联网+能碳管理+交易SaaS系统的最短路径实施指南

摘要 本指南为技术小白设计,目标是在最短时间内利用AI工具与开源系统,独立完成一套物联网平台 + 能碳管理平台 + 碳交易系统的SaaS最小可用版本(MVP),并可后续扩展。流程分为目标定义、技术选型、环境搭建、核心功能开发、SaaS化、多租户、上线运维等环节,按天推进,每步…

CVPR中深度学习新范式:通用性、鲁棒性与多模态的创新突破

来gongzhonghao【图灵学术计算机论文辅导】&#xff0c;快速拿捏更多计算机SCI/CCF发文资讯&#xff5e;分享一个深度学习领域正在迅速升温的前沿方向&#xff1a;通用性与鲁棒性的深度神经网络架构创新。随着大模型在视觉、文本乃至多模态任务中的广泛应用&#xff0c;体现出深…

Vue3 学习教程,从入门到精通,Vue 3 + Tailwind CSS 全面知识点与案例详解(31)

Vue 3 Tailwind CSS 全面知识点与案例详解一、Vue 3 核心语法知识点 1. Vue 3 基础 创建 Vue 3 项目 使用 Vite 创建项目&#xff1a;npm create vuelatest # 选择需要的特性&#xff08;如 TypeScript、Vue Router&#xff09;响应式数据 使用 ref 和 reactive&#xff1a;im…

Android中RecyclerView基本使用

一、RecyclerView 核心概念1. 基本组件关系2. 核心组件作用Adapter&#xff1a;数据与视图的桥梁LayoutManager&#xff1a;控制布局方式&#xff08;线性/网格/瀑布流&#xff09;ViewHolder&#xff1a;缓存视图组件ItemDecoration&#xff1a;添加分割线等装饰ItemAnimator&…

A100用transformers推理gpt-oss

A100本地用transformers推理gpt-oss GPT-oss试用 gpt-oss有两个原生配置是目前&#xff08;2025-8-8&#xff09;Ampere系列显卡不支持的&#xff0c;分别是默认的MXFP4量化&#xff0c;以及Flash-attn V3。tranformers推理是比较慢的&#xff0c;可以用于研究模型本身&#x…

虚拟手机号工具使用

背景&#xff1a;注册部分国外应用时需要国外手机号验证&#xff0c;例如在注册cursor时需要国外手机号 解决&#xff1a;使用虚拟手机号网页进行验证 https://temp-number.com/ 选择自己需要的国家 选择一个手机号 复制手机号到自己的app注册页面 并发送消息&#xff0c;然后…

【线程池】压测确定线程池合适的参数

【线程池】压测确定线程池合适的参数【一】案例说明【二】明确线程池核心参数及优化目标【1】线程池核心参数&#xff08;需压测验证的关键参数&#xff09;【2】优化目标【三】压测前准备【1】环境搭建【2】线程池初始配置&#xff08;基于经验值&#xff09;【3】压测工具与监…

GPT OSS 双模型上线,百度百舸全面支持快速部署

GPT OSS 是 OpenAI 推出的重量级开放模型&#xff0c;专为强推理能力、智能体任务及多样化开发场景设计&#xff0c;标志着大模型在开放性与实用性上的重要突破。该系列包含两款高性能模型&#xff1a;参数规模为 117B 的 GPT‑OSS‑120B 和 21B 的 GPT‑OSS‑20B。二者皆采用 …

C++高频知识点(十七)

文章目录81. 你对智能指针的了解82. 一元、二元仿函数的区别和使用背景一元仿函数二元仿函数83. 描述Linux下文件删除的原理84. 什么是菱形继承&#xff1f;有什么问题&#xff0c;怎么解决&#xff1f;解决菱形继承问题85. IO多路复用是什么&#xff1f;selectpollepollselect…

如何优雅的使用进行参数校验

在spring里面有一个注解 Validated可以在方法的入参里面这样写//方法 getActivityFlag(RequestBody Validated QueryActivityDto queryActivityDto) //参数详情NotBlank(message "userId不能为空")private String userId;NotNull(message "storeId不能为空&q…

Java学习第一百一十部分——CI/CD

目录 一、前言简介 二、基本信息 三、优势价值 四、核心流程 五、技术栈&#xff08;工具矩阵&#xff09; 六、最佳实践 七、与DevOps关系 八、挑战对策 九、使用建议 十、总结归纳 一、前言简介 CI/CD 的本质是&#xff1a;通过自动化流水线&#xff0c;实现代码从提…

关于 Cocoapods 使用

一、Podfile & .podspec 文件 1、Podfile 1.1. 什么是 pod 简单来说&#xff0c;一个 pod 就是 xcode 里面的一个 dependency&#xff1a; Anyway&#xff0c;pod 就是第三方库的意思。一个 pod 就是指一个第三方库。 1.2. Podfile 有什么用 Podfile 可以理解为就是…

编程速递:2025 年巴西 Embarcadero 会议,期待您的到来

每个英雄都有一段充满奋斗的旅程&#xff0c;这段旅程引领他走向荣耀&#xff0c;而开发者英雄的旅程是2025年巴西Embarcadero大会的重点&#xff0c;以庆祝Delphi成立30周年。网站现已上线巴西Embarcadero在世界上最受期待的Delphi发展英雄会议召开前90天&#xff0c;推出了Em…

DevOps简单教程应用

文章目录概念一、环境准备二、gitlab配置三、.gitlab-ci.yml文件配置概念 Devops是一个概念&#xff0c;就是边开发边测试&#xff0c;能够大大提升开发效率&#xff0c;本文使用pycharmgitlab实现一个简单的DevOps流程 一、环境准备 需要一个测试环境&#xff0c;模拟部署&…

华为流程管理体系构建与落地 之—— 业务流程规划【附全文阅读】

这部分内容聚焦华为业务流程管理&#xff0c;详细阐述了流程规划、设计、运营、评估与优化的具体方法和内容&#xff0c;为企业构建和完善流程管理体系提供了全面的指导。流程规划分类方法&#xff1a;介绍 POS、OES、OMS 等分类法&#xff0c;如 POS 法按规划、运营、支持划分…

Android 项目:画图白板APP开发(零)——功能介绍(笔锋,分页,缩放,多指,硬件加速等)

一、前言 本系列将全面的介绍一些有关Android 画图方面的知识。笔触功能包括&#xff1a;颜色、粗细、透明度、笔锋、橡皮&#xff1b;绘图功能包括&#xff1a;分页、缩放、多指、撤销恢复、笔画加速。别看功能这么多&#xff0c;简单的部分会花较少篇幅介绍&#xff0c;着重会…

香橙派 RK3588 部署千问大模型 Qwen2-VL-2B 推理视频

演示视频 香橙派RK3588部署千问大模型Qwen2-VL-2B推理视频一、场景假设 视频输入为一条网络流&#xff0c;利用大模型对视频中的图像帧进行推理。由于大模型推理耗时长&#xff0c;无法对每帧都进行推理&#xff0c;因此采用跳帧推理的方式&#xff1a;当推理完一帧后&#xf…

排序概念以及插入排序

一、排序基本概念1.就地排序&#xff1a;使用恒定的额外空间来产生输出就地排序只是在原数组空间进行排序处理&#xff0c;也就是输入的数组和得到的数组是同一个2.内部排序和外部排序&#xff1a;待排序数据可以一次性载入到内存中为内部排序&#xff0c;反之数据量过大就是外…

Webpack 核心配置与最佳实践指南

Webpack 是现代前端工程化的核心工具,理解其配置原理和优化技巧对开发效率至关重要。 一、Webpack 基础架构 1、核心概念关系图 2、核心概念详解 概念 作用 示例配置 Entry 应用入口起点 entry: ‘./src/index.js’ Output 编译结果输出位置 output.path: path.resolve(__d…

GISBox私有云+SaaS:安全协同的地理智能平台

一、概述 GISBox&#xff08;GIS 工具箱&#xff09;是一套能够对GIS 影像、地形、倾斜摄影进行场景编辑、切片转化、分发服务的 GIS 工具箱。同时&#xff0c;GISBox还支持私有云并一键开启SaaS服务。 二、什么是私有云&#xff1f; 私有云服务是一种为企业或组织量身定制的…