当前位置: 首页 > news >正文

基于redis的分布式锁

一、redis分布式锁基本信息

1.详细讲解:

Redis 分布式锁是一种用于控制分布式系统中多个进程对共享资源的并发访问的机制。通过 Redis 的原子操作和过期时间功能,可以实现一个简单而有效的分布式锁。接下来,我们将详细介绍其工作原理、基本操作步骤以及代码实现,并提供详细的注释。

2.基本原理

2.1获取锁:

使用 SET 命令尝试设置一个键,并使用 NX 参数(如果键不存在则设置)和 PX 参数(设置键的过期时间)。
如果 SET 操作返回成功(即 OK),则表示获取锁成功。

2.2释放锁:

释放锁时需要确保只有持有锁的客户端才能释放锁,因此需要在释放锁时检查锁的值是否是当前客户端的标识。
使用 Lua 脚本来保证原子性:检查锁的值并删除锁。

二、代码示例

1.redis加锁以及解锁的工具类

1.1注意事项:

1.1.1 由于RedisTemplate交由spring管理,所以我们工具类也需要注册给spring,之后通过注入方式使用工具类,这样redisTemplate就不会是null;
1.1.2 用脚本的方式执行redisTemplate.execute会出现异常java.lang.UnsupportedOperationException: io.lettuce.core.output.ValueOutput does not support set(long),好像是返回值无法转换,由于redis版本不匹配,我这边redis版本是5.0.14.1;(暂未解决脚本执行后续会修改);

@Slf4j
@Component
public class LockUtil {@Resourceprivate RedisTemplate<String, Object> redisTemplate;//定义变量private final Object RELEASE_SUCCESS = 1;private final Object RELEASE_ERROR = 0;/*** 以阻塞方式的获取锁* @param key         key* @param value       value* @param lockTimeout 锁超时时间* @param getTimeout  获取锁超时时间* @return*/public boolean lockBlock(String key, String value, long lockTimeout, long getTimeout, TimeUnit timeUnit) {long start = System.currentTimeMillis();//循环执行是否能加锁成功判断while (true) {//检测是否超时if (System.currentTimeMillis() - start > getTimeout) {log.error(Thread.currentThread().getName() + "get lock timeout");return false;}//执行set命令 ,如果返回 true,表示获取锁成功;如果返回 false,表示获取锁失败。Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, lockTimeout, timeUnit);//是否成功获取锁if (absent != null && absent) {return true;} else {log.info(Thread.currentThread().getName() + "get lock fail:{},{}", key, value);}}}/*** 解锁:由于我这边无法执行redis脚本,一直返回异常,下面redis操作并非是原子性操作* @param key 加锁key* @param value 锁value* @return 返回是否解锁成功*/public boolean unlock(String key, String value) {Object result = 0;String o = (String) redisTemplate.opsForValue().get(key);if (o != null && o.equals(value)) {Boolean delete = redisTemplate.delete(key);if (delete){result = 1;}}if (RELEASE_SUCCESS.equals(result)) {return true;}log.error(Thread.currentThread().getName() + "unlock error");return false;}
}

2.调用实现

采用多线程方式模拟调用

@Slf4j
@RestController
public class TestController {@Resourceprivate LockUtil lockUtil = new LockUtil();@GetMapping(value = "/redisLock")public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);CompletableFuture.allOf(completableFuture1, completableFuture2).get();threadPoolTaskExecutor.shutdown();}private CompletableFuture<Void> getCompletableFuture( ExecutorService threadPoolTaskExecutor,String key){CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {String value = UUID.randomUUID().toString();try {if (lockUtil.lockBlock(key, value, 3L, 10L, TimeUnit.SECONDS)) {log.info(Thread.currentThread().getName() + "获取锁成功,value is {}", value);Thread.sleep(2000);} else {log.info(Thread.currentThread().getName() + "获取锁失败,value is {}", value);}} catch (InterruptedException e) {log.info(Thread.currentThread().getName() +"获取锁异常,value is {}", value);} finally {if (lockUtil.unlock(key, value)) {log.info(Thread.currentThread().getName() + "释放锁,value is {}", value);}}}, threadPoolTaskExecutor);return  completableFuture;}
}

3.测试结果显示

运行结果

4.上面方法解决的问题

4.1通过对于redis key添加过期防止锁无法释放造成死锁;
4.2通过加锁时间限制防止加锁失败一直加锁,造成死锁;
4.3通过value比对保证解的锁是自己持有的;

5.上面方法存在的问题

5.1代码执行超过redis存放时间:

redis释放之后,别人可以获取锁这样锁就相当于失效,解决方案:看门狗机制,我们redisson已经解决上面问题了

5.2代码实现

1.redisson配置

@Configuration
@Slf4j
public class RedissonConfig {@Beanpublic RedissonClient createRedissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");//.setPassword(""); // 如果没有密码,可以省略这一行return Redisson.create(config);}
}

2.代码调用实现

@RestController
public class TestController {@Resourceprivate RedissonClient redissonClient;@GetMapping(value = "/redisLock")public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);CompletableFuture.allOf(completableFuture1, completableFuture2).get();threadPoolTaskExecutor.shutdown();}private CompletableFuture<Void> getCompletableFuture(ExecutorService threadPoolTaskExecutor, String key) {RLock lock = redissonClient.getLock(key);CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {try {// 尝试获取锁,等待时间10秒,上锁时间5秒if (lock.tryLock(10, 5, TimeUnit.SECONDS)) {log.info(Thread.currentThread().getName() + "获取锁成功");Thread.sleep(2000);} else {log.info(Thread.currentThread().getName() + "获取锁失败");}} catch (InterruptedException e) {log.info(Thread.currentThread().getName() + "获取锁异常");} finally {lock.unlock();log.info(Thread.currentThread().getName() + "释放锁成功");}}, threadPoolTaskExecutor);return completableFuture;}
}

3.运行结果

在这里插入图片描述
不足之处,望海涵,希望大佬指点;

相关文章:

  • 开源WebGIS全流程常用技术栈
  • Log4j日志级别介绍
  • 2024.06.01 校招 实习 内推 面经
  • Spring Boot 的启动原理、Spring Boot 自动配置原理
  • C++面向对象程序设计 - 命名空间
  • stm32编写Modbus步骤
  • Idea jdk配置的地方 启动时指定切换的地方
  • 嵌入式学习
  • 学习分享-分布式 NoSQL 数据库管理系统Cassandra以及它和redis的区别
  • 2024 Java 异常—面试常见问题
  • 华为坤灵路由器初始化开局的注意事项,含NAT配置
  • 使用C#快速搭建一个在windows运行的exe应用
  • python format详解
  • CI/CD实战面试宝典:从构建到高可用性的全面解析
  • UE5.2打包安卓
  • 10个最佳ES6特性 ES7与ES8的特性
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • github指令
  • Javascript编码规范
  • js 实现textarea输入字数提示
  • js继承的实现方法
  • js算法-归并排序(merge_sort)
  • php中curl和soap方式请求服务超时问题
  • PV统计优化设计
  • rabbitmq延迟消息示例
  • spring学习第二天
  • 程序员该如何有效的找工作?
  • 聊聊sentinel的DegradeSlot
  • 一些css基础学习笔记
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​【已解决】npm install​卡主不动的情况
  • ​水经微图Web1.5.0版即将上线
  • #if #elif #endif
  • #vue3 实现前端下载excel文件模板功能
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)STM32单片机上位机
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (C语言)fgets与fputs函数详解
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (k8s中)docker netty OOM问题记录
  • (MATLAB)第五章-矩阵运算
  • (六)c52学习之旅-独立按键
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (三)mysql_MYSQL(三)
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (一)SpringBoot3---尚硅谷总结
  • .net php 通信,flash与asp/php/asp.net通信的方法
  • .net 调用php,php 调用.net com组件 --
  • .NET/C# 使用反射注册事件
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • .net专家(高海东的专栏)
  • /etc/sudoer文件配置简析
  • @ModelAttribute 注解