一、什么是分布式锁?
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
二、常见的分布式锁有哪些实现方式?
- 数据库乐观锁或者悲观锁实现的分布式锁;
- 基于Redis的分布式锁;
- 基于ZooKeeper的分布式锁。
本次将主要介绍平常开发中用的最多,性能也是最好基于Redis实现的分布式锁。
三、redis分布式锁的特点
(1)redis有很高的性能;
(2)redis对此支持的命令较好,实现起来比较方便
四、使用分布式锁的时候主要用到的命令介绍
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire
expire key timeout:当key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)delete
delete key:删除key
五、使用Redis实现分布式锁的实现思想
(1)获取锁的时候,使用setnx加锁,并使用expire命令给锁加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
六、使用Redis实现分布式锁的实现原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系,基于此,Redis中可以使用SETNX命令实现分布式锁。
七、解锁实现redis分布式锁的两种姿势
第一势 使用setNx实现:
1、引入jar包
2、上代码
```java
/**
* setNxByExpire
* 获取分布式锁
* @param lockKey 锁
* @param requestId 请求标识
* @param expTime 超时时间
* @return 是否获取成功
*/
public boolean setNxByExpire(final String lockKey, final Serializable requestId, final long expTime) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
RedisSerializer keySerializer = redisTemplate.getKeySerializer();
Object object = redisConnection.execute("set", keySerializer.serialize(lockKey),
valueSerializer.serialize(requestId),
SafeEncoder.encode("NX"),
SafeEncoder.encode("EX"),
redis.clients.jedis.Protocol.toByteArray(expTime));
return object != null;
}
});
return result;
}
可以看到,我们加锁就一行代码:setNxByExpire(final String lockKey, final Serializable requestId, final long expTime),这个set()方法一共有三个形参:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个代表key的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:
当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
已有锁存在,不做任何操作。
第二势 使用Lua脚本实现:
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
第一行代码,我们写了一个简单的Lua脚本代码。
第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。
那么这段Lua代码的功能是什么呢?
首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。
那么为什么要使用Lua语言来实现呢?
因为要确保上述操作是原子性的。那么为什么执行eval()方法可以确保原子性,源于Redis的特性,下面是官网对eval命令的部分解释:简单来说,就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
程序秘籍
专注java职场经验,技术分享
程序员接私活,编程培训