命令实现
首先我们看下redis普通的set命令,如下图:![](https://img.haomeiwen.com/i20212050/0f2a7814705e8ba8.png)
我们可以看到,set命令后面出了key和value之外,还可以选择EX或PX还有NX和XX,这分别代表什么意思呢,我们来看一下效果。
#ex和px代表过期时间,EX单位是秒,PX则是毫秒
>set test ceshi PX 1000
>set test ceshi EX 1
#NX表示互斥,键不存在则设置成功
>set test ceshi NX
>(nil)
#XX则是key存在则设置成功,key不存在设置失败
>set test 1111 XX
>ok
#那么我们可以组合使用,实现分布式锁的目的
>set key value EX 5 NX
>ok
#再次设置相同key时,会返回nil
>set key value EX 5 NX
>(nil)
#那么已经设置的key是否可以追加时间呢,答案是可以的
>expire key 5
>pexpire key 5
这里需要提醒一下,redis经常说的分布式锁就是上面的方式实现的,而redis还提供了一个setnx命令,可以设置key,但是无法设置超时时间,是不推荐使用的,面试的时候也会经常问到,需要大家避免踩坑。
#redis提供的setnx命令
>setnx test programmer
>(integer) 1
#如果key不存在会设置成功,并返回个1
>setnx test code
>(integer) 0
代码实现
了解了代码实现的原理,那我们来看下代码如何实现分布式锁的,话不多说上代码。
/**
* 获取redis锁
* @param lockKey redis的key,就是锁
* @param requestId redis的value,保证唯一性,例如订单号、手机号
* @param expireTime 过期时间,
* @return
*/
public boolean getLock(String lockKey,String requestId,int expireTime) {
// ex表示过期时间是秒,nx保证key互斥,一条set命令保证原子性,防止并发
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
获取到锁之后,使用完成需要释放锁,当然也可以等待超时时间到期,自动释放。这其中会涉及到一个问题,就是锁超时释放,但业务逻辑还未执行完成。下一个业务进程已经获得了锁,如果直接执行del操作,会造成释放掉了别人的锁。那么我们就需要考虑使用lua脚本,来保证释放锁的并发。
/**
* 使用lua脚本释放锁
* @param lockKey redis的key
* @param requestId redis的value
* @return
*/
public static boolean releaseLock(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 (result.equals(1L)) {
return true;
}
return false;
}
问题
redis在单机的情况下是无法保证高可用的,在生产环境中我们一般会搭建redis的集群,而redis是AP模型,那么就会存在主从数据不同步,导致redis锁重复获得的问题。
当然正常情况下,在不需要保证业务数据强一致性时,就可以使用redis的分布式锁;如果需要保证强一致性,则考虑使用其它方式,比如zookeeper等。
看门狗(Redission分布式锁)
“看门狗”基于NIO的Netty框架实现的分布式锁,多在生产环境中使用,接下来看代码。
1、引入jar包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
2、配置Redisson
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonManager {
@Bean
public RedissonClient redisson(){
Config config = new Config();
ClusterServersConfig clusterServersConfig = config.useClusterServers()
.setScanInterval(2000) // 集群状态扫描,时间是毫秒
.addNodeAddress("redis://127.0.0.1:6379")
.addNodeAddress("redis://127.0.0.1:6380")
.addNodeAddress("redis://127.0.0.1:6381")
.addNodeAddress("redis://127.0.0.1:6382")
.addNodeAddress("redis://127.0.0.1:6383")
.addNodeAddress("redis://127.0.0.1:6384");
return Redisson.create(config);
}
}
3、使用
public void test(){
RLock key = redissonClient.getLock("key");
key.lock();
// 业务代码
key.unlock();
}
总结
以上便是使用redis实现分布式锁的过程,基本上可以满足大部分业务需求,也希望大家了解redis分布式锁的优缺点。
最后,Redisson还有很多玩法,目前是一个比较成熟的redis分布式锁框架,奉上git连接,供大家继续学习。
https://github.com/redisson/redisson/
网友评论