分布式锁实战:Redisson vs. Redis 原生指令的性能对比

分布式锁实战:Redisson vs. Redis 原生指令的性能对比

引言

在DIY主题模板系统中,用户可自定义聊天室的背景、图标、动画等元素。当多个运营人员或用户同时修改同一模板时,若没有锁机制,可能出现“甲修改了背景色,乙覆盖了甲的修改”的脏写问题。此时,分布式锁成为解决资源互斥访问的核心工具。

市场上常见的分布式锁方案有两种:

  • Redis原生指令(如SET key value NX PX):轻量、性能高,但需手动处理超时、可重入等边界问题;
  • Redisson:基于Redis的Java客户端,封装了RedLock算法,提供可重入锁、公平锁等高级特性,但实现复杂度更高。

本文将结合DIY主题模板系统的实际场景,从应用场景底层原理常见坑点压测对比选型建议五大维度,深入解析两种方案的差异,并给出实战指导。

一、分布式锁的应用场景:DIY主题模板系统的互斥需求

1.1 业务背景

DIY主题模板系统的核心功能是模板配置的增删改查,典型操作流程如下:

  1. 运营人员通过后台选择模板ID(如template-123);
  2. 系统从数据库读取模板当前配置(背景色、图标路径等);
  3. 运营人员修改配置(如将背景色从#FFFFFF改为#FF0000);
  4. 系统将新配置写回数据库。

1.2 并发问题与锁需求

当两个运营人员同时修改同一模板时,可能出现以下问题:

  • 丢失更新:甲读取旧配置→乙读取旧配置→甲写入新配置→乙写入新配置(覆盖甲的修改);
  • 脏数据:甲修改图标路径但未提交→乙基于旧路径修改其他字段→甲回滚导致乙的数据依赖失效。

1.3 分布式锁的价值

通过为模板ID(如template-123)加锁,确保同一时刻仅一个请求能修改该模板,流程如图1所示:

请添加图片描述

二、Redisson的底层原理:基于RedLock算法的增强实现

2.1 为什么需要RedLock?

传统的单节点Redis锁(如SET key value NX PX)存在单点故障风险:若Redis主节点宕机且未同步到从节点,锁可能被重复获取。为解决此问题,Redis作者提出了RedLock算法(Redisson默认实现),通过多个独立Redis实例(通常5个)提升可靠性。

2.2 RedLock的获取与释放流程

RedLock的核心逻辑是:向N个独立Redis节点依次申请锁,若在多数节点(N/2+1)成功获取锁,则认为加锁成功。具体流程如图2所示:
请添加图片描述

2.3 Redisson的封装与扩展

Redisson基于RedLock算法,提供了以下增强功能:

  • 可重入锁:同一线程可多次获取同一锁(通过lockCount计数器实现);
  • 公平锁:按请求顺序分配锁(通过Semaphore队列实现);
  • 锁续期:若业务执行时间超过锁过期时间,自动延长锁的有效期(“看门狗”机制);
  • 异步锁:支持lockAsync()/unlockAsync()异步操作,避免阻塞线程。

三、原生Redis指令的坑:从“可用”到“可靠”的距离

3.1 原生Redis锁的基础实现

通过SET key value NX PX指令可实现基础的分布式锁(NX表示仅当key不存在时设置,PX设置过期时间):

public boolean tryLock(String lockKey, String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);return "OK".equals(result);
}public void unlock(String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
}

3.2 原生实现的三大致命问题

(1)问题一:超时导致的锁误删

场景:业务执行时间超过锁的过期时间(如锁设置为30秒,业务执行了40秒),锁自动释放后,其他线程获取锁。此时原线程完成业务后,会删除新线程的锁,导致互斥失效。

原因:锁的过期时间与业务执行时间不匹配,且原生实现未提供自动续期机制。

(2)问题二:不可重入导致的死锁

场景:同一线程在未释放锁的情况下,再次尝试获取同一锁(如递归调用),由于锁已存在(NX条件不满足),加锁失败,导致死锁。

原因:原生Redis锁仅记录“锁是否存在”,未记录“持有锁的线程ID”,无法判断是否是同一线程重复获取。

(3)问题三:单点故障导致的锁失效

场景:Redis主节点宕机,未同步到从节点,新主节点未感知旧锁存在,其他线程可重新获取锁,导致同一资源被多个线程同时修改。

原因:单节点Redis无法保证高可用,锁的可靠性依赖单点。

四、压测对比:Redisson vs. 原生Redis的性能差异

为量化两种方案的性能差异,我们基于DIY主题模板系统的实际场景,设计了以下压测实验:

4.1 压测环境与参数

维度配置/值
服务器8核16G Linux(CentOS 7)
Redis集群5节点(3主2从,主节点内存8G)
压测工具JMeter(500线程,循环100次)
业务场景高并发修改同一模板(锁竞争激烈)

4.2 压测指标说明

  • TPS(每秒事务数):单位时间内成功获取并释放锁的次数;
  • 平均延迟(ms):从请求加锁到释放锁的总耗时;
  • 锁冲突率(%):加锁失败的请求占比(原生Redis无自动重试,Redisson默认重试3次);
  • 锁误删率(%):释放非自己持有的锁的概率。

4.3 压测结果与分析

(1)场景1:短耗时业务(业务执行时间<锁过期时间)
  • 原生Redis锁

    • TPS:12000
    • 平均延迟:8ms
    • 锁冲突率:5%(无重试)
    • 锁误删率:0%(业务执行时间<过期时间,无超时)
  • Redisson(RedLock)

    • TPS:8000
    • 平均延迟:15ms
    • 锁冲突率:2%(自动重试3次)
    • 锁误删率:0%(看门狗自动续期)
(2)场景2:长耗时业务(业务执行时间>锁过期时间)
  • 原生Redis锁

    • TPS:11000
    • 平均延迟:9ms
    • 锁冲突率:8%(部分请求因锁过期被拒绝)
    • 锁误删率:12%(原线程释放已过期的锁)
  • Redisson(RedLock)

    • TPS:7500
    • 平均延迟:18ms
    • 锁冲突率:3%(自动重试+续期)
    • 锁误删率:0%(看门狗续期至业务完成)
(3)场景3:Redis主节点宕机(模拟故障)
  • 原生Redis锁

    • 锁失效时间:30秒(主从切换耗时)
    • 锁冲突率:40%(主节点宕机期间,从节点未同步锁信息)
  • Redisson(RedLock)

    • 锁失效时间:5秒(多数节点存活,仍可保证锁有效)
    • 锁冲突率:5%(仅需多数节点存活即可维持锁)

4.4 数据结论

维度原生Redis锁Redisson(RedLock)
性能(TPS)高(轻量无额外开销)低(需与多个节点交互)
可靠性低(单点故障/超时误删)高(多节点+自动续期)
开发成本高(需手动处理边界问题)低(封装完善,开箱即用)

五、最佳实践:如何选择分布式锁方案?

5.1 按业务场景选择

(1)选择原生Redis锁的场景:
  • 性能敏感:如高频交易系统(每秒上万次锁操作),轻量指令更适合;
  • 短耗时业务:业务执行时间明确<锁过期时间(如5秒内),无需续期;
  • 弱一致性:允许偶发锁冲突(如用户评论点赞,重复点赞可通过幂等处理)。
(2)选择Redisson的场景:
  • 强一致性:如财务系统、配置修改(必须保证互斥);
  • 长耗时业务:业务执行时间不确定(如文件上传、复杂计算),需自动续期;
  • 高可用要求:系统依赖Redis集群(如电商大促、直播活动),需避免单点故障。

5.2 分布式锁的通用设计原则

  1. 锁粒度最小化:仅对核心资源加锁(如模板ID),避免锁整个服务;
  2. 过期时间合理:根据业务执行时间设置(建议为业务耗时的2~3倍),或启用Redisson的看门狗续期;
  3. 唯一标识防误删:锁值设置为请求唯一ID(如UUID),释放时校验(原生Redis通过Lua脚本实现);
  4. 监控与报警:记录锁获取失败率、锁持有时间,及时发现异常(如锁未释放导致的死锁)。

六、实战:在DIY主题模板系统中落地

6.1 原生Redis锁的实现(短耗时场景)

// 短耗时业务(如修改模板背景色,耗时约2秒)
public void updateTemplate(String templateId, String newConfig) {String lockKey = "lock:template:" + templateId;String requestId = UUID.randomUUID().toString();int expireTime = 5000; // 过期时间5秒(业务耗时2秒×2.5)// 加锁boolean locked = tryLock(lockKey, requestId, expireTime);if (!locked) {throw new RuntimeException("模板正在被修改,请稍后再试");}try {// 业务逻辑:读取旧配置→修改→写入String oldConfig = templateDao.get(templateId);String mergedConfig = mergeConfig(oldConfig, newConfig);templateDao.update(templateId, mergedConfig);} finally {// 释放锁(通过Lua脚本防误删)unlock(lockKey, requestId);}
}

6.2 Redisson的实现(长耗时场景)

// 长耗时业务(如模板批量上传,耗时约30秒)
public void batchUploadTemplate(String templateId, List<Resource> resources) {RLock lock = redissonClient.getLock("lock:template:" + templateId);try {// 加锁(自动续期,过期时间默认30秒)boolean locked = lock.tryLock(10, TimeUnit.SECONDS); // 最多等待10秒if (!locked) {throw new RuntimeException("模板正在被修改,请稍后再试");}// 业务逻辑:上传资源→生成配置→写入数据库(耗时30秒)for (Resource resource : resources) {ossClient.upload(resource.getPath(), resource.getContent());}String newConfig = generateConfig(resources);templateDao.update(templateId, newConfig);} finally {lock.unlock();}
}

总结

分布式锁的选择没有“最优解”,需结合业务场景(性能要求、一致性等级)、技术成本(开发维护难度)、系统架构(Redis集群规模)综合判断:

  • 原生Redis锁适合性能敏感、短耗时、弱一致性的场景,但需手动处理边界问题;
  • Redisson适合强一致性、长耗时、高可用的场景,通过封装降低开发成本。

在DIY主题模板系统中,我们对短耗时的“单个配置修改”使用原生Redis锁(TPS高,满足业务需求),对长耗时的“批量资源上传”使用Redisson(避免锁超时误删,保障数据一致性)。

希望本文的实践经验能帮助你在实际项目中做出更合理的选择!

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

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

相关文章

C++ 设计模式《复制粘贴的奇迹:小明的原型工厂》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09; &#x1f4d6; 背景故事 创业初期&#xff0c;小明每天加班写配送路线、配送策略、营销套餐。可当业务做大后&#xff0c;他发现大家常常下单“上次那个套餐”—— “老…

【Elasticsearch】映射:fielddata 详解

映射&#xff1a;fielddata 详解 1.fielddata 是什么2.fielddata 的工作原理3.主要用法3.1 启用 fielddata&#xff08;通常在 text 字段上&#xff09;3.2 监控 fielddata 使用情况3.3 清除 fielddata 缓存 4.使用场景示例示例 1&#xff1a;对 text 字段进行聚合示例 2&#…

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分

本文主要分享一个开源的 GPU 虚拟化方案&#xff1a;HAMi&#xff0c;包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案&#xff0c;HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制&#xff0c;保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…

PlayDiffusion上线:AI语音编辑进入“无痕时代”

在语音合成与语音编辑领域&#xff0c;一个长期存在的挑战是如何在修改语音内容的同时&#xff0c;保持原始语音的自然性、连贯性和说话人特征。近日&#xff0c;一款名为 PlayDiffusion 的新型 AI 语音修复模型应运而生&#xff0c;成功实现了这一目标。 PlayDiffusion 是一个…

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…

微信小程序前端面经

一、技术栈与编码能力&#xff08;10min&#xff09; 1. Vue 3 & Composition API Q1&#xff1a;请解释一下 ref 和 reactive 的区别&#xff1f;你在项目中是如何使用的&#xff1f; 答&#xff1a;ref是包装一个原始值或对象&#xff0c;通过.value访问&#xff0c;r…

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…

Houdini POP入门学习07 - 分组

使用PopGroup可对粒子进行分组操作&#xff0c;并通过表达式从而更灵活的处理粒子行为。 1.创建box作为发射器&#xff0c;连接popnet节点。 2.双击进入popnet&#xff0c;添加popwind添加向上风力。现在播放粒子可见粒子向上方移动。 3.添加popgroup进行分组&#xff0c;开启…

机器学习复习3--模型评估

误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为&#xff1a;误差&#xff08;error&#xff09;。 误差定义&#xff1a; ①在训练集上的误差称为训练误差&#xff08;training error&#xff09;或经验误差&#xff08;empirical error&#x…

Docker 镜像上传到 AWS ECR:从构建到推送的全流程

一、在 EC2 实例中安装 Docker&#xff08;适用于 Amazon Linux 2&#xff09; 步骤 1&#xff1a;连接到 EC2 实例 ssh -i your-key.pem ec2-useryour-ec2-public-ip步骤 2&#xff1a;安装 Docker sudo yum update -y sudo amazon-linux-extras enable docker sudo yum in…

MobileNet 改进:基于MobileNetV2和SSPP的图像分类

1.创新点分析 在计算机视觉领域,高效的图像分类模型一直是研究热点。 本文将详细解析一个结合了MobileNetV2和空间金字塔池化(SSPP)的深度学习模型实现。 模型概述 这个代码实现了一个轻量级但功能强大的图像分类器,主要包含两个核心组件: MobileNetV2作为特征提取器 自定…

Java中List的forEach用法详解

在 Java 中&#xff0c;List.forEach() 是 Java 8 引入的一种简洁的遍历集合元素的方法。它基于函数式编程思想&#xff0c;接受一个 Consumer 函数式接口作为参数&#xff0c;用于对集合中的每个元素执行操作。 基本语法 java 复制 下载 list.forEach(consumer); 使用示…

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…

计算机视觉与深度学习 | 基于MATLAB的相机标定

基于MATLAB的相机标定:原理、步骤与代码实现 相机标定 基于MATLAB的相机标定:原理、步骤与代码实现MATLAB相机标定完整流程1. 准备工作2. 采集标定图像3. 导入图像并检测角点4. 生成世界坐标5. 执行相机标定6. 分析标定结果7. 应用标定结果校正图像相机标定关键概念相机参数类…

物联网专业核心课程以及就业方向

物联网专业作为信息技术与产业应用深度融合的交叉学科&#xff0c;其课程体系覆盖硬件、软件、网络、数据等全链条技术&#xff0c;就业方向则随智能技术普及呈现多元化趋势。以下是基于最新行业动态与教育实践的系统分析&#xff1a; &#x1f4da; 一、物联网专业核心课程体系…

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…

线性代数证明:把行列式的某一行(列)的k倍加到另一行(列),行列式的值不变

线性代数证明 把行列式的某一行&#xff08;列&#xff09;的k倍加到另一行&#xff08;列&#xff09;&#xff0c;行列式的值不变&#xff1a; 注意五角星的位置要用到另一条性质&#xff1a;若行列式的某一行&#xff08;列&#xff09;的元素都是两数之和&#xff0c;则可以…

webrtc 在线测试, 如何在线拉流测试

1. 如下所示&#xff0c;使用腾讯提供的网页即可&#xff0c;非常赞&#xff0c;测试直播拉流 webrtc协议 WebRTC Player Demo 2.截图&#xff1a;