当前位置: 首页 > 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.dtcms.com/a/342968.html

相关文章:

  • LeetCode算法日记 - Day 17: 算法中的位运算技巧总结
  • 【黑客技术零基础入门】硬核科普什么是HTMLHTML基本结构以及HTML基本使用(非常详细)零基础入门到精通,收藏这一篇就够了!
  • 轻量级加密的下一站:后量子、AI 与自动化验证
  • 【iOS】SDWebImage第三方库源码学习笔记
  • JupyterLab 安装(python3.10)
  • 大模型之原理篇——Transformer基础、分词器
  • 深度剖析:PCB 厚铜板铜厚检测,铜厚不足的连锁反应及检测手段
  • 性能测试中性能分析与调优学习大纲整理
  • C++中纯虚函数与普通虚函数的深度解析
  • 面试紧张情绪管理:如何保持冷静自信应对挑战
  • CLAUDE.md文件介绍(Claude Code核心配置文件,开始对话或执行任务时自动加载的上下文文件)
  • 工业大模型的应用场景
  • Ubuntu22.04设置共享文件夹
  • 2025年渗透测试面试题总结-25(题目+回答)
  • 数据库运维管理平台全面解析
  • opencv学习:图像边缘检测
  • # 重磅发布 | onecode 3.0.1 Base 源码正式开源:AI赋能的企业级开发框架
  • 算法训练营day58 图论⑧ 拓扑排序精讲、dijkstra(朴素版)精讲
  • 从零开始的Agent学习(二)-增加文档输出功能
  • 医疗信创新征程:常德二院全栈国产化项目引领行业变革
  • 审美积累 | 界面设计拆分 | Redesign Health - Services 医疗页面设计
  • 8.21网络编程——词典(未完成,有问题)
  • kotlin协程笔记-朱凯
  • C# 基本数据类型
  • 生信分析自学攻略 | R语言数据筛选和修改
  • 前端:文件直接在浏览器里下载
  • VMware ESXi 服务器暴露高危漏洞,中国1700余台面临勒索软件威胁
  • UE 虚幻引擎, unreal engine(1)概略介绍,安装本引擎,创建账户,打开 UE,创建项目,项目导入内容,尝试运行的添加第一人称游戏,
  • Vibe Coding v.s Prompt Engineering
  • 【Docker】在Ubuntu22.04上安装Docker