Redis如何实现分布式锁详解

网友投稿 546 2023-01-15

Redis如何实现分布式锁详解

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;

//获取锁超时时间

CEqFSMdl 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 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依赖:

org.redisson

redisson

3.14.0

配置类:

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;

}

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:金融小程序过审(小程序涉及金融审核不通过)
下一篇:小程序生态分析方法有(小程序 分析)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~