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

目录

一、简单了解分布式锁

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

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

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

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

二、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,一经查实,立即删除!

相关文章

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

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

【机器学习】任务七:聚类算法 (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…

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

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

图片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…

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

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

Java项目-基于springboot框架的智慧外卖系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

springboot+uinapp基于Android的固定资产借用管理平台

文章目录 前言项目介绍技术介绍功能介绍核心代码数据库参考 系统效果图论文效果图 前言 文章底部名片&#xff0c;获取项目的完整演示视频&#xff0c;免费解答技术疑问 项目介绍 固定资产借用管理平台设计的目的是为用户提供使用申请、故障报修、设备归还、意见反馈等管理方…

Python+Flask接口判断身份证省份、生日、性别、有效性验证+docker部署+Nginx代理运行

这里写目录标题 一、接口样式二、部署流程2.1 镜像打包2.1.1 准备工作2.1.2 build打包2.1.3 dokcer部署运行2.1.4 Nginx代理 三、代码及文件3.1 index.py3.2 areaCodes.json3.3 Dockerfile 一、接口样式 https://blog.henryplus.cn/idcardApi/idCard/query?idcard{idcard} 如果…

【MySQL】内外连接

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解MySQL的内外连接。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1a;带你玩…

真别跟风,CISP认证只对这些人有用

CISP认证&#xff0c;作为信息安全领域的重要专业资格认证&#xff0c;受到了业界的广泛认可。 它不仅证明了持证者在信息安全方面的专业能力&#xff0c;也是许多企业和组织在招聘信息安全岗位时的重要参考标准。然而&#xff0c;CISP认证并非适合所有人。 虽然安全方向热度很…

ArkUI自定义Scroll滑动 指示器

最近项目中有个需求&#xff0c;关于滑动组件 Scroll 底部指示器跟随手势滑动等比例展示的效果&#xff0c;下图展示了要实现的效果。 自定义指示器组件 这里其实不是一个进度条&#xff0c;所以需要我们需要自定义绘制该组件&#xff0c;在鸿蒙中绘制组件单独使用&#xff0c…

九盾叉车高位显示器:重塑叉车视界,引领高位精准

在繁忙的物流与仓储中&#xff0c;叉车不仅是力量与效率的化身&#xff0c;更是精准与安全的守护者。九盾安防&#xff0c;以科技之名&#xff0c;打造叉车高位显示器&#xff0c;彻底革新了货叉升降的盲区挑战&#xff0c;为物流、仓储及码头等领域带来了前所未有的作业体验。…

arm架构ceph pacific部署

背景 合作伙伴实验室的华为私有云原来使用单点的nfs做为存储设备&#xff0c;现有两方面考量&#xff0c;业务需要使用oss了&#xff0c;k8s集群及其他机器也需要一套可扩展的分布式文件系统 部署ceph 初始机器配置规划 IP配置主机名Role10.17.3.144c8g1T数据盘ceph-node01…