一、使用

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. laravel7 h-ui模板点改

    1:html页面 <td> @if($item->fang_status == 0) <span onclick="changeFangStatus(this,{{$ ...

  2. think php 框架下拉分页

    //以对象的形式获取数据库$data变量的信息,将lastPage()传输至页面 $lastpage = $data->lastPage(); $this->assign('lastpag ...

  3. yum源出现 AppStream‘ 缓存失败,忽略这个 repo。同步仓库 ‘base‘ 缓存失败,忽略这个 repo 的问题解决办法

    今天安装Wget时出现如下错误 到 /etc/yum.repos.d/目录下编辑CentOS-Base.repo ,更改为如下内容. vim CentOS-Base.repo # CentOS-Bas ...

  4. Linux——vi命令详解

    转载 Linux--vi命令详解   原文链接:https://blog.csdn.net/cyl101816/article/details/82026678 vi编辑器是所有Unix及Linux系 ...

  5. CVE-2017-12635(Couchdb垂直权限绕过漏洞)

    简介 Apache CouchDB是一个开源数据库,专注于易用性和成为"完全拥抱web的数据库".它是一个使用JSON作为存储格式,JavaScript作为查询语言,MapRedu ...

  6. 使用postman进行post请求传递中文导致后台接收乱码的问题

    1.个人猜测估计是如果header里不指明编码的话,经过tomcat服务器时会导致转换乱码信息,这样就算你在filter里配置了EncodingFilter相关的过滤器也无济于事.. 解决方法就是在h ...

  7. springcloud断路器的作用?

    当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应) 断路器有完全打开状态:一段时间内 达到一定 ...

  8. 上传文件到阿里云linux服务器(windows到Linux的文件上传)

    在"运行"中输入cmd,打开控制台,切换到刚才Putty的安装目录下,我的是E:\Putty,然后输入pscp命令,我们需要这个命令来实现文件的上传.如下命令格式: F:\PuTT ...

  9. 实践中如何优化 MySQL ?

    最好是按照以下顺序优化: 1.SQL 语句及索引的优化 2.数据库表结构的优化 3.系统配置的优化 4.硬件的优化 详细可以查看 阿里 P8 架构师谈:MySQL 慢查询优化.索引优化.以及表等优化

  10. springboot-数据库访问之mybatis

    选中mysqldirver,数据库驱动器 选中jdbc ,帮我们配置数据源,连接数据库 选中mybatis,持久层 <!-- 这个不是springboot官方出来的,是mybatis为了设备sp ...