分布式锁(Redis的setnx、Redisson)
一、使用Redis的setnx实现分布式锁
1、使用Redis的setnx实现分布式锁出现的问题
(1) 宕机时的锁释放问题
在分布式系统中,如果一个节点获取了锁,但在执行任务过程中发生故障,没有释放锁,其他节点可能会一直等待锁被释放。
解决方案:设置锁的过期时间,确保即使持有锁的节点发生故障,锁也会在一定时间后被自动释放。
(2)锁误释放问题
如果使用固定的键来表示锁,并且客户端在完成任务后释放锁,可能会错误地释放其他客户端获取的锁。
解决方案:每个客户端在获取锁时使用一个唯一的值(如UUID),并在释放锁时检查存储在键中的值是否与自己的值匹配。
(3)原子性问题
SETNX命令本身是原子的,但是如果你需要执行一系列操作(比如设置值和设置过期时间),它们不是原子的。这意味着如果在设置值之后和设置过期时间之前发生故障,可能会导致键没有过期时间。
解决方案:执行Lua脚本期间不会有其他脚本或命令被执行,从而保证了操作的原子性。
2、测试锁的使用代码实现
/*** 采用SpringDataRedis实现分布式锁* 原理:执行业务方法前先尝试获取锁(setnx存入key val),如果获取锁成功再执行业务代码,业务执行完毕后将锁释放(del key)*/
@Override
public void testLock() {//0.先尝试获取锁 setnx key val//问题:锁可能存在线程间相互释放//Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock", 10, TimeUnit.SECONDS);//解决:锁值设置为uuidString uuid = UUID.randomUUID().toString();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);if(flag){//获取锁成功,执行业务代码//1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0String value = stringRedisTemplate.opsForValue().get("num");//2.如果值为空则非法直接返回即可if (StringUtils.isBlank(value)) {return;}//3.对num值进行自增加一int num = Integer.parseInt(value);stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));//4.将锁释放 判断uuid//问题:删除操作缺乏原子性。//if(uuid.equals(stringRedisTemplate.opsForValue().get("lock"))){ //线程一:判断是满足是当前线程锁的值// //条件满足,此时锁正好到期,redis锁自动释放了线程2获取锁成功,线程1将线程2的锁删除// stringRedisTemplate.delete("lock");//}//解决:redis执行lua脚本保证原子,lua脚本执行会作为一个整体执行//执行脚本参数 参数1:脚本对象封装lua脚本,参数二:lua脚本中需要key参数(KEYS[i]) 参数三:lua脚本中需要参数值 ARGV[i]//4.1 先创建脚本对象 DefaultRedisScript泛型脚本语言返回值类型 Long 0:失败 1:成功DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();//4.2设置脚本文本String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +"then\n" +" return redis.call(\"del\",KEYS[1])\n" +"else\n" +" return 0\n" +"end";redisScript.setScriptText(script);//4.3 设置响应类型redisScript.setResultType(Long.class);stringRedisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);}else{try {//睡眠Thread.sleep(100);//自旋重试this.testLock();} catch (InterruptedException e) {e.printStackTrace();}}
}
二、使用Redisson
1. 引入依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId>
</dependency>
2. Redission配置类:
/*** redisson配置信息*/
@Data
@Configuration
@ConfigurationProperties("spring.data.redis")
public class RedissonConfig {private String host;private String password;private String port;private int timeout = 3000;private static String ADDRESS_PREFIX = "redis://";/*** 自动装配**/@BeanRedissonClient redissonSingle() {Config config = new Config();if(!StringUtils.hasText(host)){throw new RuntimeException("host is empty");}SingleServerConfig serverConfig = config.useSingleServer().setAddress(ADDRESS_PREFIX + this.host + ":" + port).setTimeout(this.timeout);if(StringUtils.hasText(this.password)) {serverConfig.setPassword(this.password);}return Redisson.create(config);}
}
3. 测试Redisson锁的使用代码实现
@Autowired
private RedissonClient redissonClient;/*** 使用Redison实现分布式锁* 开发步骤:* 1.使用RedissonClient客户端对象 创建锁对象* 2.调用获取锁方法* 3.执行业务逻辑* 4.将锁释放**/
public void testLock() {//0.创建锁对象RLock lock = redissonClient.getLock("lock1");//0.1 尝试加锁//0.1.1 lock() 阻塞等待一直到获取锁,默认锁有效期30slock.lock();//1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0String value = stringRedisTemplate.opsForValue().get("num");//2.如果值为空则非法直接返回即可if (StringUtils.isBlank(value)) {return;}//3.对num值进行自增加一int num = Integer.parseInt(value);stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));//4.将锁释放lock.unlock();}
负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
看门狗原理:
只要线程一加锁成功,就会启动一个 watch dog 看门狗,它是一个后台线程,会每隔`10`秒检查一下,如果线程一还持有锁,那么就会不断的延长锁`key`的生存时间。因此,Redisson就是使用Redisson解决了锁过期释放,业务没执行完问题。
- 如果我们指定了锁的超时时间,就发送给Redis执行脚本,进行占锁,默认超时就是我们制定的时间,不会自动续期;
- 如果我们未指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】
4. 实战使用:
@Autowired
private RedissonClient redissonClient;@Transactional(rollbackFor = Exception.class)
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {//抢单成功或取消订单,都会删除该key,redis判断,减少数据库压力if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {//抢单失败throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);}// 初始化分布式锁,创建一个RLock实例RLock lock = redissonClient.getLock(RedisConstant.ROB_NEW_ORDER_LOCK + orderId);try {/*** TryLock是一种非阻塞式的分布式锁,实现原理:Redis的SETNX命令* 参数:* waitTime:等待获取锁的时间* leaseTime:加锁的时间*/boolean flag = lock.tryLock(RedisConstant.ROB_NEW_ORDER_LOCK_WAIT_TIME,RedisConstant.ROB_NEW_ORDER_LOCK_LEASE_TIME, TimeUnit.SECONDS);//获取到锁if (flag){//二次判断,防止重复抢单if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {//抢单失败throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);}//修改订单状态//update order_info set status = 2, driver_id = #{driverId} where id = #{id}//修改字段OrderInfo orderInfo = new OrderInfo();orderInfo.setId(orderId);orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());orderInfo.setAcceptTime(new Date());orderInfo.setDriverId(driverId);int rows = orderInfoMapper.updateById(orderInfo);if(rows != 1) {//抢单失败throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);}//记录日志this.log(orderId, orderInfo.getStatus());//删除redis订单标识redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);}} catch (InterruptedException e) {//抢单失败throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);} finally {if(lock.isLocked()) {lock.unlock();}}return true;
}
使用了 Redisson 客户端来获取一个分布式锁,并且通过 tryLock() 方法来尝试获取锁。这个方法接受三个参数:
waitTime:尝试获取锁的超时时间。leaseTime:锁的自动续期时间。unit:时间单位,例如TimeUnit.SECONDS。
设置了 waitTime 和 leaseTime,这表明您正在使用 tryLock() 方法的自动续期功能。这意味着即使原始的 leaseTime 到期,锁也会在后台自动续期,直到手动释放锁或者发生异常导致锁被自动释放。
相关文章:
分布式锁(Redis的setnx、Redisson)
一、使用Redis的setnx实现分布式锁 1、使用Redis的setnx实现分布式锁出现的问题 (1) 宕机时的锁释放问题 在分布式系统中,如果一个节点获取了锁,但在执行任务过程中发生故障,没有释放锁,其他节点可能会一…...
从0开始深度学习(4)——线性回归概念
1 线性回归 回归(regression)指能为一个或多个自变量与因变量之间的关系进行建模。 1.1 线性模型 线性假设是指目标可以表示为特征的加权和,以房价和面积、房龄为例,可以有下面的式子: w称为权重(weigh…...
C语言中的预处理指令中的其中一对——#ifdef和#ifndef
目录 开头1.什么是#ifdef和#ifndef?2.#ifdef和#ifndef的实际应用判断ABCD这个宏是否被定义过判断HELLO这个宏是否没被定义过防止头文件重复定义 下一篇博客要说的东西 开头 大家好,我叫这是我58。今天,我们要学一下关于C语言中的预处理指令中的其中一对…...
交换机自动化备份配置(H3C_无人值守)
介绍: 在日常运维过程中,需要定时备份设备的配置,在设备数量过于庞大的情况下,对我们的运维工作会造成极大地不便,通过python自动化能够完美解决人工手动保存设备配置的问题。而且自动化运维在未来也一定是大势所趋&a…...
缓存预热有哪些方案?
一道经典面试题:缓存预热有哪些方案? 在系统业务高峰期到来之前,我们提前将一些热点数据加载到缓存中,进而提高系统的响应速度,这就是所谓的缓存预热。 那么怎么实现缓存预热呢? 一般来说,我…...
「iOS学习」——Masonry学习
iOS学习 前言Masonry的属性Masonry的使用基础APIAuto Boxing修饰语倍数中心点设置边距优先级使用 总结 前言 暑假我们学习了使用CocoaPods引入第三方库,实现使用SVG图片。而Masonry作为一个轻量级的布局架构,在使用中可以节省很多时间。故进行简单学习。…...
828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署GitLab服务器
828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署Gitlab服务器 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、GitLab介绍2.1 GitLab简介2.2 GitLab主要特点 三、本次…...
51单片机的无线病床呼叫系统【proteus仿真+程序+报告+原理图+演示视频】
1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温湿度传感器模块矩阵按键时钟模块等模块构成。适用于病床呼叫系统、16床位呼叫等相似项目。 可实现基本功能: 1、LCD1602实时显示北京时间、温湿度信息、呼叫床位等信息; 2、DHT11采集病房温湿度信息&…...
计算机毕业设计 | SpringBoot+vue 游戏商城 steam网站管理系统(附源码)
1,项目背景 国家大力推进信息化建设的大背景下,城市网络基础设施和信息化应用水平得到了极大的提高和提高。特别是在经济发达的沿海地区,商业和服务业也比较发达,公众接受新事物的能力和消费水平也比较高。开展商贸流通产业的信息…...
【CH395的简单示例代码】
提供一个基于CH395的简单示例代码,这里将展示如何初始化CH395,并发送一个简单的HTTP请求。请注意,实际使用时还需要根据具体的硬件平台和开发环境调整代码。 假设我们使用的是一个具有SPI接口的微控制器,并且已经将CH395连接到该…...
AI模型:追求全能还是专精?
目录 引言 一、全能型AI模型的诱惑 1.1 通用智能的愿景 1.2 资源整合的优势 1.3 应对未知挑战的能力 1.4 挑战与不足 二、专精型AI模型的魅力 2.1 深度与精度的提升 2.2 成本控制与效率优化 2.3 易于监管与解释性增强 2.4 挑战与不足 三、全能型与专精型AI的全面评…...
ffmpeg音视频开发从入门到精通——ffmpeg 视频数据抽取
文章目录 FFmpeg视频处理工具使用总结环境配置主函数与参数处理打开输入文件获取流信息分配输出文件上下文猜测输出文件格式创建视频流并设置参数打开输出文件并写入头信息读取、转换并写入帧数据写入尾信息并释放资源运行程序注意事项源代码 FFmpeg视频处理工具使用总结 环境…...
Node.js之文件夹的操作
1.创建文件夹操作 // 导入fs模块 const fs require(fs)//创建文件夹操操作 fs.mkdir(./Page, err > {if (err) {console.log(操作失败)return}console.log(操作成功) }) 2.递归创建文件夹 //递归创建文件夹 fs.mkdir(./a/b/c, {recursive: true}, err > {if (err) {co…...
线程的四种操作
所属专栏:Java学习 1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链…...
自我指导:提升语言模型自我生成指令的能力
人工智能咨询培训老师叶梓 转载标明出处 传统的语言模型,尤其是经过指令微调的大型模型,虽然在零样本(zero-shot)任务泛化上表现出色,但它们高度依赖于人类编写的指令数据。这些数据往往数量有限、多样性不足…...
使用Node.js实现单文件上传功能—含代码解释
1、概念 文件上传的具体内容 在前端让用户发送(上传)图片,图片由后端(服务器)接收,并转存到到服务端设备上的操作node.js的文件上传功能主要是使用:multer 插件实现的 搭建一个图片上传的接口 先让接口开通,再去做插件下载/配置等…...
【机器人工具箱Robotics Toolbox开发笔记(一)】Matlab机器人工具箱简介
MATLAB是一款被广泛应用于科学计算和工程领域的专业软件。它的全称为Matrix Laboratory(矩阵实验室),因为其最基本的数据类型就是矢量与矩阵,所以在处理数学和科学问题时非常方便,可用于线性代数计算、图形和动态仿真的…...
基于 Metropolis 的朗之万算法
基于 Metropolis 的朗之万算法 1. 未经调整的朗之万算法2. 基于 Metropolis 的朗之万算法 (MALA)2.1. MH算法2.2. 基于 Metropolis 的朗之万算法 (MALA) 3. Metropolis 调整的朗之万截断算法(MALTA) 1. 未经调整的朗之万算法 未调整的朗之万算法 (ULA) 是…...
SAM2POINT:以zero-shot且快速的方式将任何 3D 视频分割为视频
摘要 我们介绍 SAM2POINT,这是一种采用 Segment Anything Model 2 (SAM 2) 进行零样本和快速 3D 分割的初步探索。 SAM2POINT 将任何 3D 数据解释为一系列多向视频,并利用 SAM 2 进行 3D 空间分割,无需进一步训练或 2D-3D 投影。 我们的框架…...
深入理解FastAPI的response_model:自动化数据验证与文档生成
使用 FastAPI 的 response_model 参数 在构建 RESTful API 时,确保数据的一致性和正确性是非常重要的。FastAPI 提供了强大的工具来帮助开发者实现这一目标。其中一个关键特性是 response_model 参数,它允许开发者定义期望的响应格式,并自动…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
02.运算符
目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&:逻辑与 ||:逻辑或 !:逻辑非 短路求值 位运算符 按位与&: 按位或 | 按位取反~ …...
