一、核心问题与解决方案

问题本质

ClientARedisClientBSET lock_key clientA_id EX 30业务处理中...锁超时自动释放SET lock_key clientB_id EX 30DEL lock_key锁被意外释放!ClientARedisClientB

Redisson解决方案

  1. 客户端唯一ID:UUID+线程ID作为锁标识
  2. Lua脚本原子操作:校验+删除一步完成
  3. 看门狗机制:后台线程定期续期
  4. 发布订阅:锁释放通知避免无效轮询

二、源码实现解析

1. 加锁流程(Lock操作)

核心Lua脚本
-- KEYS[1]: 锁key
-- ARGV[1]: 锁过期时间(毫秒)
-- ARGV[2]: 客户端唯一标识(UUID:threadId)-- 锁不存在时加锁
if (redis.call('exists', KEYS[1]) == 0) thenredis.call('hset', KEYS[1], ARGV[2], 1) -- 使用hash存储redis.call('pexpire', KEYS[1], ARGV[1])return nil
end-- 锁存在且是当前线程持有(重入锁)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) thenredis.call('hincrby', KEYS[1], ARGV[2], 1) -- 重入计数+1redis.call('pexpire', KEYS[1], ARGV[1])return nil
end-- 返回锁剩余生存时间
return redis.call('pttl', KEYS[1])
看门狗启动逻辑(RedissonLock类)
private void scheduleExpirationRenewal(long threadId) {// 创建定时任务Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) {// 检查锁是否仍被当前线程持有RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {// 异常处理return;}if (res) {// 递归调用,实现周期性续期scheduleExpirationRenewal(threadId);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 默认30s/3=10s执行一次
}
续期Lua脚本
-- KEYS[1]: 锁key
-- ARGV[1]: 续期时间(默认30s)
-- ARGV[2]: 客户端唯一标识-- 检查是否仍是当前线程持有锁
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then-- 续期redis.call('pexpire', KEYS[1], ARGV[1])return 1
end
return 0

2. 解锁流程(Unlock操作)

解锁Lua脚本
-- KEYS[1]: 锁key
-- KEYS[2]: 发布订阅频道
-- ARGV[1]: 释放锁消息(0L)
-- ARGV[2]: 锁过期时间
-- ARGV[3]: 客户端唯一标识-- 锁不存在(可能已过期)
if (redis.call('exists', KEYS[1]) == 0) then-- 发布解锁消息redis.call('publish', KEYS[2], ARGV[1])return 1
end-- 非当前线程持有
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) thenreturn nil
end-- 减少重入计数
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1)
if (counter > 0) then-- 重入次数>0,仅更新过期时间redis.call('pexpire', KEYS[1], ARGV[2])return 0
else-- 完全释放锁redis.call('del', KEYS[1])-- 发布解锁消息redis.call('publish', KEYS[2], ARGV[1])return 1
end
锁释放通知机制
// RedissonLock.unlockAsync方法
protected RFuture<Boolean> unlockAsync(long threadId) {// 执行解锁Lua脚本RFuture<Boolean> future = unlockInnerAsync(threadId);future.onComplete((res, e) -> {// 取消看门狗任务cancelExpirationRenewal(threadId);if (e != null) {// 异常处理return;}if (res == null) {// 锁非当前线程持有throw new IllegalMonitorStateException();}});return future;
}

3. 锁等待机制

请求线程(Thread B)Redis服务器发布订阅频道锁持有线程(Thread A)初始状态:Thread A持有锁尝试获取锁 (lua脚本)返回锁剩余TTL(500ms)订阅锁释放频道 (SUBSCRIBE)等待指定时间(500ms)收到锁释放消息重新尝试获取锁直接尝试获取锁alt[等待期间收到通知][等待超时未收到通知]loop[等待锁释放]取消订阅 (UNSUBSCRIBE)执行业务逻辑取消订阅 (UNSUBSCRIBE)返回获取锁失败alt[获取锁成功][获取锁失败]Thread A释放锁执行解锁lua脚本发布锁释放消息(PUBLISH)请求线程(Thread B)Redis服务器发布订阅频道锁持有线程(Thread A)

三、完整工作流程

加锁流程

尝试加锁
成功?
启动看门狗线程
获取锁剩余TTL
订阅锁释放频道
等待TTL时间
执行业务逻辑

解锁流程

执行解锁Lua脚本
校验通过?
减少重入计数
计数=0?
删除锁并发布通知
更新过期时间
抛出异常
取消看门狗任务

四、关键设计亮点

  1. 可重入锁设计

    • 使用Hash结构存储 clientId:重入次数
    • 避免同一线程多次加锁导致死锁
  2. 锁续命机制

    • 默认每10秒续期一次(internalLockLeaseTime/3)
    • 续期时间可配置(默认30秒)
  3. 高效等待机制

    // 订阅锁释放通知
    RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {// 超时处理
    }
    
  4. 容错处理

    • 网络异常时自动重试
    • 加锁超时自动取消
    • 看门狗线程异常自动终止

五、最佳实践建议

  1. 锁命名规范

    // 使用业务前缀+资源ID
    RLock lock = redisson.getLock("order:pay:" + orderId);
    
  2. 超时时间设置

    // 根据业务最大耗时设置
    lock.lock(10, TimeUnit.SECONDS);
    
  3. 避免长事务

    • 超过30秒的业务考虑拆分
    • 监控看门狗日志,警惕续期失败
  4. 集群环境特别配置

    Config config = new Config();
    config.useClusterServers().setCheckSlotsCoverage(false); // 避免slot覆盖检查
    

六、性能优化点

  1. 减少网络往返

    • 所有关键操作用Lua脚本实现
    • 单次请求完成多个操作
  2. 避免无效轮询

    • 通过发布订阅通知等待线程
    • 精确控制重试时机
  3. 轻量级看门狗

    • 定时任务而非持续轮询
    • 空闲时自动释放资源

通过Redisson的这套实现,分布式锁在保证安全性的同时,实现了高性能和高可用性,是生产环境的首选方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91595.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91595.shtml
英文地址,请注明出处:http://en.pswp.cn/web/91595.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CS231n-2017 Lecture8深度学习框架笔记

深度学习硬件&#xff1a;CPU:CPU有数个核心&#xff0c;每个核心可以独立工作&#xff0c;同时进行多个线程&#xff0c;内存与系统共享GPU&#xff1a;GPU有上千个核心&#xff0c;但每个核心运行速度很慢&#xff0c;适合并行做类似的工作&#xff0c;不能独立工作&#xff…

以ros的docker镜像为例,探讨docker镜像的使用

标题以ros的docker镜像为例&#xff0c;探讨docker镜像的使用&#xff08;待完善&#xff09; 1. docker介绍&#xff08;以ros工程距离&#xff09; &#xff08;1&#xff09;个人理解&#xff1a;docker就是一个容器&#xff0c;主要的作用就是将环境打包好&#xff0c;方…

Android Audio实战——TimeCheck机制解析(十三)

上一篇文章我们虽然通过 tombstoned Log 推断出 audioserver 崩溃的原因就是系统调用内核接口时发生阻塞,导致 TimeCheck 检测超时异常而崩溃,但并没有实质性的证据证明是 kernel 层出现问题导致的崩溃,因此这里我们继续看一下 TimeCheck 的检测原理。 一、TimeCheck机制 T…

飞机大战小游戏

1.视觉设计&#xff1a;采用柔和的蓝紫色渐变背景&#xff0c;营造梦幻感飞机、敌机和子弹使用柔和的糖果色调添加了粒子爆炸效果&#xff0c;增强视觉反馈星星收集物增加游戏趣味性2.游戏机制&#xff1a;玩家使用左右方向键控制飞机移动空格键发射子弹P键暂停游戏击落敌机获得…

Linux 启动服务脚本

1. 创建命令文件# 创建可执行文件 touch 文件名称 例&#xff1a; touch stopServer.sh2. 命令文件授权# 授权文件可执行权限 chmod 777 文件名称 例&#xff1a; chmod 777 stopServer.sh3. 停止服务命令编写#!/bin/bash# 获取进程号 pidps -ef | grep -- /mnt/apache-tomcat-…

【华为机试】34. 在排序数组中查找元素的第一个和最后一个位置

文章目录34. 在排序数组中查找元素的第一个和最后一个位置描述示例 1&#xff1a;示例 2&#xff1a;示例 3&#xff1a;提示&#xff1a;解题思路算法分析问题本质分析双重二分查找详解左边界查找过程右边界查找过程算法流程图边界情况分析各种解法对比二分查找变种详解时间复…

【网络编程】WebSocket 实现简易Web多人聊天室

一、实现思路 Web端就是使用html JavaScript来实现页面&#xff0c;通过WebSocket长连接和服务器保持通讯&#xff0c;协议的payload使用JSON格式封装 服务端使用C配合第三方库WebSocket和nlonlohmann库来实现 二、Web端 2.1 界面显示 首先&#xff0c;使用html来设计一个…

AI 驱动、设施扩展、验证器强化、上线 EVM 测试网,Injective 近期动态全更新!

作为一个专注于金融应用、且具有高度可互操作性的高性能 Layer-1 区块链&#xff0c;Injective 自诞生以来便为开发者提供有即插即用的技术模块&#xff0c;以便开发者能够更好地搭建新一代 Web3 金融类应用。谈及项目发展的愿景和基本定位&#xff0c;创始团队曾提到希望 Inje…

Qt-----初识

1. 什么是Qt定义&#xff1a;Qt是一个跨平台的应用程序和用户界面框架&#xff0c;主要用于开发具有图形用户界面的应用程序&#xff0c;同时也支持非GUI程序的开发。 编程语言&#xff1a;主要使用C&#xff0c;但也提供了对Python&#xff08;PyQt&#xff09;、JavaScript&a…

理解微信体系中的 AppID、OpenID 和 UnionID

前言: 在开发微信相关的服务(如小程序,公众号,微信开放平台等)时,很多人都会接触到几个看起来相似但实际用途不同的额ID: AppiD, OpenID,UnionID. 搞清楚这三者的区别,是微信生态开发中的基本功,本文将从开发者视角触发,深入浅出地解释它们的关系,区别以及实际应用场景一.什么是…

FFmpeg,如何插入SEI自定义数据

FFmpeg&#xff0c;如何插入SEI自定义数据 一、什么是SEI&#xff1f; SEI&#xff08;Supplemental Enhancement Information&#xff0c;补充增强信息&#xff09;是H.264/H.265视频编码标准中的一种元数据载体&#xff0c;它允许在视频流中嵌入额外的信息&#xff0c;如时…

为什么分类任务偏爱交叉熵?MSE 为何折戟?

在机器学习的世界里&#xff0c;损失函数是模型的“指南针”——它定义了模型“好坏”的标准&#xff0c;直接决定了参数优化的方向。对于分类任务&#xff08;比如判断一张图片是猫还是狗&#xff09;&#xff0c;我们通常会选择交叉熵作为损失函数&#xff1b;而在回归任务&a…

[echarts]横向柱状图

前言 接到一个需求&#xff0c;需要展示一个横向的柱状图&#xff0c;按数量从大到小排序&#xff0c;并定时刷新 使用react配合echarts进行实现。 react引入echarts import React, { useEffect, useRef } from react; import * as echarts from echarts; import DeviceApi fro…

【开源项目】轻量加速利器 HubProxy 自建 Docker、GitHub 下载加速服务

​​引言​​ 如果你经常被 Docker 镜像拉取、GitHub 文件下载的龟速折磨&#xff0c;又不想依赖第三方加速服务&#xff08;担心稳定性或隐私&#xff09;&#xff0c;今天分享的 ​​HubProxy​​ 可能正是你需要的。这个开源工具用一行命令就能部署&#xff0c;以极低资源消…

java web jsp jstl练习

JSP 的学习。 核心功能模块 1. 源代码层 &#xff08; src &#xff09; HelloWorld &#xff1a;主程序入口领域模型 &#xff1a; domain 包含User.java和ceshi.java控制器 &#xff1a; servlet 包含登录验证和验证码相关ServletWeb表现层 &#xff08; web &#xff09; JS…

VSCode 完全指南:释放你的编码潜能

零、简介 在当今的软件开发领域&#xff0c;代码编辑器的选择至关重要&#xff0c;它就像是工匠手中的工具&#xff0c;直接影响着工作效率和成果质量。Visual Studio Code&#xff08;简称 VSCode&#xff09;自问世以来&#xff0c;迅速在全球开发者社区中崭露头角&#xff…

《n8n基础教学》第一节:如何使用编辑器UI界面

在本课中&#xff0c;你将学习如何操作编辑器界面。我们将浏览画布&#xff0c;向您展示每个图标的含义&#xff0c;以及在 n8n 中构建工作流程时在哪里可以找到您需要的东西。本课程基于 n8n 最新版本 。在其他版本中&#xff0c;某些用户界面可能有所不同&#xff0c;但这不会…

gcc g++ makefile CMakeLists.txt cmake make 的关系

gcc&#xff1a;C语言编译器g&#xff1a;C编译器makefile&#xff1a;定义编译规则、依赖关系和构建目标。可以手动编写&#xff0c;也可以由CMakeLists.txt生成cmake&#xff1a;读取CMakeLists.txt文件&#xff0c;生成Makefilemake&#xff1a;构建工具&#xff0c;执行Mak…

SFT 训练器

SFT 训练器 “训练时间到!” 我们现在终于可以创建一个监督微调训练器的实例了: trainer = SFTTrainer( model=model, processing_class=tokenizer, args=sft_config, train_dataset=dataset, )SFTTrainer 已经对数据集进行了预处理,因此我们可以深入查看,了解每个小批次…

Android Material Components 全面解析:打造现代化 Material Design 应用

引言 在当今移动应用开发领域&#xff0c;用户体验(UX)已成为决定应用成功与否的关键因素之一。Google推出的Material Design设计语言为开发者提供了一套完整的视觉、交互和动效规范&#xff0c;而Material Components for Android(MDC-Android)则是将这些设计理念转化为可重用…