Redis分布式锁 Redis怎样实现分布式锁详解
kusedexingfu 人气:0一、前言
在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; /** * 获取锁 * @param key * @param value * @param ms * @return */ 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(); } } } /** * 解锁 * @param key * @param value * @return */ 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; /** * redisson 配置类 */ @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; /** * redis分布式锁帮助类 */ @Component public class RedissLockUtil { @Autowired private RedissonClient redissonClient; /** * 加锁 * @param lockKey * @return */ public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * 释放锁 * @param lockKey */ public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } /** * 释放锁 * @param lock */ public void unlock(RLock lock) { lock.unlock(); } /** * 带超时的锁 * @param lockKey * @param timeout 超时时间 单位:秒 */ public RLock lock(String lockKey, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, TimeUnit.SECONDS); return lock; } /** * 带超时的锁 * @param lockKey * @param unit 时间单位 * @param timeout 超时时间 */ public RLock lock(String lockKey, TimeUnit unit , int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } /** * 尝试获取锁 * @param lockKey * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @return */ 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; } } /** * 尝试获取锁 * @param lockKey * @param unit 时间单位 * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @return */ 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; } } }
加载全部内容