redis 实现分布式锁
redis 实现分布式锁的方式有两种:
通过 redis 提供的 setnx 进行实现,往 redis 中使用 setnx 插入 key 时,如果 key 存在,则返回 0,可以通过插入 key 的返回值进行判断来实现分布式锁
通过使用 Redission(客户端)来实现分布式锁。可以调用 Redission 提供的 api,即 lock(),unlock()方法进行加锁和锁的释放。此外, Redission 还提供了 watchdog,即看门狗,来对加锁的 key 每隔 10 s对该 key 进行续时,(从而避免锁的过期)
Redission 的所有指令是通过 lua 脚本进行实现的,lua 脚本可以保证所有指令执行的原子性
关键词:setnx,Redission,lock,unlock,watchdog,lua,原子性
基于 Redis 的分布式锁实现思路
下面是使用 Redis 实现分布式锁的步骤:
生成分布式锁的 key,一般为业务相关的业务 key;
生成分布式锁的 value,一般为1或者线程 id;
生成分布式锁的过期时间,避免锁过程中宕机锁无法解开;
通过不同语言的 API 实现 SET key value [EX seconds] [NX]命令;
通过命令执行结果返回的标识判断是否加锁成功,一般 true 为上锁成功;
释放锁时,通过删除分布式锁的 key 实现。
Java 实现
这里提供不可重入的分布式锁的实现方式:
@Component
public class RedisLock {@Resourceprivate StringRedisTemplate redisTemplate;/*** 尝试获取分布式锁* @param key 锁的键值* @param value 锁的值* @param expireTime 锁的过期时间* @param timeUnit 锁的过期时间单位* @return 是否获取到锁*/public boolean tryLock(String key, String value, long expireTime, TimeUnit timeUnit) {// 利用 setIfAbsent 方法实现分布式锁Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, timeUnit);return Boolean.isTrue(success);}/*** 释放分布式锁* @param key 锁的键值*/public void unlock(String key) {redisTemplate.delete(key);}
}
什么是可重入锁
分布式锁的可重入是指在分布式环境下,同一个线程可以多次获取同一个锁,而不会出现死锁或其他异常情况。这是因为可重入锁会记录当前线程已经获取锁的次数,每次释放锁时会将次数减一,只有当次数为零时才会真正释放锁。在分布式环境下,可重入锁会将当前线程的标识和获取锁的次数一起存储在分布式存储系统中,以便其他节点可以识别当前线程是否已经获取了锁,并且可以正确地释放锁。
可重入锁的好处是可以避免死锁和其他异常情况,同时也可以提高代码的可读性和可维护性。但是,在分布式环境下,可重入锁的实现需要考虑到网络延迟、节点故障等因素,因此需要谨慎设计和测试。
可重入锁的简单实现
下面是一个使用Java和Redis实现可重入分布式锁的示例代码:
import redis.clients.jedis.Jedis;public class RedisReentrantLock {private Jedis jedis;private String lockKey;private String lockValue;private int lockCount;public RedisReentrantLock(Jedis jedis, String lockKey) {this.jedis = jedis;this.lockKey = lockKey;this.lockValue = null;this.lockCount = 0;}public synchronized boolean lock() {if (lockCount > 0) {lockCount++;return true;}String value = String.valueOf(System.currentTimeMillis());if (jedis.setnx(lockKey, value) == 1) {lockValue = value;lockCount = 1;return true;} else {String currentValue = jedis.get(lockKey);if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {String oldValue = jedis.getSet(lockKey, value);if (oldValue != null && oldValue.equals(currentValue)) {lockValue = value;lockCount = 1;return true;}}}return false;}public synchronized boolean unlock() {if (lockCount == 0) {return false;}lockCount--;if (lockCount == 0) {jedis.del(lockKey);lockValue = null;}return true;}
}
在这个示例中,我们使用了Jedis客户端来连接Redis服务器。构造函数接受一个Jedis实例和一个锁的键值作为参数。lock()方法用于获取锁,如果当前线程已经获取了锁,则增加锁的计数器,否则尝试使用setnx命令在Redis中创建一个新的键值对,如果创建成功,则表示当前线程获取了锁,否则尝试获取当前锁的值并比较是否过期,如果过期则使用getset命令更新锁的值,如果更新成功则表示当前线程获取了锁。unlock()方法用于释放锁,如果当前线程还持有锁,则减少锁的计数器,如果计数器为零,则删除锁的键值对。
需要注意的是,这个示例中的锁并不是完全可重入的,因为它只记录了锁的计数器,而没有记录获取锁的线程。如果多个线程使用相同的锁键值并且在同一时间尝试获取锁,则可能会出现死锁或其他异常情况。为了实现完全可重入的锁,需要在锁的值中记录获取锁的线程标识,并在释放锁时检查当前线程是否持有锁。
Redisson实现分布式锁
Redisson是一个基于Redis的Java驻留对象服务(Remote Service)和分布式锁框架。它提供了一种简单而强大的方式来实现分布式锁,以确保在分布式环境中的多个进程或线程之间的互斥访问。
下面是使用Redisson实现分布式锁的步骤:
-
引入Redisson依赖
在Maven项目中,可以通过以下方式引入Redisson依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.15.5</version>
</dependency>
-
创建Redisson客户端
在使用Redisson之前,需要先创建Redisson客户端。可以通过以下方式创建:
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
这里使用的是单节点模式,如果是集群模式,需要使用useClusterServers()
方法。
-
获取分布式锁
获取分布式锁的方式非常简单,只需要调用getLock()
方法即可:
RLock lock = redisson.getLock("myLock");
lock.lock();
try {// 执行业务逻辑
} finally {lock.unlock();
}
这里创建了一个名为myLock
的锁,并通过lock()
方法获取锁。在执行业务逻辑之前,需要先获取锁,否则会被阻塞。
-
释放分布式锁
在业务逻辑执行完毕后,需要释放分布式锁,否则其他进程或线程无法获取锁。可以通过unlock()
方法释放锁:
lock.unlock();
总结:
使用Redisson实现分布式锁非常简单,只需要引入依赖、创建Redisson客户端、获取锁、释放锁即可。同时,Redisson还提供了很多其他的分布式服务,如分布式Map、分布式List等,可以方便地实现分布式应用。
教学视频
我之前也录制了一套redis分布式锁的视频(https://www.imooc.com/learn/1371),点击下方阅读全文即可观看啦。