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

【Redis实现基础的分布式锁及Lua脚本说明】

使用Redis实现基础的分布式锁及Lua脚本说明

  • 1. 概念
    • 1.1 什么是分布式锁
    • 1.2 什么是Lua脚本
  • 2. 为什么要使用Lua脚本
  • 3.实现分布式锁

1. 概念

1.1 什么是分布式锁

分布式锁是指,在多个服务或节点中的锁机制,用于协调对共享资源的访问。
说白了就是分布式系统中使用的资源锁,防止系统业务发生并发冲突。

举个栗子

比如,去上卫生间(只有一个公共坑位),如果我先进去了上大了,没有锁门,这时你闹肚子也来了,然后一起进来和我抢坑位。。。。。。
实际上分布式系统中的高并发场景中,可能不只是两个三个人去抢一个坑位,最常见的就是淘宝京东的618,双11活动期间的秒杀、减库存场景。
所以如果有一把锁,先得到锁的人入坑,上锁(加锁),其他人就要排队,等在坑里的人大完后打开锁(释放锁)后,后面排队的人才能拿到锁再进坑。

分布式锁主要包括【互斥性】、【防死锁】、【可重入性】、【高性能】、【高可用】等特性。下面一一解释一下各个特性的概念。

【互斥性】:同一时间点,只能有一个客户端可以持有锁,其他客户端得排队等待锁被释放掉后再去争取资源。(同一时间只能有一个人带锁入坑开大!)

【防死锁】:如果,如果说很不巧,持有锁的客户端发生了崩溃,锁是能够自动释放的,不会陷入死锁情况从而导致整个系统也跟着崩了。(你正在坑里开大,上了锁,偷摸吸食然后被自己臭晕了,卫生间的管理员看你半小时还没动静替你找来了120把你拉走,顺便还开了锁让排队的人继续使用)

【可重入性】:同一个客户端可以多次的获取到同一把锁。(你先拿到了锁,进坑开大了,发现没带纸,可以随时在进去)

【高性能】:锁的响应速度要足够快,加解锁操作低延迟。(五秒真男人,开大足够快)

【高可用】:使用集群保证锁服务不会因单点故障不可用。(怕一个坑被你们拉的堵住了,多修了几间卫生间,一个卫生间一把锁)

1.2 什么是Lua脚本

一个轻量级的脚本语言,专门设计用来嵌入到其他程序里,帮你快速扩展功能。比如,游戏里NPC的行为、奶茶店的自动点单系统,甚至Redis的原子操作,都可以用Lua搞定!

举个栗子

你开了一家奶茶店,想让店里的机器人自动做奶茶。
没有Lua的情况下,你得组建一个开发团队,没日没夜的去搞机器人的行为开发,就是嵌入式开发。
有了Lua的情况下,你直接给机器人安装上【Lua软件】,打开app的对话框用几行简单的脚本就能教它:“先加珍珠,再加牛奶,最后摇一摇!”

Lua的核心特点是【轻量级】、【可嵌入性】、【动态类型】、【高效】

【轻量级】:Lua的代码几乎全部是标准C写的,体积小到只有200KB左右。

【可嵌入性】:Lua能直接嵌入C/C++等程序中,甚至Redis和其他数据库中,像插件一样调用。

【动态类型】:Lua的变量类型在运行时自动确定,不用提前声明类型,想换什么内容都行。

【高效】:LuaJIT(即时编译器)能进一步加速执行,速度堪比编译型语言(比如C)。

2. 为什么要使用Lua脚本

在 Redis 中,分布式锁的核心问题是:

加锁操作必须是原子的(即多个命令不能被中断)。
解锁操作也必须是原子的(避免误删其他客户端的锁)。

如果直接使用多个 Redis 命令(如 SETNX + EXPIRE),可能会出现以下问题:

网络延迟:客户端在 SETNX 成功后,还没来得及设置 EXPIRE 就崩溃,导致锁永远不会过期(死锁)。
并发竞争:其他客户端可能在加锁时读取到错误的状态(如未设置超时的锁)。

Lua 脚本的作用:

原子性:Redis 会将整个 Lua 脚本作为一个整体执行,期间不会被其他命令打断。
逻辑封装:在脚本中可以完成复杂的逻辑(如加锁、设置超时、验证标识),避免多条命令之间的竞态条件。

3.实现分布式锁

加锁:
使用Lua脚本实现 SET key NX PX 原子加锁,防止多个客户端同时处理库存(如果键不存在则设置,同时设置过期时间)。

解锁:
使用 Lua 脚本原子性地检查值(客户端唯一标识)并删除锁,防止误删其他客户端的锁,确保原子性。

实例代码:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import java.util.Collections;
import java.util.UUID;/*** @author: gaokelai* @date: 2025/7/29*/
@Service
public class StockService {private final RedisTemplate<String, Object> redisTemplate;public StockService(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}// Lua 脚本常量private static final String LOCK_SCRIPT ="if redis.call('get', KEYS[1]) == nil then " +"   redis.call('set', KEYS[1], ARGV[1], 'nx', 'px', ARGV[2]); " +"   return 1; " +"else " +"   return 0; " +"end";private static final String UNLOCK_SCRIPT ="if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]); " +"else " +"   return 0; " +"end";// 扣减库存逻辑public boolean deductStock(String productId, int count) {String lockKey = "lock:product:" + productId;String clientId = UUID.randomUUID().toString(); // 客户端唯一标识// 加锁(使用 Lua 脚本)Boolean isLocked = redisTemplate.execute(DefaultRedisScript.of(LOCK_SCRIPT, Boolean.class),Collections.singletonList(lockKey),clientId, 30000 // 锁的过期时间(毫秒));if (Boolean.TRUE.equals(isLocked)) {try {// 扣减库存(原子操作)String stockKey = "stock:" + productId;Long currentStock = redisTemplate.opsForValue().decrement(stockKey, count);if (currentStock == null || currentStock < 0) {// 库存不足,回滚redisTemplate.opsForValue().increment(stockKey, count);return false;}return true;} finally {// 解锁(使用 Lua 脚本)redisTemplate.execute(DefaultRedisScript.of(UNLOCK_SCRIPT, Long.class),Collections.singletonList(lockKey),clientId);}} else {return false; // 获取锁失败}}
}

调用实例


// 初始库存设置
redisTemplate.opsForValue().set("stock:product_1001", "10");// 模拟扣减 product_1001 的库存,每次扣 1 件
stockService.deductStock("product_1001", 1); // 多线程模拟并发扣减
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {executor.submit(() -> {boolean result = stockService.deductStock("product_1001", 1);System.out.println("扣减结果: " + result);});
}
executor.shutdown();
http://www.lryc.cn/news/603272.html

相关文章:

  • 使用 Canvas 替代 <video> 标签加载并渲染视频
  • 【深度学习】独热编码(One-Hot Encoding)
  • 怎么提升服务器的防攻击能力!
  • day064-kodbox接入对象存储与配置负载均衡
  • 「源力觉醒 创作者计划」 百度AI的战略“惊蛰”,一场重塑格局的“破壁行动”
  • JSON在java中的使用
  • 力扣热题100--------240.搜索二维矩阵
  • 半导体企业选用的跨网文件交换系统到底应该具备什么功能?
  • Spring Boot 请求限流实战:基于 IP 的高效防刷策略
  • Qt 并行计算框架与应用
  • 重塑浏览器!微软在Edge加入AI Agent,自动化搜索、预测、整合
  • [明道云]-基础教学2-工作表字段 vs 控件:选哪种?
  • nodejs 实现Excel数据导入数据库,以及数据库数据导出excel接口(核心使用了multer和node-xlsx库)
  • 架构实战——互联网架构模板(“用户层”和“业务层”技术)
  • 向量内积:揭示方向与相似性的数学密码
  • 瑞盟NFC芯片,MS520
  • 网上买卖订单处理手忙脚乱?订单处理工具了解一下
  • Radash.js 现代化JavaScript实用工具库详解 – 轻量级Lodash替代方案
  • python优秀案例:基于机器学习算法的景区旅游评论数据分析与可视化系统,技术使用django+lstm算法+朴素贝叶斯算法+echarts可视化
  • 机器学习、深度学习与数据挖掘:三大技术领域的深度解析
  • uipath数据写入excel的坑
  • perf工具在arm上的安装记录
  • 机器学习、深度学习与数据挖掘:核心技术差异、应用场景与工程实践指南
  • p5.js 从零开始创建 3D 模型,createModel入门指南
  • 新升级超值型系列32位单片机MM32G0005
  • p5.js 三角形triangle的用法
  • 逻辑回归算法
  • [源力觉醒 创作者计划]_文心大模型4.5开源:从技术突破到生态共建的国产AI解读与本地部署指南
  • 单片机学习笔记.PWM
  • hive专题面试总结