一、基于Redis实现分布式锁
(1)基于Lua脚本(包含SETNX + EXPIRE两条指令)实现分布式锁
// 尝试获取锁
public boolean tryLock(String lockKey, String lockValue, int expireTime) {
// Lua 脚本
String luaScript =
"if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
" redis.call('expire', KEYS[1], ARGV[2]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
// 调用 Lua 脚本
Object result = jedis.eval(luaScript,
Collections.singletonList(lockKey),
Arrays.asList(lockValue, String.valueOf(expireTime)));
// 判断是否成功获取锁
return result.equals(1L);
}
// 释放锁
public void releaseLock(String lockKey) {
jedis.del(lockKey);
}
(2)基于SET的扩展命令(SET EX PX NX)实现分布式锁
SET命令扩展命令
SET key value [EX seconds | PX milliseconds] [NX | XX]
- NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
- EX seconds :设定key的过期时间,时间单位是秒。
- PX milliseconds: 设定key的过期时间,单位为毫秒
- XX: 仅当key存在时设置值
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class RedisDistributedLock {
/**
* 尝试获取锁
*
* @param lockKey 锁的键
* @param lockValue 锁的值(通常使用唯一值来确保只有持有该值的客户端才能释放锁)
* @param expireTime 锁的过期时间(单位:秒)
* @return true 表示获取锁成功,false 表示获取锁失败
*/
public boolean tryLock(String lockKey, String lockValue, int expireTime) {
String result = jedis.set(lockKey, lockValue, "NX", "EX", expireTime);
return "OK".equals(result); // 返回 "OK" 表示成功获取锁
}
/**
* 释放锁
*
* @param lockKey 锁的键
* @param lockValue 锁的值,确保只有持有该值的客户端才能释放锁
*/
public void releaseLock(String lockKey, String lockValue) {
// Lua 脚本确保只有持有该值的客户端才能释放锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
jedis.eval(luaScript, 1, lockKey, lockValue);
}
public static void main(String[] args) {
// 创建 Jedis 实例连接到 Redis
Jedis jedis = new Jedis("localhost");
// 实例化 RedisDistributedLock
RedisDistributedLock lock = new RedisDistributedLock(jedis);
String lockKey = "lock:resource"; // 锁的键
String lockValue = UUID.randomUUID().toString(); // 使用 UUID 生成唯一的锁值
int expireTime = 10; // 锁的过期时间,单位为秒
// 尝试获取锁
if (lock.tryLock(lockKey, lockValue, expireTime)) {
try {
System.out.println("锁获取成功,执行操作...");
// 执行需要加锁的操作
// ...
} finally {
// 释放锁
lock.releaseLock(lockKey, lockValue);
System.out.println("锁已释放");
}
} else {
System.out.println("锁获取失败,资源被占用");
}
// 关闭 Jedis 连接
jedis.close();
}
}
(3)基于Redisson+Redlock实现分布式锁
Redisson底层原理图:
Redisson也是使用Redis的SET key value NX PX expireTime 命令(即 SET 命令的扩展选项 NX 和 PX),这是一个原子性操作。
Redisson 采用了“看门狗”机制,当客户端获取锁成功后,Redisson 会启动一个后台线程定期为锁续期。
Redisson 为每个线程生成一个唯一的 UUID 标识,以确保锁的持有者是唯一的。
Redisson 分布式锁是可重入的,即一个线程可以多次获取同一把锁,而不会发生死锁。
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedissonRedlockExample {
public static void main(String[] args) {
// 1. 配置多个 Redis 节点
Config config = new Config();
config.useReplicatedServers()
.addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.2:6379",
"redis://127.0.0.3:6379", "redis://127.0.0.4:6379",
"redis://127.0.0.5:6379");
// 2. 创建 Redisson 客户端
RedissonClient redissonClient = Redisson.create(config);
// 3. 获取分布式锁
RLock lock = redissonClient.getLock("redlockKey");
try {
// 4. 尝试加锁,等待时间10秒,锁定时间30秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
try {
System.out.println("成功获取 Redlock 锁,执行操作...");
// 执行需要加锁的业务逻辑
} finally {
// 5. 释放锁
lock.unlock();
System.out.println("Redlock 锁释放成功");
}
} else {
System.out.println("未能获取锁,资源被占用");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 6. 关闭 Redisson 客户端
redissonClient.shutdown();
}
}
}