【分布式微服务云原生】《Redis 分布式锁的挑战与解决方案及 RedLock 的强大魅力》

《Redis 分布式锁的挑战与解决方案及 RedLock 的强大魅力》

摘要: 本文深入探讨了使用 Redis 做分布式锁时可能遇到的各种问题,并详细阐述了相应的解决方案。同时,深入剖析了 RedLock 作为分布式锁的原因及原理,包括其多节点部署、获取锁、释放锁等关键步骤。读者将通过本文了解到如何在实际应用中正确使用 Redis 分布式锁,避免潜在问题,并充分发挥 RedLock 的优势,提升系统的可靠性和安全性。

关键词:Redis 分布式锁、问题解决、RedLock、原子性、锁超时、可重入性

一、Redis 分布式锁存在的问题及解决方案

  1. 原子性问题
    • 问题:在 Redis 中,SETNX(set if not exists)和EXPIRE(设置过期时间)两个操作不是原子性的,可能导致锁的设置不安全。
    • 解决方案:使用 Lua 脚本将这两个操作合并为一个原子操作,确保加锁和设置超时时间要么同时成功,要么同时失败。
    • Java 代码示例
Jedis jedis = new Jedis("localhost", 6379);
String luaScript = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end";
Object result = jedis.eval(luaScript, 1, "lockKey", "lockValue", "30000");
if ("1".equals(result.toString())) {System.out.println("加锁成功");
} else {System.out.println("加锁失败");
}
  1. 锁超时问题
    • 问题:如果锁的持有者在释放锁之前崩溃了,那么锁将不会被释放,导致死锁。
    • 解决方案:为锁设置一个合理的超时时间,即使持有者崩溃,锁也会在超时后自动释放。
  2. 锁的可重入性
    • 问题:在可重入锁中,同一个线程可能多次获取同一把锁,必须确保锁能够被正确地释放。
    • 解决方案:使用线程的标识符(如 UUID)和重入次数来确保锁可以被正确地释放。
    • Java 代码示例
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;class ReentrantRedisLock {private Map<String, Integer> threadLockCount = new HashMap<>();private String lockKey;private String lockValue;public ReentrantRedisLock(String lockKey) {this.lockKey = lockKey;this.lockValue = UUID.randomUUID().toString();}public boolean lock() {String currentThreadId = Thread.currentThread().getName();if (threadLockCount.containsKey(currentThreadId) && threadLockCount.get(currentThreadId) > 0) {threadLockCount.put(currentThreadId, threadLockCount.get(currentThreadId) + 1);return true;}Jedis jedis = new Jedis("localhost", 6379);if (jedis.setnx(lockKey, lockValue) == 1) {jedis.pexpire(lockKey, 30000);threadLockCount.put(currentThreadId, 1);return true;}return false;}public boolean unlock() {String currentThreadId = Thread.currentThread().getName();if (!threadLockCount.containsKey(currentThreadId) || threadLockCount.get(currentThreadId) <= 0) {return false;}threadLockCount.put(currentThreadId, threadLockCount.get(currentThreadId) - 1);if (threadLockCount.get(currentThreadId) == 0) {Jedis jedis = new Jedis("localhost", 6379);if (jedis.get(lockKey).equals(lockValue)) {jedis.del(lockKey);}threadLockCount.remove(currentThreadId);}return true;}
}
  1. 误删问题
    • 问题:如果多个线程尝试获取同一把锁,可能会有线程误删其他线程已经获取的锁。
    • 解决方案:在释放锁时,检查锁的当前持有者是否是当前线程,确保只有锁的持有者才能释放它。
    • Java 代码示例
import redis.clients.jedis.Jedis;class SafeRedisLock {private String lockKey;private String lockValue;public SafeRedisLock(String lockKey) {this.lockKey = lockKey;this.lockValue = Thread.currentThread().getName() + "-" + System.currentTimeMillis();}public boolean lock() {Jedis jedis = new Jedis("localhost", 6379);if (jedis.setnx(lockKey, lockValue) == 1) {jedis.pexpire(lockKey, 30000);return true;}return false;}public boolean unlock() {Jedis jedis = new Jedis("localhost", 6379);String currentValue = jedis.get(lockKey);if (currentValue!= null && currentValue.equals(lockValue)) {jedis.del(lockKey);return true;}return false;}
}
  1. 自动续期问题
    • 问题:如果业务执行时间超过锁的超时时间,锁将被释放,但业务尚未完成。
    • 解决方案:使用后台线程(看门狗)定期检查和续期锁的超时时间。
  2. 安全性问题
    • 问题:锁可能被其他客户端误操作或恶意释放。
    • 解决方案:使用具有唯一性的值(如 UUID)作为锁的 value,确保只有设置该锁的客户端可以释放它。
  3. 主从复制延迟问题
    • 问题:在主从复制架构中,如果主节点在复制完成前崩溃,从节点可能接管了没有锁信息的数据库。
    • 解决方案:使用 Redlock 算法,它通过尝试在多个 Redis 实例上加锁来提高锁的安全性。
  4. 锁的粒度问题
    • 问题:粗粒度的锁可能影响并发性能,而细粒度的锁可能难以管理。
    • 解决方案:根据业务需求合理设计锁的粒度,或使用读写锁(Redisson 支持)来提高性能。
  5. 大量失败请求的自旋锁
    • 问题:在高并发情况下,大量的请求可能因锁而被阻塞。
    • 解决方案:合理设计重试策略和超时策略,避免无限期地等待锁。
  6. 读写锁效率问题
    • 问题:在读写锁中,读锁可能阻塞写锁,导致性能问题。
    • 解决方案:优化锁的使用策略,如使用 Redisson 提供的公平锁或非公平锁。
  7. 大 Key 问题影响集群性能:Redis 集群中大 Key 可能导致数据分布不均,影响写入性能。
    • 解决方案: 对大 Key 进行拆分,确保每个 Key 的大小和成员数量合理,维持集群内数据均衡。

二、RedLock 作为分布式锁的原因及原理

  1. 多节点部署
    • RedLock 算法使用多个独立的 Redis 实例(通常是奇数个,如 5 个),这些实例之间不进行数据复制或其他形式的通信,以确保它们完全独立运行。
  2. 获取锁
    • 客户端尝试从每个 Redis 实例获取锁,通过发送一个具有唯一标识和较短过期时间的锁请求。客户端设置一个超时时间,这个时间应小于锁的过期时间,以避免在某个 Redis 实例响应超时时客户端无限期地等待。
  3. 多数节点共识
    • 如果客户端能够在大多数(N/2 + 1 个)Redis 实例上成功获取锁,并且从获取第一个锁到最后一个锁的总耗时小于锁的过期时间,那么认为客户端成功获取了分布式锁。
  4. 锁的安全性
    • 如果客户端未能在超过一半的 Redis 实例上获取锁,或者获取锁的总时间超过了锁的过期时间的一半,则认为加锁失败,客户端需要尝试重新获取锁。
  5. 避免死锁
    • 即使客户端在获取锁后崩溃或无法正常释放锁,由于锁具有过期时间,锁最终会自动释放,从而避免了死锁的发生。
  6. 容错性
    • RedLock 算法具有容错性,即使部分 Redis 节点宕机,只要大多数节点(即过半数以上的节点)仍在线,RedLock 算法就能继续提供服务,并确保锁的正确性。
  7. 释放锁
    • 客户端完成对受保护资源的操作后,需要向所有曾获取锁的 Redis 实例发送释放锁的请求。如果客户端无法完成释放锁的操作,由于锁的自动过期机制,锁最终也会被释放。
  8. 故障处理
    • 如果在任意节点发现锁已经存在,或者在多数节点上未能成功获取锁,客户端应立即放弃并重试,确保不会误删其他客户端的锁。
  9. 时钟漂移校正
    • 考虑到服务器间可能存在的时间不一致(时钟漂移),RedLock 在计算锁的过期时间时会加入一定的误差范围,确保即使有轻微的时间偏差,也不会影响锁的正确性。

RedLock 流程图

graph TD;A[客户端发起获取锁请求] --> B[尝试向多个 Redis 实例获取锁];B --> C{在大多数实例上获取成功?};C -->|是| D[认为获取锁成功];C -->|否| E[加锁失败,重试];D --> F[操作受保护资源];F --> G[向所有实例发送释放锁请求];G --> H[完成释放锁];

三、Redis 分布式锁与 RedLock 的对比

对比项Redis 分布式锁RedLock
原子性需要使用 Lua 脚本保证自动保证原子性
安全性存在主从复制延迟等安全风险通过多节点提高安全性
容错性相对较低较高,部分节点宕机仍能工作

四、总结

通过对 Redis 分布式锁存在的问题及解决方案的探讨,以及对 RedLock 作为分布式锁的原因及原理的分析,我们可以看出,在分布式系统中,选择合适的锁机制至关重要。Redis 分布式锁在一定程度上满足了多线程和多进程环境下的锁需求,但也存在一些问题需要我们谨慎处理。而 RedLock 则通过多节点部署等方式,提高了分布式锁的可靠性和安全性。

在实际应用中,我们应根据具体的业务场景和需求,选择合适的锁机制,并充分考虑各种潜在的问题和风险。同时,也可以借助开源框架如 Redisson 来简化分布式锁的使用和管理。

快来评论区分享你在使用 Redis 分布式锁和 RedLock 过程中的观点和经验吧!让我们一起交流学习,共同进步!😉

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

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

相关文章

HarmonyOS鸿蒙- 一行代码自动换行技巧

DevEco Studio 编辑器设置 一行代码自动换行显示。 一、代码自动换行设置方式路径&#xff1a;File > Editor > General 如图: 二、找到标题&#xff1a;Soft Wraps 勾选《Soft-wrap these files:》&#xff0c;然后在后面添加*.ets 然后保存即可。添加后&#xff0c…

【TIMM库】是一个专门为PyTorch用户设计的图像模型库 python库

TIMM库 1、引言&#xff1a;遇见TIMM2、初识TIMM&#xff1a;安装与基本结构3、实战案例一&#xff1a;图像分类4、实战案例二&#xff1a;迁移学习5、实战案例三&#xff1a;模型可视化6、结语&#xff1a;TIMM的无限可能 1、引言&#xff1a;遇见TIMM 大家好&#xff0c;我是…

LangSplat和3D language fields简略介绍

LangSplat: 3D Language Gaussian Splatting 相关技术拆分解释&#xff1a; 3dgs&#xff1a;伟大无需多言SAM&#xff1a;The Segment Anything Model&#xff0c;是图像分割领域的foundational model&#xff0c;已经用在很多视觉任务上&#xff08;如图像修复、物体追踪、图…

支持国密算法的数字证书-国密SSL证书详解

在互联网中&#xff0c;数字证书作为标志通讯各方身份信息的数字认证而存在&#xff0c;常见的数字证书大都采用国际算法&#xff0c;比如RSA算法、ECC算法、SHA2算法等。随着我国加强网络安全技术自主可控的大趋势&#xff0c;也出现了支持国密算法的数字证书-国密SSL证书。那…

OpenCV高级图形用户界面(21)暂停程序执行并等待用户按键输入函数waitKey()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 等待按键 该函数 waitKey 在 delay≤0 时无限等待按键事件&#xff0c;或者在 delay 为正数时等待 delay 毫秒。由于操作系统在切换线程时有最小…

一文详解视频参数——FFmpeg -i选项下的视频参数解析

随着多媒体内容在网络上的日益普及,对视频文件的处理需求也变得越来越重要。FFmpeg 是一款强大的跨平台音视频编解码库及工具集,能够帮助开发者实现对音频、视频文件的转码、剪辑、合并等多种功能。本文将重点探讨如何利用 ffmpeg -i xxx.mp4 来获取视频文件的基本信息,并对…

前端工程启动工具

一些思考 在公司项目中&#xff0c;需要启一个新的前端工程&#xff08;一个基于Webpack的React工程&#xff09;。因为同一个项目中有其他的前端工程&#xff0c;我们最开始想的是参考另外一个工程的配置重启一个新的工程&#xff0c;但是又因为原来的工程用的库版本都比较老…

软件设计师:软件工程

文章目录 一、开发模型&#xff08;1&#xff09;瀑布模型&#xff08;需求明确&#xff09;&#xff08;2&#xff09;增量模型&#xff08;快速构建&#xff09;&#xff08;3&#xff09;演化模型&#xff08;迭代模型&#xff09;&#xff08;3.1&#xff09;原型模型&…

自学C语言——函数(全)

接上一篇&#xff1a;自学C语言——数组&#xff08;二&#xff09; 1.函数的概念 C语言中&#xff1a;函数子程序 C语言中的函数就是一个完成某项特定的任务的一小段代码。 2.库函数 标准库和头文件 C语言标准中规定了C语言的语法规则&#xff0c;C语言不提供库函数&am…

Ubuntu 22.04上安装Miniconda

在Ubuntu 22.04上安装Miniconda&#xff0c;可以按照以下步骤进行&#xff1a; 步骤1&#xff1a;更新系统 首先&#xff0c;更新您的系统软件包&#xff1a; sudo apt update sudo apt upgrade -y步骤2&#xff1a;下载Miniconda安装脚本 访问Miniconda的官方网站或使用以下…

IAR全面支持旗芯微车规级MCU,打造智能安全的未来汽车

中国上海&#xff0c;2024年10月18日 — 在全球汽车电子快速发展的今天&#xff0c;IAR与苏州旗芯微半导体有限公司&#xff08;以下简称“旗芯微”&#xff09;联合宣布了一项激动人心的合作——IAR Embedded Workbench for Arm 9.60.2版本现已全面支持旗芯微车规级MCU&#x…

【Docker】docker | 部署nginx

一、概述 记录下nginx的部署流程&#xff1b;将conf配置文件映射到宿主机 前提依赖&#xff1a;自行准备nginx的镜像包 二、步骤 1、运行、无映射 docker run --name nginx -p 80:80 -d nginx:1.18.0-alpine 80&#xff1a;80&#xff0c;前面是宿主机端口&#xff1b;如果冲…

IPsec简单介绍

VPN相关介绍 VPN&#xff1a;虚拟私有网络 例如&#xff1a;像这种不加密的 PPTPL2TP ------- 一般用在windows server 服务端&#xff08;但是大多数企业不用这个&#xff09; 假如总公司内部的PC1要去访问分公司内部的PC2&#xff08;一般用在公司服务器有内网的服务&#…

vue需要清除定时器和延时器吗

在更新组件时清除定时器&#xff1a; 如果你的定时器是在组件的更新过程中创建的&#xff0c;你可能需要在更新前清除它&#xff0c;以免重复创建。你可以在组件的beforeUpdate钩子中清除定时器。 例如&#xff0c;在Vue2中&#xff0c;你可以这样清除定时器&#xff1a; exp…

【知识科普】今天聊聊前端打包工具webpack

文章目录 webpack概述1. 入口&#xff08;Entry&#xff09;2. 输出&#xff08;Output&#xff09;3. Loader4. 插件&#xff08;Plugins&#xff09;5. 模式&#xff08;Mode&#xff09;6. 浏览器兼容性&#xff08;Browser Compatibility&#xff09;7. 环境&#xff08;En…

Oracle 使用位图索引 Cost降低200倍! 探讨位图索引的利与弊

一.简介 位图索引&#xff08;Bitmap Index&#xff09; 是 Oracle 数据库中一种特殊类型的索引&#xff0c;适用于低基数&#xff08;Low Cardinality&#xff09;列&#xff0c;即那些列中可选值相对较少的情况下使用。它与常规的 B-tree 索引不同&#xff0c;位图索引通过位…

Vue组件学习 | 二、Vuex组件

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。以下是 Vuex 的基本用法 Vuex 基本用法 安装 Vuex 首先&#xff0c;你需要安装 Vuex。如果你使用的是 n…

003:无人机概述

摘要&#xff1a;本文介绍无人机的定义和分类、无人机系统定义、民用无人机驾驶员分类和应用领域。 一、无人机的定义和分类 1.无人机定义 无人机是一种能够在无人驾驶的条件下完成复杂空中飞行任务和各种负载任务的飞行器&#xff0c;可以被视为“空中机器人”。它利用先进的…

(48)MATLAB使用firls函数设计均衡器

文章目录 前言一、频域均衡器的设计二、MATLAB源代码1.firls函数与freqz函数2.MATLAB仿真源代码3.代码说明 三、仿真结果画图 前言 利用MATLAB的firls函数&#xff0c;根据所要求的频率向量和频响幅度向量&#xff0c;设计出所需的均衡器&#xff0c;使得包括滤波器在内的系统…

【python爬虫】python的requests模块使用`Session`对象可以保持会话状态,自动处理Cookie等信息

1. 请求发送 网络爬虫的第一步是发送HTTP请求。Python中的requests库是发送请求的首选工具&#xff0c;它简单易用且功能强大。 使用requests库 import requestssession requests.Session() session.headers {"User-Agent": "Mozilla/5.0",# 其他请求…