使用Redis实现分布式锁
最近公司在做微服务的升级改造项目,因此之前在单机系统下的应用锁ReentrantLock 以及 java的关键字synchronized,就不再适合作为锁来实现共享变量的同步。因为他们只适用于一台jvm内部。
后经过对比redis和zookeeper实现分布式锁,公司决定采用redis来实现分布式。
实现分布式锁要考虑的问题
1、要能够实现锁的功能,保证在同一时间只能有一个线程在处理数据。
2、要考虑锁的释放问题,不能造成死锁。
3、根据业务场景,要考虑锁的可重入性,关于可重入锁和不可重入锁,大家可参考这篇文章可重入锁与不可重入锁
redis的配置代码
本文采用的是redis的cluster模式:3主3从模式。
public static JedisCluster cluster = null;
static {
Set hostAndPorts = new HashSet<>();
HostAndPort node7001 = new HostAndPort(“127.0.0.1”, 7001);
HostAndPort node7002 = new HostAndPort(“127.0.0.1”, 7002);
HostAndPort node7003 = new HostAndPort(“127.0.0.1”, 7003);
hostAndPorts.add(node7001);
hostAndPorts.add(node7002);
hostAndPorts.add(node7003);
cluster = new JedisCluster(hostAndPorts);
}
获取锁
本文在Redis中采用hash的数据结构存储,key为要所住的对象,有两个field,一个为current,代表当前持有锁的线程名称,一个lockCount,表示当前持有锁线程的加锁次数。
尝试获取锁,当前线程尝试获取锁,若当前锁未被其他线程持有,则直接获取锁。
大家都知道Redis的setNX原子操作的,setnx后,在调用expire命令为key设置过期时间。但是此方法有一个问题,若是在执行完setnx后程序崩溃了,则该锁可能变成死锁。
因此我们可以写一个lua脚本,因为redis在执行脚本命令的是时候,是在一个事务内执行完成的。
代码示例:
脚本说明:首先判断当前锁的线程是否为当前线程,若为,则直接返回1;设置当前持有锁的线程成功,则设置锁的过期时间,以及设置上锁次数为0;返回1.其他返回0.
尝试加锁
首先,先判断当前线程是否获取锁成功,获取成功,调用redis设置加锁次数。
解锁
判断当前线程是否持有锁,若持有该锁,则加锁次数减一,若是加锁次数为0 ;则直接释放锁。
释放锁
若当前线程持有锁,则直接删除锁。
运行结果
结束语
欢迎大家一起来讨论,本人是一个初学redis的菜鸟