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

基于 Redis 的分布式锁 Spring Boot 集成 Redisson 使用分布式锁确保对共享资源的互斥访问

目录

前言

SetNX

pom

yml

Controller

存在的问题

可重入的SetNX

Redisson

pom

yml

Controller


前言

工作中开发过一个上传文件的接口,每个区县都有自己的资源压缩包需要上传到系统,系统接收到压缩包后,需要解压,提取出里面的文件保存到文件服务器中,解析里面的SQLITE文件得到数据保存到数据库中。

由于处理的过程会比较耗时,所以使用了异步处理的方式来优化用户体验,接口接收到文件后快速响应,返回上传成功,异步线程在后台继续执行解析压缩包业务逻辑。为了防止在异步线程处理期间,用户再次上传压缩包,从而导致上传资源数据不一致问题,在异步线程处理期间要获取锁来保证上传资源数据一致。由于项目的架构是微服务架构,所以需要使用分布式锁

项目中有使用 Redis,所以可以基于 Redis 来实现分布式锁,利用 Redis 的 SetNX 便能够简单实现分布式锁了。

SetNX

使用 Spring 框架中的 RedisTemplate 客户端,通过它可以对Redis进行多种操作

pom

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--spring2.0集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>

yml

application.yml

spring:redis:host: localhostpassword: ''port: 6379timeout: 10000lettuce:pool:max-active: 8max-idle: 8max-wait: -1min-idle: 0server:port: 8888

Controller

以下是一个简单的 RedisTemplate 使用 SetNX 的简单示例

@RestController
@RequestMapping("/redis")
public class RedisTemplateController {@ResourceRedisTemplate<String, String> redisTemplate;@GetMapping("/setNX")public String setNX(String key) {Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 60, TimeUnit.SECONDS);if (!Boolean.TRUE.equals(flag)) {return "请稍后再试";}try {System.out.println("processing");Thread.sleep(4 * 1000);} catch (Exception e) {throw new RuntimeException(e.getMessage());} finally {redisTemplate.delete(key);}return "成功";}
}

存在的问题

使用 Redis + RedisTemplate,可以非常简单高效地实现分布式锁,针对绝大部分小系统已经够用了,因为大部分系统终其一生都遇不到那些极端情况,就算遇到了也是重启大法解决一切。。。

存在以下的问题:

  1. 锁无法续期:如果为锁设置了过期时间,万一超过了过期时间程序还没有执行完,而锁就被释放了,程序就可能出错了。
  2. 锁永不释放:如果不设置过期时间,那么如果客户端崩溃,那么该分布式锁就永远不会被释放了。
  3. 不可重入:同一个线程无法重复获取到锁,导致发生死锁。

可重入的SetNX

直接使用 SetNX 会存在以上的问题,不可重入的问题,实际上可以通过线程ID来标识锁,在加锁的时候,将唯一的线程ID也保存进去,这样在判断锁的时候,就可以通过线程ID来判断该分布式锁是不是属于当前线程的了,如果是,则重入数+1

示例代码如下:

public class RedisLockUtil {public static synchronized boolean lock(RedisTemplate<String, Serializable> redisTemplate, String key) {String currentThreadId = String.valueOf(Thread.currentThread().getId());String lockValue = (String) redisTemplate.opsForValue().get(key);if (lockValue == null) {lockValue = String.format("%s:%s", currentThreadId, 1);redisTemplate.opsForValue().set(key, lockValue);return true;}String[] parts = lockValue.split(":");if (parts.length == 2 && parts[0].equals(currentThreadId)) {int count = Integer.parseInt(parts[1]) + 1;lockValue = String.format("%s:%s", currentThreadId, count);redisTemplate.opsForValue().set(key, lockValue, 60, TimeUnit.SECONDS);return true;}return false;}public static synchronized void unlock(RedisTemplate<String, Serializable> redisTemplate, String key) {String currentThreadId = String.valueOf(Thread.currentThread().getId());String lockValue = (String) redisTemplate.opsForValue().get(key);if (lockValue != null) {String[] parts = lockValue.split(":");if (parts.length == 2 && parts[0].equals(currentThreadId)) {int count = Integer.parseInt(parts[1]);if (count > 1) {lockValue = String.format("%s:%s", currentThreadId, count - 1);redisTemplate.opsForValue().set(key, lockValue, 60, TimeUnit.SECONDS);} else {redisTemplate.delete(key);}}}}}

 这也只是解决了可重入的问题,还是存在锁续期,锁永远不会被释放问题。

Redisson

解决以上问题,一劳永逸的方式就是直接使用 Redisson,Redisson 的 lock 通过 Watchdog 机制,解决了锁续期的问题。

在没有指定过期时间的前提下,Redisson 客户端实例获取到一个分布式锁,Watchdog 机制基于 Netty 的时间轮启动一个后台任务,定期向Redis发送续期命令,重新设置锁的过期时间,默认续期30秒,每10秒做一次续期。

当分布式锁释放或者 Redisson 客户端关闭时,Watchdog 也停止锁的续期任务。这样就完美地保证程序执行期间获取到的锁不会提前释放。即使客户端崩溃,也不会出现锁永远不会被释放的情况,因为客户端崩溃,Watchdog 也一起停止了续期任务,过了过期时间,锁自己就释放了。

pom

        <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.11.4</version></dependency>

yml

application.yml

spring:redis:redisson:config: "classpath:redisson.yml"server:port: 8888

redisson.yml

{
  "singleServerConfig":{
    "idleConnectionTimeout":10000,
    "pingTimeout":1000,
    "connectTimeout":10000,
    "timeout":3000,
    "retryAttempts":3,
    "retryInterval":1500,
    "subscriptionsPerConnection":5,
    "clientName":null,
    "address": "redis://localhost:6379",
    "subscriptionConnectionMinimumIdleSize":1,
    "subscriptionConnectionPoolSize":50,
    "connectionMinimumIdleSize":32,
    "connectionPoolSize":64,
    "database":0
  },
  "threads":0,
  "nettyThreads":0,
  "codec":{
    "class":"org.redisson.codec.JsonJacksonCodec"
  },
  "transportMode":"NIO"
}

Controller

@RestController
@RequestMapping("/redisson")
public class RedissonLockController {@Resourceprivate RedissonClient redissonClient;@GetMapping("/lock")public String reentrantLock(String key) {RLock reentrantLock = redissonClient.getLock(key);try {if (!reentrantLock.tryLock()) {return "请稍后再试";}System.out.println("processing");Thread.sleep(4 * 1000);} catch (Exception e) {reentrantLock.unlock();} finally {reentrantLock.unlock();}return "成功";}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【深度学习】【语音】TTS,StyleTTS 2,论文
  • Android中的沉浸式丝滑转场之共享元素转场动画
  • 机器学习之主成分分析(PCA)
  • Mipi SoundWire Spec 详解4.1
  • sql注入复现(1-14关)
  • linux下的C++程序
  • 【Linux】常见指令
  • 无人机挂载抓捕网
  • 基于Python的数据科学系列(1):Python基础
  • Android HandlerThread泄漏FD问题
  • 学习笔记五:在k8s中安装EFK组件
  • Java多商户新零售超市外卖商品系统
  • Project Euler_Problem 587_Concave Triangle (背包问题)
  • 力扣399.除法求值
  • Python 日志处理分析简介
  • 【Leetcode】104. 二叉树的最大深度
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • Laravel Telescope:优雅的应用调试工具
  • magento2项目上线注意事项
  • Selenium实战教程系列(二)---元素定位
  • uva 10370 Above Average
  • Wamp集成环境 添加PHP的新版本
  • 猴子数据域名防封接口降低小说被封的风险
  • 基于HAProxy的高性能缓存服务器nuster
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 两列自适应布局方案整理
  • 你真的知道 == 和 equals 的区别吗?
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 7行Python代码的人脸识别
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 回归生活:清理微信公众号
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ​学习笔记——动态路由——IS-IS中间系统到中间系统(报文/TLV)​
  • ‌移动管家手机智能控制汽车系统
  • # 移动硬盘误操作制作为启动盘数据恢复问题
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)fock函数详解
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • (自适应手机端)行业协会机构网站模板
  • (最新)华为 2024 届秋招-硬件技术工程师-单板硬件开发—机试题—(共12套)(每套四十题)
  • ./configure,make,make install的作用
  • .net wcf memory gates checking failed
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .netcore 获取appsettings
  • .net访问oracle数据库性能问题
  • .NET中使用Protobuffer 实现序列化和反序列化