Redis分布式实现原理
一、使用
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分布式实现原理的更多相关文章
- Redis分布式锁原理
1. Redis分布式锁原理 1.1. Redisson 现在最流行的redis分布式锁就是Redisson了,来看看它的底层原理就了解redis是如何使用分布式锁的了 1.2. 原理分析 分布式锁要 ...
- redis分布式锁原理与实现
分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候, ...
- 分布式-技术专区-Redis分布式锁原理实现
在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务.分布式锁等.那具体什么是分布式锁,分布式锁应用在哪些业务场景.如何来实现分布式锁呢?今天来探讨分布式锁这个话题. ...
- Java进阶专题(二十五) 分布式锁原理与实现
前言 现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计.那么在这一个系统中,就会存在若干个微服务,而且服务间也会产生相互通信调用.那么既然产生了服务调用,就必然会存在服务调用延迟或失败 ...
- 利用多写Redis实现分布式锁原理与实现分析(转)
利用多写Redis实现分布式锁原理与实现分析 一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- Redisson实现Redis分布式锁的底层原理
一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识.所以咱们这篇文章 ...
- 项目中用到了Redis分布式锁,了解一下背后的原理
前言 以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.现在博主在某金融平台实习,发现Redis在生产中并不只是当作缓存这么简单.在我接触到的项目中,Redis起到了一个分布式锁的作用 ...
- Redis分布式锁的正确使用与实现原理
模拟一个电商里面下单减库存的场景. 1.首先在redis里加入商品库存数量. 2.新建一个Spring Boot项目,在pom里面引入相关的依赖. <dependency> <gro ...
随机推荐
- React学习小结(一)
一.React的发展 facebook在构建instagram网站的时候遇见两个问题: 1.数据绑定的时候,大量操作真实dom,性能成本太高 2.网站的数据流向太混乱,不好控制 于是facebook起 ...
- 记一次CentOS根目录空间占满的解决方案
问题背景是这样的,根目录初始大小是50G,我把mysql和docker都装在根目录底下了. 随着时间推移,mysql大小达到了27个G,docker则是22个G 根目录空间不够用了,上图: 图 1-1 ...
- golang 中 channel 的详细使用、使用注意事项及死锁分析
目录 1.什么是 channel,介绍管道 2.channel 的基本使用 3.channel 的使用场景 4.使用 channel的注意事项及死锁分析 什么是 channel 管道 它是一个数据管道 ...
- 新的ASP.NET Core 迁移指南
最近在微信里做了一个调查: Web Forms应用程序升级到.NET 6, 收到550份调查,调查还在继续,欢迎参与调查.可以访问链接:https://wj.qq.com/s2/9822949/ac3 ...
- 20192204 2019-2020-2 《Python程序设计》实验四报告
20192204 2019-2020-2 <Python程序设计>实验四报告 课程:<Python程序设计> 班级: 1922 姓名: 李龙威 学号:20192204 实验教师 ...
- Java的jinfo命令使用详解
jinfo命令简介 jinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行 ...
- [MySQL]MySQL8.0的一些注意事项以及解决方案
MySQL8.0 注意事项以及解决方案 1. MySQL8.0 修改大小写敏感配置 天坑MySQL8.0! 在安装后, 便无法通过修改配置文件,重启服务,或者执行sql来更改数据库配置, 要想配置的话 ...
- 程序语言与编程实践4-> 蓝桥杯C/C++备赛记录2 | 第二周学习训练
0323,又是一周星期三,按道理该总结了.这周前几天写题比较多,后面事情多了起来,就没怎么写了.主要方向是洛谷的基本语法熟悉,PTA平台数据结构的一些题目. 0323附上: 题目比较多,所以文章可能有 ...
- CF932G Palindrome Partition(回文自动机)
CF932G Palindrome Partition(回文自动机) Luogu 题解时间 首先将字符串 $ s[1...n] $ 变成 $ s[1]s[n]s[2]s[n-1]... $ 就变成了求 ...
- CF1545X Codeforces Round #732
A. AquaMoon and Strange Sort 叉人题 如果数字各不相同,只需要统计每个数参与构成的逆序对总数,如果是奇数一定最终朝左,偶数朝右.无意义的数字交换对方向是没有影响的 继续考虑 ...