首先什么是分布式锁?
分布式锁解决了什么问题?
例如:现在你的线程,用synchronized实现了对一个类的监控,在单机情况下确实能实现串行化执行代码,但是如果部署到集群上去,或者启动多个application呢?用一个图来理解一下吧
实现思路是,让一个所有JVM都能知道和看得到的锁监视器作为一个分布式的锁,这样就解决了,分布式下,synchronized等无法解决的多线程问题。
那么有哪几种方式可以实现分布式锁呢?
我所了解的三种分别是:
1、Mysql:利用Mysql本身的互斥锁机制
2、Redis的setnx+expired
3、Zookeeper:利用节点的唯一性和有序性实现互斥
三者总结如下:
今天我们只要使用Redis 的 setnx 命令来实现分布式锁
如上图我们发现我们在第一次setnx lock 返回值为1 表示成功了,第二次我再去setnx lock 返回值为
0表示失败了
但我们删除锁后再尝试,又成功了。
但是实际到我们的业务,可能会出现各种各样的问题,导致锁无法及时释放,怎么办呢,我们用EXPIRE
命令设置过期时来间托底,就是,你的代码没能及时删掉锁,redis也会自己帮你删掉,防止死锁。
相关命令
ttl 命令查看过期时间,-2表示过期了,-1表示永不过期
Java相关redis 代码实现
首先定义一个接口类
public interface ILock {
/**
* 尝试获取锁
* @param timeoutSec 锁持有的超时时间,过期后自动释放
* @return true代表获取锁成功; false代表获取锁失败
*/
boolean tryLock(long timeoutSec);
/**
* 释放锁
*/
void unlock();
}
获取锁代码
/**
* 定义一个锁的要点
* 1.锁的对象要精确,范围不能过大,否则会影响并发度,此处对应锁的key
* 2.上锁的执行者要明确,否则解锁时会将别人定义的锁解开,此处对应锁的value
*/
public boolean tryLock(long timeoutSec) {
// 获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
删除锁代码
@Override
public void unlock() {
// 获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁中的标示
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
// 判断标示是否一致
if(threadId.equals(id)) {
// 释放锁
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
相关基本代码我们实现了,但是其中存在一些问题,例如锁误删,锁的原子性问题、
下一篇见!