一、使用

1、pom.xml导入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置文件

@Configuration
public class RedissonConfig { // 自定义在yml或properties文件中
@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();
if (StringUtils.isBlank(password)) {
config.useSingleServer().setAddress("redis://" + host + ":" + port);
} else {
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
}
return Redisson.create(config);
} }

3、使用类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.redis.util.RedisLockRegistry; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; /**
* @author Forlan
* @since 2022-04-03
*/
public class ForlanTest { private static final Logger logger = LoggerFactory.getLogger(ForlanTest.class); @Autowired
private RedisLockRegistry redisLockRegistry; public void test() {
String lockKey = "key_id";
Lock loginLock = redisLockRegistry.obtain(lockKey.intern());
boolean getLock = false;
try {
// tryLock()底层调用this.tryLock(0L, TimeUnit.MILLISECONDS)
getLock = loginLock.tryLock(5, TimeUnit.SECONDS);
if (getLock) {
//获得锁执行业务
}
} catch (Exception e) {
logger.error("异常信息...", e);
} finally {
if (getLock) {
//释放锁
loginLock.unlock();
}
}
}
}

二、原理

obtain方法

private final Map<String, RedisLockRegistry.RedisLock> locks;

private final class RedisLock implements Lock {
private final String lockKey;
private final ReentrantLock localLock;
private volatile long lockedAt; private RedisLock(String path) {
this.localLock = new ReentrantLock();
this.lockKey = this.constructLockKey(path);
}
} public Lock obtain(Object lockKey) {
Assert.isInstanceOf(String.class, lockKey);
String path = (String)lockKey;
return (Lock)this.locks.computeIfAbsent(path, (x$0) -> {
return new RedisLockRegistry.RedisLock(x$0);
});
}

主要是根据lockKey去查locks这个map中是否已经存在这个key

如果存在就返回内部类RedisLock

如果不存在就创建一个RedisLock,以lockKey为key,RedisLock为value放入map中

备注:每个分布式应用自己都会创建一个RedisLockRegistry实例,同一个应用的多个线程共享RedisLock类

tryLock方法

public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
long now = System.currentTimeMillis();
// 尝试拿取本地锁
if (!this.localLock.tryLock(time, unit)) {
return false;
} else {
try {
long expire = now + TimeUnit.MILLISECONDS.convert(time, unit); boolean acquired;
// 当前时间还没过期并且还未获得redis锁,睡眠100ms继续重试
while(!(acquired = this.obtainLock()) && System.currentTimeMillis() < expire) {
Thread.sleep(100L);
} if (!acquired) {
this.localLock.unlock();
} return acquired;
} catch (Exception var9) {
this.localLock.unlock();
this.rethrowAsLockException(var9);
return false;
}
}
}

主要过程

先获得本地锁,拿不到直接返回失败

当前时间还没过期并且还没拿到redis锁,睡眠100ms继续重试

如果拿到redis锁,结束循环,返回成功

如果超时了还没拿到,释放锁,返回失败

拿redis锁的过程

private boolean obtainLock() {
boolean success = (Boolean)RedisLockRegistry.this.redisTemplate.execute(RedisLockRegistry.this.obtainLockScript, Collections.singletonList(this.lockKey), new Object[]{RedisLockRegistry.this.clientId, String.valueOf(RedisLockRegistry.this.expireAfter)});
if (success) {
this.lockedAt = System.currentTimeMillis();
} return success;
}

通过obtainLock方法,执行lua脚本来获取

redisTemplate.execute()参数说明:

第一个obtainLockScript参数就是要执行的lua脚本;

local lockClientId = redis.call('GET', KEYS[1])
if lockClientId == ARGV[1] then
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return true
elseif not lockClientId then
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
return true
end
return false

第二个参数就是表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用1为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推);

第三个参是附加参数 arg [arg …] ,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)

为什么要用本地锁

  • 为了可重入
  • 为了减轻redis服务器的压力

为什么要用lua脚本

  • 保证原子性
  • 减少网络开销
  • 替代redis的事务功能

unlock方法

public void unlock() {
if (!this.localLock.isHeldByCurrentThread()) {
throw new IllegalStateException("You do not own lock at " + this.lockKey);
} else if (this.localLock.getHoldCount() > 1) {
this.localLock.unlock();
} else {
try {
if (Thread.currentThread().isInterrupted()) {
RedisLockRegistry.this.executor.execute(this::removeLockKey);
} else {
this.removeLockKey();
}
if (RedisLockRegistry.logger.isDebugEnabled()) {
RedisLockRegistry.logger.debug("Released lock; " + this);
}
} catch (Exception var5) {
ReflectionUtils.rethrowRuntimeException(var5);
} finally {
this.localLock.unlock();
}
}
}

释放锁的过程

1、判断是否是当前线程持有锁,如果不是,抛异常(本地锁)

2、判断当前线程持有锁的计数

如果当前线程持有锁的计数 > 1,说明本地锁被当前线程多次获取,这时只会释放本地锁,释放之后当前线程持有锁的计数-1。

否则,释放本地锁和redis锁。

Redis分布式实现原理的更多相关文章

  1. Redis分布式锁原理

    1. Redis分布式锁原理 1.1. Redisson 现在最流行的redis分布式锁就是Redisson了,来看看它的底层原理就了解redis是如何使用分布式锁的了 1.2. 原理分析 分布式锁要 ...

  2. redis分布式锁原理与实现

    分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候, ...

  3. 分布式-技术专区-Redis分布式锁原理实现

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务.分布式锁等.那具体什么是分布式锁,分布式锁应用在哪些业务场景.如何来实现分布式锁呢?今天来探讨分布式锁这个话题. ...

  4. Java进阶专题(二十五) 分布式锁原理与实现

    前言 ​ 现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计.那么在这一个系统中,就会存在若干个微服务,而且服务间也会产生相互通信调用.那么既然产生了服务调用,就必然会存在服务调用延迟或失败 ...

  5. 利用多写Redis实现分布式锁原理与实现分析(转)

    利用多写Redis实现分布式锁原理与实现分析   一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...

  6. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  7. Redisson实现Redis分布式锁的底层原理

    一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识.所以咱们这篇文章 ...

  8. 项目中用到了Redis分布式锁,了解一下背后的原理

    前言 以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.现在博主在某金融平台实习,发现Redis在生产中并不只是当作缓存这么简单.在我接触到的项目中,Redis起到了一个分布式锁的作用 ...

  9. Redis分布式锁的正确使用与实现原理

    模拟一个电商里面下单减库存的场景. 1.首先在redis里加入商品库存数量. 2.新建一个Spring Boot项目,在pom里面引入相关的依赖. <dependency> <gro ...

随机推荐

  1. think php 富框架文本编辑器

    商品的详细描述一般都是图文混合.对于图文混合的内容,需要使用富文本编辑器来实现. Ueditor富文本编辑器的下载地址: http://ueditor.baidu.com/website/downlo ...

  2. 从原理学习Java反序列化

    1 序列化与反序列化 1.1 概念 序列化: 将数据结构或对象转换成二进制串的过程 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 1.2 使用场景 当你想把的内存中的对象状态 ...

  3. Window7环境下安装Scrapy 方法

    Window7环境下安装Scrapy Scrapy在CPython(默认Python实现)和PyPy(从PyPy 5.9开始)下运行Python 2.7和Python 3.4或更高版本. 如果您使用的 ...

  4. Net中事件的用法之一

    1.事件与委托的关系 委托是一种类型 事件是委托的一个实例 事件中涉及两种角色--事件发布者和事件订阅者. 事件发布者: 触发事件的对象称为事件发布者. 事件订阅者: 捕获事件并对其做出处理的对象称为 ...

  5. java-计算机

    计算机 硬件 装机:CPU 内存 主板 IO设备(input output) 冯诺依曼体系结构 CPU读取数据在运算器中运算传输到存储器,控制器控制输出结果. 软件

  6. php——字符串的""和null,empty的关系

    public function test(){ $test = ""; if($test==null){ echo "test==null <hr>" ...

  7. OOS 预览报错解决思路

    预览报错解决思路: 官方链接:https://docs.microsoft.com/zh-cn/officeonlineserver/office-online-server :> 查看服务器内 ...

  8. 创建一个 20G 的分区,并格式化为 ext4 文件系统

     创建一个 20G 的分区,并格式化为 ext4 文件系统,并完成如下要求: (1)block 大小为 2048,预留空间 20%,卷标为 MYDATA #fdisk /dev/sdb -->n ...

  9. Git指令小结

    一.初始化 git config --global user.name "username" 设置git用户名 git config --global user.email &qu ...

  10. java对xml文件的操作

    xml文件格式(示例): <?xml version="1.0" encoding="UTF-8"?> <root> <Funct ...