一.分布式锁的介绍
定义:
分布式锁是满足在分布式系统或集群模式下,实现多进程可见并且互斥的锁机制。
核心特性(基于图示):
二.基于Redis实现分布式锁
<1>原理分析
在分布式系统中,为了保证多个节点对共享资源的互斥访问,常使用 Redis 实现分布式锁。其核心是实现两个基本方法:获取锁 和 释放锁。
1、获取锁
- 互斥性:确保同一时刻只有一个线程能成功获取锁。
- 非阻塞尝试:尝试一次,成功返回 true,失败返回 false,不等待。
命令
SET lock thread1 NX EX 10
- NX:仅当 key 不存在时才设置 → 保证互斥
- EX 10:设置过期时间为 10 秒 → 防止死锁
2、释放锁
- 手动释放:业务执行完成后主动删除锁。
- 超时自动释放:通过设置过期时间,即使服务宕机或业务超时,锁也会自动失效,避免死锁。
命令
DEL key
<2>流程图

<3>代码实现Redis锁—版本1
获取锁与删除锁代码
public class SimpleRedisLock implements ILock{
private String name;
private StringRedisTemplate stringRedisTemplate;
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
private static final String KEY_PREFIX = "lock:";
@Override
public boolean tryLock(long timeoutSec) {
String threadId = String.valueOf(Thread.currentThread().getId());
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return BooleanUtil.isTrue(success);
}
@Override
public void unlock() {
//删除锁
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
将锁添加进业务中
//5.1获取用户id
Long UserId = UserHolder.getUser().getId();
//创建锁对象
SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order:" + UserId, stringRedisTemplate);
//获取锁
boolean isLock = simpleRedisLock.tryLock(1200);
//判断获取锁成功
if (!isLock) {
return Result.fail("不允许重复下单");
}
//获取代理对象
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId, voucher);
<3>解决Redis分布锁中的误删问题
1.产生原因以及解决办法
误删问题的核心原因是:锁的持有者(线程)与释放者不一致,且缺乏身份校验机制。
具体产生场景如下:
后果:线程 B 失去保护,此时若有线程 C 进来,就能和线程 B 同时访问临界资源,导致数据竞争和不一致。
根本症结:释放锁时,没有判断“当前要删的锁是不是我自己加的那把”。

解决办法

线程1先成功获取锁并执行业务,但在完成前锁因超时被自动释放;此时它尝试释放锁时发现锁已不属于自己(返回NIL),于是不操作。随后线程2趁机获取到新锁,正常执行业务后校验归属一致,安全释放锁。接着线程3也顺利拿到锁开始执行。
2.代码实现
改进Redis的分布式锁
需求:修改之前的分布式锁实现,满足:
- 如果一致则释放锁
- 如果不一致则不释放锁
//获取锁
@Override
public boolean tryLock(long timeoutSec) {
String threadId = ID_PREFIX + String.valueOf(Thread.currentThread().getId());
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return BooleanUtil.isTrue(success);
}
//删除锁
@Override
public void unlock() {
//获取线程标识
String threadId = ID_PREFIX + String.valueOf(Thread.currentThread().getId());
//获取锁中的标识
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
//判断标识是一致
if (threadId.equals(id)) {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
<4>分布式锁的原子性问题
1.产生原因

产生原因
→ 结果:线程B的锁被A提前删掉,后续线程C可立即抢锁,导致并发失控!
2.解决办法—-Redis的Lua脚本
Lua脚本简介
Redis 提供了 Lua 脚本功能,在一个脚本中编写多条 Redis 命令,确保多条命令执行时的原子性。 Lua 是一种编程语言,基本语法可参考:https://www.runoob.com/lua/lua-tutorial.html
这里重点介绍调用函数语法
redis.call('命令名称', 'key', '其它参数', …)
示例一:执行 set name jack
redis.call('set', 'name', 'jack')
示例 2:先 set name Rose,再 get name
redis.call('set', 'name', 'Rose')
local name = redis.call('get', 'name')
return name
写好脚本以后,需要用 Redis 命令来调用脚本,调用脚本的常见命令如下:
127.0.0.1:6379> help @scripting
EVAL script numkeys key [key …] arg [arg …]
summary: Execute a Lua script server side
since: 2.6.0
例如,我们要执行 redis.call('set', 'name', 'jack') 这个脚本,语法如下:
# 调用脚本
EVAL "return redis.call('set', 'name', 'jack')" 0
- 脚本内容:"return redis.call('set', 'name', 'jack')"
- Key 参数个数:0(表示脚本中没有使用 KEYS 数组类型的参数)
如果脚本中的 key、value 不想写死,可以作为参数传递。
- key 类型参数会放入 KEYS 数组。
- 其它参数会放入 ARGV 数组。
- 在脚本中可以从 KEYS 和 ARGV 数组获取这些参数。
示例代码
# 调用脚本
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Rose
3.代码实现
释放锁的业务流程是这样的:
用 Lua 脚本表示如下:
— 这里的 KEYS[1] 就是锁的 key,这里的 ARGV[1] 就是当前线程标示
— 获取锁中的标示,判断是否与当前线程标示一致
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
— 一致,则删除锁
return redis.call('DEL', KEYS[1])
end
— 不一致,则直接返回
return 0
我们要在idea中集成一个EmmyLua插件
然后创建这个文件

将lua脚本写入文件中
— 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
— 释放锁 del key
return redis.call('del', KEYS[1])
end
return 0
完整代码
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public void unlock() {
stringRedisTemplate.execute(UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + String.valueOf(Thread.currentThread().getId()));
}





