redis_点评(14.优惠券-java调用lua脚本改造分布式锁)

发布时间:2026/6/12 17:12:26
redis_点评(14.优惠券-java调用lua脚本改造分布式锁) 这次改造的核心目标是把「解锁时的非原子操作」改成「原子操作」彻底解决分布式锁的「判断 删除」时序问题。一、先回顾旧版解锁的致命缺陷旧版解锁逻辑public void unlock() { // 1. 获取当前线程标识 String threadId ID_PREFIX Thread.currentThread().getId(); // 2. 从Redis获取锁标识get操作 String id stringRedisTemplate.opsForValue().get(KEY_PREFIX name); // 3. 判断标识是否一致 if (threadId.equals(id)) { // 4. 删除锁delete操作 stringRedisTemplate.delete(KEY_PREFIX name); } }问题出在get和delete是两次独立的 Redis 命令中间存在时间窗口。二、用 Lua 脚本实现原子解锁Lua 脚本的特性Redis 会将整个脚本作为一个原子操作执行中间不会被其他命令打断。新增 Lua 脚本配置// 定义解锁脚本 private static final DefaultRedisScriptLong UNLOCK_SCRIPT; static { UNLOCK_SCRIPT new DefaultRedisScript(); // 加载resources下的unlock.lua文件 UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua)); // 指定脚本返回值类型 UNLOCK_SCRIPT.setResultType(Long.class); }DefaultRedisScriptSpring Data Redis 提供的脚本执行器static静态块类加载时就加载脚本避免每次解锁都重新读取文件unlock.lua存储解锁逻辑的 Lua 脚本文件替换 Java 解锁逻辑为 Lua 脚本调用Override public void unlock() { // 调用Lua脚本执行解锁 stringRedisTemplate.execute( UNLOCK_SCRIPT, // 脚本对象 Collections.singletonList(KEY_PREFIX name), // KEYS参数锁的key ID_PREFIX Thread.currentThread().getId() // ARGV参数当前线程标识 ); }1、stringRedisTemplate.execute(...)作用Spring 提供的执行 Redis Lua 脚本的核心方法特点Redis 会把整个脚本当作一条原子命令执行不会被其他命令打断2、UNLOCK_SCRIPT提前加载好的Lua 解锁脚本3.Collections.singletonList(KEY_PREFIX name)传给 Lua 的KEYS 数组参数内容lock:order:userId锁的 Redis Key必须用 List 集合是 Redis 脚本的参数规范4.ID_PREFIX Thread.currentThread().getId()传给 Lua 的ARGV 参数内容当前线程的唯一标识UUID 线程 ID用于 Lua 脚本判断这把锁是不是当前线程加的三、unlock.lua脚本内容-- 比较线程标示与锁中的标示是否一致 if (redis.call(get, KEYS[1]) ARGV[1]) then -- 释放锁标识一致删除锁 return redis.call(del, KEYS[1]) end -- 标识不一致直接返回 return 0redis.call(get, KEYS[1])获取锁的标识原子操作 ARGV[1]和当前线程的标识对比原子操作redis.call(del, KEYS[1])如果一致删除锁原子操作总结通过 Lua 脚本将「判断锁标识 删除锁」合并为 Redis 原子操作彻底解决旧版 Java 解锁逻辑中「非原子操作导致的锁误删问题」让分布式锁的解锁过程 100% 安全可靠。