Redis如何实现分布式锁详解
一、前言
在Java的并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock
来使用它。
但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下,就需要分布式锁了。
通常的分布式锁的实现方式有redis,zookeeper,但是一般我们的程序中都会用到redis,用redis做分布式锁,也能够降低成本。
二、实现原理
2.1 加锁
加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。
在Redis 2.6.12以及之前,可以通过setnx key value (key不存在才设置成功)设置值,通过expire key seconds设置过期时间。但是由于是两个命令,不是原子的。如果在设置值之后还没有来得及设置过期时间,程序挂掉了,那么这个key就永远的存在redis中了。
在Redis 2.6.12之后,redis提供了set key value EX seconds NX 和set key value PX millisecond NX 来原子性的设置值和设置过期时间。
2.2 解锁
解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉,在删除前需要判断是不是设置的value,如果是才删除。
为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。LUA脚本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
三、通过RedisTemplate实现分布式锁
我们通过SpringBoot构建的应用程序一般都是用RedisTemplate来操作缓存redis。下面介绍基于RedisTemplate来实现分布式锁。
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class RedisLockUtil {
@Autowired
private RedisTemplate redisTemplate;
//获取锁超时时间
private long timeout = 60000;
public Boolean getLock(String key, String value, Long ms) {
long startTime = System.currentTimeMillis();
while (true) {
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, ms, TimeUnit.MILLISECONDS);
if (flag) {
return true;
}
//避免一直无限获取锁
if (System.currentTimeMillis() - startTime > timeout) {
return false;
}
try {
log.info("{}重试锁", key);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Boolean unLock(String key, String value) {
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del',KEYS[1]) " +
"else" +
" return 0 " +
"end";
// 构造RedisScript并指定返回类类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
// 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
Object result = redisTemplate.execute(redisScript, Arrays.asList(key), value);
return "1".equals(result.toString());
}
}
四、通过Redisson实现
Redisson为我们封装了细节,可以开箱即用。
引入maven依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.0</version>
</dependency>
配置类:
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient getRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
}
redis属性配置:
spring:
redis:
host: 127.0.0.1
port: 6379
password: root
封装的加锁解锁工具类:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedissLockUtil {
@Autowired
private RedissonClient redissonClient;
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
public void unlock(RLock lock) {
lock.unlock();
}
public RLock lock(String lockKey, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.SECONDS);
return lock;
}
public RLock lock(String lockKey, TimeUnit unit , int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
public boolean tryLock(String lockKey, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
}
到此这篇关于Redis如何实现分布式锁详解的文章就介绍到这了,更多相关Redis分布式锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341