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

redis在Spring中应用相关

local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];-- lockname不存在
if(redis.call('exists', key) == 0) thenredis.call('hset', key, threadId, '1');redis.call('expire', key, releaseTime);return 1;
end;-- 当前线程已id存在
if(redis.call('hexists', key, threadId) == 1) thenredis.call('hincrby', key, threadId, '1');redis.call('expire', key, releaseTime);return 1;
end;
return 0;
    /*** 获取锁*/public String tryLock(String lockName, long releaseTime) {// 存入的线程信息的前缀String key = UUID.randomUUID().toString();// 执行脚本Long result = (Long) redisTemplate.execute(lockScript,Collections.singletonList(lockName),key + Thread.currentThread().getId(),releaseTime);if (result != null && result.intValue() == 1) {return key;} else {return null;}}

这里lua脚本中,KEYS[1]和上面execute参数对应关系是什么?同时 Collections.singletonList(lockName)解释下这个

好问题 👍,你这个 Lua 脚本和 Java 调用里有两个核心点需要搞清楚:


1️⃣ KEYS 和 ARGV 的对应关系

在 Redis 的 Lua 脚本执行机制里,参数分为两类

  • KEYS:传入的 key 列表(由 Redis 官方约定必须放在 KEYS,便于集群模式下路由到正确的槽位)
  • ARGV:传入的 普通参数(值、过期时间、线程 id 等)

调用时:

redisTemplate.execute(script, keys, args...)
  • keys → 会传给 Lua 脚本里的 KEYS 数组
  • args... → 会传给 Lua 脚本里的 ARGV 数组

2️⃣ 你的例子对应关系

Lua 脚本:

local key = KEYS[1];
local threadId = ARGV[1];

Java 调用:

Long result = (Long) redisTemplate.execute(lockScript,Collections.singletonList(lockName),   // 传 KEYSkey + Thread.currentThread().getId(), // ARGV[1]releaseTime                            // ARGV[2]
);

对应关系:

  • KEYS[1] = lockName (就是 Redis 的锁名,比如 "myLock"
  • ARGV[1] = key + Thread.currentThread().getId() (线程唯一标识,用来判断是不是自己的锁)
  • ARGV[2] = releaseTime (锁的过期时间,脚本里可能用来刷新 TTL)

3️⃣ Collections.singletonList(lockName) 的意思

在 Java 里:

Collections.singletonList(lockName)
  • 创建一个 只包含一个元素的不可变 List
  • 这里就是 [lockName]

因为 execute() 的第二个参数要求是一个 List<K>,即 可能有多个 Redis key,所以就用 singletonList 来包装成一个单元素的 List。


✅ 总结

  • KEYS 对应 Java 里传的第二个参数(List 类型)
  • ARGV 对应 Java 里后面跟着的可变参数(Object… args)
  • Collections.singletonList(lockName):只是把一个锁名包装成单元素 List,以便传到 Lua 脚本的 KEYS[1]

下图并发问题解释

在这里插入图片描述
很好 👍 你写的这个 1.0 版本的 Redis 分布式锁思路已经很对了:

  • tryLock():用 SETNX + EXPIRE 实现加锁(Spring Data Redis 封装成 setIfAbsent)。
  • unlock():解锁时通过 校验 value 来防止误删别人的锁。

但是小张指出的问题也对:


🚨 问题:解锁非原子性

你现在的 unlock 分两步:

  1. get(lockName)
  2. del(lockName)

这两个操作中间 不是原子操作。可能会发生:

  • 线程 A 执行 get,发现是自己锁,准备执行 del
  • 此时锁过期了,线程 B 成功 tryLock,重新设置了锁
  • 线程 A 继续执行 del,把 线程 B 的锁给删掉了

这就是并发场景下的 锁误删问题


✅ 解决方案:Lua 脚本

Redis 本身是单线程执行命令的,所以如果把 get + del 合并成一个原子脚本,就不会有问题。

Lua 脚本可以让 Redis 保证这两个操作在一次执行过程中不被打断。

典型 Lua 脚本:

if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
elsereturn 0
end

含义:

  • KEYS[1]:锁的 key
  • ARGV[1]:传入的 value(uuid)
  • 如果锁的 value 和传入的 uuid 一致 → 执行 del 删除
  • 否则返回 0,不做删除

这样整个检查 + 删除就是 Redis 内部原子执行,不会被其它线程打断。


📌 Spring Data Redis 使用 Lua 解锁

private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 0 " +"end");UNLOCK_SCRIPT.setResultType(Long.class);
}public void unlock(String key, String uuid) {redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(key), uuid);
}

🎯 总结

  • 1.0 版本的问题:get + del 不是原子操作,可能误删他人锁。
  • 用 Lua 脚本:在 Redis 内部一次性完成 比较 + 删除,避免竞态条件。
  • 这是分布式锁里 解锁安全性 的核心改进。

引用大佬原文: 掘金https://juejin.cn/post/6961380552519712798#heading-5

http://www.xdnf.cn/news/1340209.html

相关文章:

  • Django ModelForm
  • C#基础编程核心知识点总结
  • 打破传统课程模式,IP变现的创新玩法 | 创客匠人
  • RabbitMQ面试精讲 Day 26:RabbitMQ监控体系建设
  • 从零开始的Agent学习(二)-增加文档输出功能
  • 36、供应链计划与执行优化 (军方后勤) - /供应链管理组件/military-logistics-scm
  • 34、扩展仓储管理系统 (跨境汽车零部件模拟) - /物流与仓储组件/extended-warehouse-management
  • 3D 环形旋转图片轮播(纯html,css,js)
  • 力扣hot100:无重复字符的最长子串,找到字符串中所有字母异位词(滑动窗口算法讲解)(3,438)
  • 从零开始理解 K 均值聚类:原理、实现与应用
  • 自学嵌入式第二十四天:数据结构(4)-栈
  • linux-ubuntu里docker的容器portainer容器建立后如何打开?
  • WSL的Ubuntu如何改名字
  • Ubuntu网络图标消失/以太网卡显示“未托管“
  • java项目:如何优化JVM参数?
  • nginx-自制证书实现
  • 读《精益数据分析》:精益画布——创业与产品创新的高效工具
  • 【工具】前端JS/VUE修改图片分辨率
  • 使用Docker部署Coze Studio开源版
  • Advanced Math Math Analysis |02 Limits
  • Oracle CLOB类型转换
  • k8s下的网络通信与认证
  • 【C++】模板(进阶)
  • 从YOLOv5到RKNN:零冲突转换YOLOv5模型至RK3588 NPU全指南
  • 在线课程|基于SprinBoot+vue的在线课程管理系统(源码+数据库+文档)
  • openEuler系统中如何将docker安装在指定目录
  • ES_文档
  • 【数据结构】树与二叉树:结构、性质与存储
  • 牛客:链表的回文结构详解
  • 牛客:链表分割算法详解