目录
基于 Redis
的分布式锁
其实 Redis
官网已经给出了实现:https://redis.io/topics/distlock,说各种书籍和博客用了各种手段去用 Redis
实现分布式锁,建议用 Redlock
实现,这样更规范、更安全
我们默认指定大家用的是 Redis 2.6.12
及更高的版本,就不再去讲 setnx、expire
这种了,直接 set
命令加锁
set key value[expiration EX seconds|PX milliseconds] [NX|XX]
# 例如
SET resource_name my_random_value NX PX 30000
SET
命令的行为可以通过一系列参数来修改
EX second
:设置键的过期时间为second
秒。SET key value EX second
效果等同于SETEX key second value
PX millisecond
:设置键的过期时间为millisecond
毫秒。SET key value PX millisecond
效果等同于PSETEX key millisecond value
NX
:只在键不存在时,才对键进行设置操作。SET key value NX
效果等同于SETNX key value
XX
:只在键已经存在时,才对键进行设置操作
这条指令的意思:当 key——resource_name
不存在时创建这样的 key
,设值为 my_random_value
,并设置过期时间 30000
毫秒。因为 Redis
是单线程的,这一条指令不会被打断,所以是原子性的操作
Redis
实现分布式锁的主要步骤
- 指定一个
key
作为锁标记,存入Redis
中,指定一个唯一的标识作为value
- 当
key
不存在时才能设置值,确保同一时间只有一个客户端进程获得锁,满足互斥性
特性 - 设置一个过期时间,防止因系统异常导致没能删除这个
key
,满足防死锁
特性 - 当处理完业务之后需要清除这个
key
来释放锁,清除key
时需要校验value
值,需要满足解铃还须系铃人
设置一个随机值的意思是在解锁时候判断 key
的值和我们存储的随机数是不是一样,一样的话,才是自己的锁,直接 del
解锁就行
当然这个两个操作要保证原子性,所以 Redis
给出了一段 lua
脚本(Redis
服务器会单线程原子性执行 lua
脚本,保证 lua
脚本在处理的过程中不会被任意其它请求打断)
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
Redis
实现分布式锁的问题
- 获取锁时,过期时间要设置多少合适呢?预估一个合适的时间,其实没那么容易,比如操作资源的时间最慢可能要
10s
,而我们只设置了5s
就过期,那就存在锁提前过期的风险。这个问题先记下,我们先看下Javaer
要怎么在代码中用Redis
锁 - 容错性如何保证呢?
Redis
挂了怎么办,你可能会说上主从、上集群,但也会出现这样的极端情况,当我们上锁后,主节点就挂了,这个时候还没来的急同步到从节点,主从切换后锁还是丢了
带着这两个问题,我们接着看
基于 Redisson
的分布式锁
Redisson
概述
redisson
是 Redis
官方的分布式锁组件。GitHub
地址:https://github.com/redisson/redisson
Redisson
是一个在 Redis
的基础上实现的 Java
驻内存数据网格。它不仅提供了一系列的分布式的 Java
常用对象,还实现了可重入锁 ReentrantLock
、公平锁 FairLock
、联锁 MultiLock
、 红锁 RedLock
、 读写锁 ReadWriteLock
等,还提供了许多分布式服务。Redisson
提供了使用 Redis
的最简单和最便捷的方法。Redisson
的宗旨是促进使用者对 Redis
的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上
Redisson
支持单点模式、主从模式、哨兵模式、集群模式,只是配置的不同
Redisson
实现分布式锁
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("").setDatabase(1);
RedissonClient redissonClient = Redisson.create(config);
RLock disLock = redissonClient.getLock("mylock");
boolean isLock;
try {
/*尝试获取锁的最大等待时间是100秒,超过这个值还没获取到,就认为获取失败锁的持有时是10秒*/
isLock = disLock.tryLock(100, 10, TimeUnit.MILLISECONDS);
if (isLock) {
// 做自己的业务
Thread.sleep(10000);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
disLock.unlock();
}
Redisson
分布式锁的源码
先看下 RLock
的类关系
跟着源码,可以发现 RedissonLock
是 RLock
的直接实现,也是我们加锁、解锁操作的核心类
RLock
接口
// 获取RLock对象
RLock lock = redisson.getLock("myLock");
RLock
提供了各种锁方法,我们来解读下这个接口方法。可以看到继承自 JDK
的 Lock
接口和 Reddsion
的异步锁接口 RLockAsync
public interface RLock extends Lock, RLockAsync {
/*获取锁的名字*/
String getName();
/**
* 这个叫终端锁操作,表示该锁可以被中断 假如A和B同时调这个方法,A获取锁,B为获取锁,那么B线程可以通过
* Thread.currentThread().interrupt(); 方法真正中断该线程
*/
void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException