一、什么是分布式锁?
要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。
线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
二、分布式锁的使用场景。
线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。
有这样一个情境,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
分布式锁一般有三种实现方式:
1. 数据库乐观锁;2. 基于Redis的分布式锁;
3. 基于ZooKeeper的分布式锁
三、分布式锁的实现(Redis)
分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。
Redis分布式锁:
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了
/**
* 获取一个lockExpireTime时间的锁,如果获取不到,每隔tryInterval时间获取一次锁,timeout时间后,锁申请超时,获取锁失败
* @param key -- 锁的key
* @param accessKey--加锁的业务key(相当于key对应的value)
* @param timeout--等待超时时间,毫秒
* @param tryInterval --多久重试获取锁,毫秒
* @param lockExpireTime--锁超时时间--单位秒
* @return
*/
public boolean getRedisLock(String key, String accessKey, long timeout, long tryInterval, long lockExpireTime){
try{
String lockKey = key;
long startTime = System.currentTimeMillis();
String transValue = accessKey;
// 直到获取到锁为止
while (true)
{
if(redisTemplate.opsForValue().setIfAbsent(lockKey, transValue))
{
redisTemplate.opsForValue().set(lockKey,transValue,lockExpireTime,TimeUnit.MILLISECONDS);
return true;
}
//如果没有获取到,并且已经超时
if(System.currentTimeMillis() - startTime > timeout){
return false;
}
//延迟一段时间
Thread.sleep(tryInterval);
}
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
*
* @param key
* @param accessTime
*/
public synchronized void releaseUserTransLock(String key, String accesskey) {
try {
boolean lockOk = isLockByOwner(key, accesskey);
if (lockOk) {
redisTemplate.delete(key);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断加锁与解锁是不是同一个客户端
* @param key
* @param accesskey (相当于key对应的value)
* @return
*/
public boolean isLockByOwner(String key, String accesskey)
{
try {
ValueOperations<Object, Object> opsForValue = redisTemplate.opsForValue();
// 获取redis中key对应的value
String cacheTransValue = (String) opsForValue.get(key);
if (accesskey.equalsIgnoreCase(cacheTransValue)) {
return true;
}
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}