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 ...
随机推荐
- 微信小程序实现图表展示
开发可参考以下链接 点击有道云链接 小程序柱状图展示 [单元目标] 掌握小程序柱状图展示 [教学内容] 1.1 掌握组件导入 在miniprogram下的utils文件夹(若没有就新建一个)下,新建w ...
- 构造方法__construct()与析构方法__destruct() 构造方法:刚出生时婴儿的啼哭 析构方法:老人临终时的遗言
<?phpclass person{ var $name; var $sex; var $age;//构造方法 function __construct($name, $sex, $age) { ...
- tp 防止xss攻击
跨站脚本攻击(Cross Site Scripting),攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的. ...
- JS类型判断&原型链
JS类型检测主要有四种 1.typeof Obj 2.L instanceof R 3.Object.prototype.toString.call/apply(); 4.Obj.constructo ...
- 万字长文---关于PKM收集与整理系统的思考和实践
PKM闭环中有一个很重要的环节就是信息输入,包括各种信息来源,例如微信公众号.博客.知乎.RSS等等,因此也就诞生了一大堆稍后读软件,如何真正有效的获取输入而不是做一只仓鼠是需要思考的.最近看了< ...
- 《Shader入门精要》中MVP变换的Projection矩阵与《GAMES101图形学入门》中的区别
game101的透视投影的投影矩阵是这样的 正交投影是这样的 而shader入门精要的透视投影矩阵是这样子 正交投影矩阵是这样子 game101的透视投影是这样得到的 而正交投影的时候并没有假设中心点 ...
- ArcMap操作练习题目及答案
题目: 1.主观题(20分)用文字说明中学选址的过程,并说明分析过程中所用的空间分析方法和分析工具. 2.主观题(10分)计算目标区内道路网的密度.(数据框1) 3.主观题(20分)对roadnet里 ...
- 单循环链表(基于java语言)
public class CircleSinglyLinkList { private Node head; CircleSinglyLinkList(){ this.head = null; } C ...
- Redis的Unable to connect to Redis和java.io.IOException: 远程主机强迫关闭了一个现有的连接问题的解决
学习项目xhr系统用到springboot + vue(https://github.com/lenve/vhr),文档中要求使用到RabbitMQ,但是从我搭建开发环境来看,是否配置Rabbit ...
- CVE-2017-7269(IIS远程代码执行)
利用CVE-2017-7269让IIS向自己的msf反弹一个shell 漏洞编号:CVE2017-7269 服务器版本:Windows server 2003 中间件:IIS6.0 攻击工具:meta ...