如何在分布式环境中实现高可靠性分布式锁

目录

一、简单了解分布式锁

(一)分布式锁:应对分布式环境的同步挑战

(二)分布式锁的实现方式

(三)分布式锁的使用场景

(四)分布式锁需满足的特点

二、Redis 实现分布式锁的基本思路(粗糙实现版本)

(一)实现步骤

(二)基本代码展示

(三)上述实现的缺陷

三、健壮分布式锁聚焦

(一)误删问题的分析

问题说明

解决方案

具体实现步骤

具体代码实现

(二)原子性保证

问题场景

解决方案:使用 Lua 脚本

设置锁并设置过期时间(原子操作)

释放锁(原子操作)

Java 调用 Lua 脚本

(三)超时自动解锁

问题描述

传统解决方案

改进方案:锁续期机制

具体实现步骤

Java 实现示例

注意事项

四、总结


随着系统架构逐渐从单机走向分布式,如何在分布式环境下保证线程同步执行成为一个不可忽视的问题。分布式锁作为解决这一问题的关键技术,为分布式系统中的资源共享和任务协调提供了重要支持。选择合适的分布式锁实现方式,可以有效提高系统的可靠性和一致性,确保业务逻辑的正确执行。历 史相关文章回顾:

  • 谈谈Redis分布式锁
  • 分布式锁的几种简单实现方式分析
  • RedLock 与 Redisson 实现分布式锁

一、简单了解分布式锁

在多线程环境下,为了保证同一时间只有一个线程能够执行某段代码,Java 提供了 synchronized 关键字和 ReentrantLock 类作为本地锁的解决方案。这些机制在单个应用或单个 JVM 实例中运行良好,确保了同一进程内的线程同步。但是,随着分布式架构的广泛应用,应用程序通常运行在多个节点上,并且每个节点都有多个线程同时处理任务。在这种情况下,传统的本地锁机制已经无法满足分布式环境下的同步需求。

(一)分布式锁:应对分布式环境的同步挑战

在分布式系统中,应用程序可能运行在多个物理或虚拟的节点上,这意味着相同的资源可能会被不同节点上的多个线程同时访问。为了确保这些线程在不同节点上同步执行,防止资源竞争和数据不一致问题,我们需要使用一种能够跨节点的同步机制——分布式锁

分布式锁是一种用于控制在分布式环境中,某个共享资源在同一时刻只能被一个节点或线程使用的机制。它类似于传统的本地锁,但具有跨节点的协调能力。分布式锁通常由外部的分布式系统组件(如 Redis、Zookeeper、Tair 等)来实现,这些组件提供了高可用的锁服务,确保即使在节点故障或网络分区的情况下,锁的状态依然能够保持一致。

(二)分布式锁的实现方式

分布式锁可以通过多种方式实现,每种方式都有其适用的场景和优缺点。以下是几种常见的分布式锁实现方式(简单直接的实现方式见:分布式锁的几种简单实现方式分析):

  1. 基于 Redis 的分布式锁 Redis 是一种常用的内存数据库,可以通过 SETNX 命令(Set if Not Exists)来实现分布式锁。Redis 锁具有高性能、低延迟的优点,适用于大部分需要快速锁定的场景。通过设置锁的过期时间,可以防止死锁问题。

  2. 基于 Zookeeper 的分布式锁 Zookeeper 是一个分布式协调服务,提供了严格的一致性保证。它通过创建临时有序节点实现分布式锁。Zookeeper 锁的优点是可靠性高,适用于对数据一致性要求高的场景,如分布式事务。

  3. 基于数据库的分布式锁 可以利用数据库的行级锁来实现分布式锁,通过在数据库表中插入一条记录或更新记录的状态来表示加锁。虽然这种方式实现简单,但性能较低,适用于锁争用不激烈的场景。

  4. 基于 Tair 的分布式锁 Tair 是一种高性能分布式缓存系统,也支持分布式锁功能,适用于需要高并发和高可用的场景。

(三)分布式锁的使用场景

分布式锁在分布式系统中有广泛的应用,典型的使用场景如:

  • 分布式任务调度: 确保某个任务在某个时间点只由一个节点执行,防止重复调度。
  • 分布式事务控制: 在多服务参与的分布式事务中,确保事务的各个阶段按照预定顺序执行。
  • 资源竞争 防止多个节点同时修改相同的资源(如数据库记录、缓存数据)导致的数据不一致问题。

(四)分布式锁需满足的特点

特点描述
互斥性确保同一时刻只有一个线程能持有锁,防止多个节点或线程对共享资源的并发访问,保证资源的独占使用。
可重入性允许同一节点上的同一个线程在已持有锁的情况下,能够再次成功获取该锁,避免锁重入时产生死锁。
锁超时通过为锁设置过期时间,防止因线程异常或故障未释放锁而导致的死锁情况,确保系统的稳定性和健壮性。
高性能与高可用性锁的加锁与解锁操作需要高效,以满足高并发需求,并且要确保在节点故障或网络分区等情况下,锁服务依然可用,保障系统的持续运行。
阻塞与非阻塞性支持锁的阻塞和非阻塞模式。在阻塞模式下,线程在锁不可用时等待锁的释放,并在锁可用时及时被唤醒;在非阻塞模式下,线程可以立即返回继续执行其他逻辑。
可扩展性锁机制能够随着系统规模的增长而扩展,支持更多节点和更高并发量,保持系统的性能和可靠性。

二、Redis 实现分布式锁的基本思路(粗糙实现版本)

Redis 是一个高性能的键值存储系统,适合用于实现分布式锁,因为它能够在高并发的场景下提供快速的读写操作。借助 Redis 的 SET 命令及其 NX(不存在则插入)参数,我们可以构建一个简单的分布式锁机制。

(一)实现步骤

  1. 获取锁:通过 SET key value NX EX seconds 命令尝试获取锁。如果 key 不存在,则插入成功,并设置过期时间(EX 参数),表示锁定成功;如果 key 已存在,则表示锁已经被其他客户端持有,获取锁失败。

  2. 解锁:当持有锁的线程完成任务后,可以通过 DEL key 命令删除该 key 来释放锁,从而让其他等待锁的线程有机会获得锁。

  3. 防止死锁:为了防止死锁,在获取锁时设置一个合理的过期时间(TTL),即使由于程序异常未能显式释放锁,锁也会在 TTL 到期后自动释放。

(二)基本代码展示

// 尝试获取锁
if (set(key, 1, "NX", "EX", 30)) {try {// 执行需要加锁的业务逻辑} finally {// 释放锁del(key);}
}

(三)上述实现的缺陷

尽管这种方法简单易用,但它存在几个严重的问题,使得其无法成为一个健壮的分布式锁实现:

  1. 非原子性操作:锁的获取与锁的过期时间设置不是原子操作。假设在 SETNX 成功后,但在设置过期时间之前,程序崩溃或出现异常,那么锁将一直存在,导致其他线程无法获取锁,从而产生死锁。

  2. 锁误解除:当持有锁的线程被阻塞或出现延迟,锁的过期时间到期后自动释放,此时如果有其他线程获取了同一个锁,原本持有锁的线程执行完毕后仍然会执行 DEL 操作,从而误解锁,破坏了其他线程的业务逻辑。

  3. 业务超时自动解锁导致并发问题:由于业务执行时间不确定,如果锁的 TTL 到期,锁会自动释放,可能导致多个线程同时执行临界区代码,从而引发并发问题。

  4. 不可重入性:该实现不支持可重入性,即同一线程无法多次获得同一把锁。如果线程因递归或重复调用需要再次获取锁,会因为锁已经存在而获取失败。

三、健壮分布式锁聚焦

(一)误删问题的分析

问题说明

在分布式锁的实现中,存在一种潜在的风险,即线程在解锁时误删了其他线程持有的锁。具体情况如下:

  1. 线程1持有锁:线程1成功获取了锁并执行了一段业务逻辑。
  2. 线程1阻塞:线程1在执行过程中由于某种原因被阻塞,未能及时释放锁,导致锁的TTL(过期时间)到期,锁自动释放。
  3. 线程2获取锁:此时,线程2尝试获取锁,并成功获得了已经释放的锁。
  4. 线程1解除阻塞:线程1解除阻塞,继续执行并尝试释放锁。
  5. 误删锁:由于线程1并不知道锁已经由线程2重新获取,因此直接执行 DEL 操作,误删了属于线程2的锁。

解决方案

为了解决上述问题,可以在锁中存储一个唯一标识符(例如线程ID或UUID),并在释放锁时检查该标识符是否匹配,从而确保只有持有锁的线程才能成功释放锁。

具体实现步骤

  1. 获取锁时存储标识符:在获取锁时,使用 Redis 的 SET key value NX PX milliseconds 命令,其中 value 是一个唯一标识符(如线程ID或UUID)。这样可以确保在锁存储时记录锁的所有者信息。

  2. 释放锁时校验标识符:在释放锁时,先检查当前锁的值是否与线程的唯一标识符匹配。只有当标识符匹配时,才执行 DEL 操作以释放锁。

具体代码实现

String threadId = UUID.randomUUID().toString(); // 生成唯一标识符
if (set(key, threadId, "NX", "EX", 30)) {try {// 执行业务逻辑} finally {if (threadId.equals(get(key))) {del(key); // 释放锁}}
}

同时,这种方式也能够将分布式锁改造成可重入的分布式锁,在获取锁的时候判断一下是否是当前线程获取的锁,锁标识自增便可。

(二)原子性保证

在分布式锁中,SETNXEXPIRE 操作不是原子性的,可能导致死锁等并发问题。为了解决这个问题,我们可以使用 Lua 脚本来确保这些操作的原子性。

问题场景

  • 非原子性操作SETNX 成功后,如果 EXPIRE 操作未执行(例如由于服务器故障或网络问题),锁可能没有超时时间,从而导致死锁。
  • 误删锁:线程在判断标识符一致后,如果因阻塞导致锁过期,其他线程可能获取锁,而原线程仍然执行解锁操作,误删了新的锁。

解决方案:使用 Lua 脚本

Lua 脚本可以将多个 Redis 操作封装为一个原子操作,确保获取锁、设置过期时间、判断标识符和删除锁的操作按预期执行。Lua 脚本示例:

设置锁并设置过期时间(原子操作)
if (redis.call('setnx', KEYS[1], ARGV[1]) < 1) then   return 0;  -- 获取锁失败
end;
redis.call('expire', KEYS[1], tonumber(ARGV[2]));  -- 设置过期时间
return 1;  -- 获取锁成功
释放锁(原子操作)
if (redis.call('get', KEYS[1]) == ARGV[1]) then    return redis.call('del', KEYS[1]);  -- 释放锁
end; 
return 0;  -- 当前线程不是锁持有者

Java 调用 Lua 脚本

通过 Java 调用 eval 方法执行上述 Lua 脚本,确保 Redis 操作的原子性:

// 获取锁
Object result = jedis.eval(luaScriptForSet, 
Collections.singletonList(key), 
Arrays.asList(threadId, "30"));// 释放锁
Object result = jedis.eval(luaScriptForDel, 
Collections.singletonList(key), 
Collections.singletonList(threadId));

使用 Lua 脚本可以确保分布式锁的关键操作在 Redis 中实现原子性,避免了由于非原子性操作导致的死锁和误删锁等并发问题,从而提升系统的可靠性。

(三)超时自动解锁

超时自动解锁的问题虽然在某些场景下不可避免,但可以通过一些机制来缓解,比如延长 TTL 或者增加锁续期机制。

问题描述

在分布式锁的使用中,如果线程的执行时间超过了锁的 TTL(过期时间),锁会自动释放,这时其他线程可能会获取到锁,而原线程还未执行完毕,可能导致数据不一致或业务逻辑错误。

传统解决方案

延长 TTL:可以通过将 TTL 设置得足够长来避免这种情况,但这可能导致其他线程长时间等待锁,特别是在发生意外宕机时,下一个线程将会阻塞很长时间,这并不优雅。

改进方案:锁续期机制

为了更优雅地解决这个问题,可以给获取锁的线程单独开一个守护线程,检测当前线程的运行情况。当发现锁的 TTL 即将到期时,守护线程可以自动为该锁续期,从而保证业务逻辑能够顺利执行完毕。

具体实现步骤

  1. 启动守护线程:在获取锁后,启动一个守护线程定期检查锁的 TTL 是否即将过期。

  2. 续期机制:守护线程在锁即将过期时,自动向 Redis 发送 PEXPIRE 命令,延长锁的有效时间。

  3. 停止守护线程:在业务逻辑执行完毕并释放锁后,守护线程应该被及时停止,避免不必要的资源消耗。

Java 实现示例

// 获取锁并启动守护线程
if (set(key, threadId, "NX", "EX", 30)) {// 启动守护线程ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);scheduler.scheduleAtFixedRate(() -> {if (threadId.equals(get(key))) {expire(key, 30);  // 续期锁}}, 25, 25, TimeUnit.SECONDS);try {// 执行业务逻辑} finally {if (threadId.equals(get(key))) {del(key);  // 释放锁}scheduler.shutdown();  // 停止守护线程}
}

注意事项

  • 续期间隔:设置合理的续期间隔,通常可以设置为略小于 TTL,例如在 TTL 为 30 秒时,每 25 秒续期一次。

  • 可靠性:确保守护线程可靠地执行续期操作,避免续期失败导致锁过期。

通过为分布式锁增加一个守护线程来实现锁续期机制,可以避免由于线程阻塞导致的超时自动解锁问题,从而确保业务逻辑能够完整执行。这种方法比简单延长 TTL 更加优雅和灵活。

四、总结

 分布式锁确保在分布式环境中,某个共享资源在同一时刻只能被一个节点或线程访问,避免了传统本地锁在多节点环境中的同步问题。分布式锁通常由外部组件(如 Redis、Zookeeper)实现,这些组件提供了高可用的锁服务,确保锁在节点故障或网络分区情况下的可靠性。

常见实现方式:

  • 简单实现利用 Redis 的 SET 命令和 NX(不存在则插入)参数,可以快速实现分布式锁。然而,这种实现存在误删和超时处理等问题。
  • 健壮实现通过使用唯一标识符和 Lua 脚本来确保操作的原子性,可以解决误删和超时问题,提高分布式锁的可靠性。Lua 脚本将锁的设置和释放操作封装为原子操作,避免了非原子性操作带来的并发问题。
  • 看门狗机制通过监控锁的有效期,并在锁即将过期时自动续期,确保业务逻辑在锁的有效期内顺利执行,避免了因锁超时导致的数据不一致问题。

关键特点:

  • 互斥性可重入性锁超时高性能与高可用性阻塞与非阻塞性可扩展性是分布式锁需要满足的基本特点。这些特点确保了锁的有效性和系统的稳定性。

通过选择合适的分布式锁实现方式,可以有效提升系统的可靠性和一致性,确保业务逻辑的正确执行。在实际应用中,需要根据具体场景选择合适的实现方式,并进行适当的优化和调整,以应对分布式环境下的复杂挑战。

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

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

相关文章

IP不纯净的后果及解决方案

在如今的互联网时代&#xff0c;知识产权&#xff08;IP&#xff09;保护显得尤为重要。随着社交媒体的迅猛发展&#xff0c;尤其是以TikTok为代表的短视频平台的崛起&#xff0c;内容创作者面临着越来越多的挑战&#xff0c;其中之一便是IP不纯净的问题。本文将探讨做IP不纯净…

12、论文阅读:利用生成对抗网络实现无监督深度图像增强

Towards Unsupervised Deep Image Enhancement With Generative Adversarial Network 摘要介绍相关工作传统图像增强基于学习的图像增强 论文中提出的方法动机和目标网络架构损失函数1) 质量损失2) 保真损失3&#xff09;身份损失4&#xff09;Total Loss 实验数据集实现细节评…

redis 使用

打开redis 前台启动 同路径下打开redis-server 出现窗口&#xff0c;即启动成功 此时关闭窗口&#xff0c;redis关闭&#xff1b; 不管有没有使用密码&#xff0c;或者使用了什么密码&#xff0c;都能连上 如果使用下文提到的redis cli增加密码&#xff0c;就只能使用你设置的…

【机器学习】任务七:聚类算法 (K-means 算法、层次聚类、密度聚类对鸢尾花(Iris)数据进行聚类)

目录 1.基础知识 1.1 K-Means 算法 1.2 层次聚类&#xff08;Hierarchical Clustering&#xff09; 1.3 密度聚类&#xff08;DBSCAN&#xff09; 1.4 距离和相似度度量方法 1.5 总结&#xff1a; 2.K-means 算法对鸢尾花&#xff08;Iris&#xff09;数据进行聚类 2.1…

手写 | 设计模式

这里写目录标题 观察者 vs 发布订阅 观察者 vs 发布订阅 参考代码 观察者模式&#xff0c;一对多&#xff0c;两个角色&#xff1a;观察者observer和被观察者/主题Subject。 Subject维护一个数组&#xff0c;记录有哪些Observer&#xff1b;通过调自身的noticefy方法&#xf…

Linux - 环境变量 | 命令行参数 | 进程基础

文章目录 一、了解冯诺依曼体系结构1、概念2、对数据层面3、实例二、操作系统1、概念2、设计OS的目的3、定位4、操作系统怎么管理&#xff1f; 三、进程1、概念2、怎么管理进程3、描述进程-PCB4、描述进程怎么运行&#xff08;粗略&#xff09;5、进程属性6、创建子进程7、创建…

mac 安装最新版nginx

1. clone最新版本源代码&#xff1a; git clone https://github.com/nginx/nginx.git 2. 下载PCRE 没有PCRE那我们就下&#xff0c;下载地址&#xff1a;https://sourceforge.net/projects/pcre/files/pcre/&#xff0c;笔者下载的pcre-8.45.zip&#xff0c;下载之后解压到ngi…

实战RAG第一天——llama_index向量索引,查询引擎,搜索知识库问答,全部代码,保姆级教学

一、llama_index简介 llama_index(以前称为 GPT Index)是一个用于构建、查询、索引大型文档和数据集的开源框架。它的核心功能是帮助开发者将大语言模型(LLM)与自己的数据集无缝集成,从而进行知识库的构建、查询等任务。llama_index 使用 Python 编写,并结合了多种大语言…

Qt 窗口悬停事件和鼠标跟踪

悬停事件 //这行代码用于启用窗口部件&#xff08;widget&#xff09;的悬停事件跟踪功能。 setAttribute(Qt::WA_Hover, true); 作用 setAttribute(Qt::WA_Hover, true); 这行代码的作用是设置窗口部件的一个属性标志&#xff0c;使其能够接收悬停事件&#xff08;hover eve…

解码生成式AI产品全攻略:看这一篇就够了

解码生成式AI产品全攻略 随着人工智能技术的不断发展&#xff0c;生成式AI产品逐渐成为人们生活中不可或缺的一部分。生成式AI产品能够根据输入的指令或数据生成新的、独一无二的内容&#xff0c;具有广泛的应用场景&#xff0c;如音乐、图像、视频、文本等。但是&#xff0c;…

图片dpi怎么调整?这几个方法让你轻松搞定!

在数字化时代&#xff0c;图片的质量直接关系到视觉体验的效果。无论是社交媒体分享、网页展示还是专业打印&#xff0c;调整图片的DPI&#xff08;Dots Per Inch&#xff0c;每英寸点数&#xff09;都是至关重要的一步。今天&#xff0c;我们就来聊聊如何轻松调整图片的DPI&am…

2024年10月21日第二部分

规划第一次演唱会,微积分 入档足够优秀都可以 【【马斯克演讲】一场会让你哭泣的励志演讲&#xff01;】 https://www.bilibili.com/video/BV1az421z7ei/?share_sourcecopy_web&vd_source476fcb3b552dae37b7e82015a682a972 ‍你可能不知道马斯克有多牛。两万亿刀&#…

博客|基于springBoot的精简博客系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 &#xff08;1&#xff09;个人中心 &#xff08;2&#xff09;管理员管理 &#xff08;3&#xff09;用户管理 &#xff08;4&#xff09;博客信…

骨传导耳机哪个品牌好用?骨传导耳机排行榜前五名冠军机型精选!

随着科技的不断进步&#xff0c;我们的生活方式也在不断演变。在数码耳机领域&#xff0c;骨传导耳机的兴起无疑是非常有创意的。它凭借独特的传导方式&#xff0c;能够让用户在享受高质量音效的同时&#xff0c;保持对周围环境的感知&#xff0c;可以在一些特定场景下带来更好…

软件压力测试如何进行?深圳软件测试机构分享

软件压力测试是每个重要软件测试工作的一部分&#xff0c;是一种基本的软件质量保证行为。压力测试不是在常规条件下运行手动或自动测试&#xff0c;而是在计算机数量较少或系统资源匮乏的条件下运行测试。通常要进行软件压力测试的资源包括内部内存、CPU 可用性、磁盘空间和网…

SpringSecurity源码分析以及如何解决前后端分离出现的跨域问题

解决Security前后端分离出现的跨域问题 一. Security源码分析 首先在看源码之前我们先来看这张图 , 这张图展示了Security执行的全部流程 从上图可知Security执行的入口是UsernamePasswordAuthenticationFilter这个抽象类 , 那我们就先从该类进行分析 1. UsernamePasswordAu…

多ip访问网站

多ip访问网站 安装nginx [rootlocalhost ~]# dnf install nginx -y关闭防火墙&#xff0c;sulinux [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0添加多IP&#xff08;这里使用图形化模式&#xff09; [rootlocalhost ~]# nmtui [rootloca…

VSCode创建插件HelloWorld找不到指令解决办法

按照网上的教程执行yo code并且生成成功 但是F5打开调试新窗口后&#xff0c;ctrl shift P&#xff0c;输入helloworld并没有指令提示 原因&#xff1a;当前电脑安装的VSCode版本过低&#xff0c;不支持当前插件的使用&#xff08;因为自动生成的插件总是默认使用最新版VSC…

Redis优劣势分析

Redis优劣势分析 Redis是一个开源的、内存中的数据结构存储&#xff0c;用于数据库、缓存和消息代理。以下是Redis的优劣势分析&#xff1a; 优点&#xff1a; 高性能&#xff1a; Redis是内存数据库&#xff0c;读写速度非常快&#xff0c;适合需要高性能的场景。丰富的数据…

EESE 2024国际学术会议二轮征稿启动,优质稿件持续收录中

第三届环境工程与与可持续能源国际会议&#xff08;EESE 2024&#xff09;将于2024年12月在长沙召开&#xff0c;邀请多位顶尖学者分享研究成果。会议聚焦环境工程与可持续能源&#xff0c;涵盖多个领域&#xff0c;旨在搭建交流平台&#xff0c;促进产学研合作。投稿、参会形式…