SpringBoot中基于MongoDB的findAndModify原子操作实现分布式锁原理详解

❃博主首页 : 「码到三十五」 ,同名公众号 :「码到三十五」,wx号 : 「liwu0213」
☠博主专栏 : <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关>
♝博主的话 : 搬的每块砖,皆为峰峦之基;公众号搜索「码到三十五」关注这个爱发技术干货的coder,一起筑基

分布式系统中,分布式锁是一种常用的同步机制,通过MongoDB提供的findAndModify原子操作,可以有效地实现分布式锁的功能。

文章目录

    • 一、MongoDB的锁机制
    • 二、分布式锁的需求
    • 三、基于MongoDB的分布式锁实现原理
      • 1. 锁集合的创建
      • 2. 尝试获取锁
      • 3. 锁的重入和超时
      • 4. 释放锁
      • MongoDB findAndModify原理
    • 四、Spring Boot中简单实现
        • 1. 定义锁文档
        • 2. 实现锁服务
        • 3. 使用锁服务
    • 五、注意

一、MongoDB的锁机制

MongoDB的锁机制主要用于保护数据的一致性和正确性。当多个客户端同时对同一文档进行操作时,MongoDB通过锁机制来确保每个操作的顺序和结果都是正确的。锁机制通过对文档进行加锁来实现,包括读锁和写锁。读锁允许多个客户端同时读取文档,但写锁则是互斥的,即同一时间只能有一个客户端持有写锁。

findAndModify是MongoDB提供的一个非常强大的命令,它允许你同时执行查询和更新操作,并且这个操作是原子的。这意味着在findAndModify执行期间,没有其他客户端可以修改被查询的文档,直到该命令完成。这个特性使其成为实现分布式锁的理想选择。

二、分布式锁的需求

分布式系统中,分布式锁需要满足以下几个基本要求:

  1. 互斥性:在任意时刻,只有一个客户端能够持有锁。
  2. 不会死锁:客户端在持有锁期间如果崩溃或断开连接,锁必须能够被释放,以防止死锁。
  3. 容错性:在分布式环境下,部分节点或网络故障不应影响锁的正常工作。
  4. 高性能:锁的获取和释放操作应该尽可能快,以减少对系统性能的影响。

三、基于MongoDB的分布式锁实现原理

1. 锁集合的创建

首先,在MongoDB中创建一个专门的集合(如locks)来存储锁信息。每个锁由一个文档表示,文档中包含了锁的关键信息,如锁名(lockName)、持有者(holder)、锁定时间(lockedAt)、过期时间(expiresAt)等。

2. 尝试获取锁

当客户端需要获取锁时,它执行以下步骤:

  • 使用findAndModify命令查询locks集合中的对应锁文档。
  • 查询条件包括锁名和当前持有者为空(表示锁未被占用)且当前时间小于过期时间(如果存在过期时间字段)。
  • 更新操作设置持有者为当前客户端的标识,设置锁定时间,并可选地设置过期时间。
  • 如果findAndModify命令成功更新了文档,则表示客户端成功获取了锁;如果更新失败(因为其他客户端已经设置了持有者或已过期时间已过),则表示锁已被占用或已过期。

3. 锁的重入和超时

  • 重入性:可以通过在文档中增加一个重入计数器来实现锁的重入性。当客户端尝试重新获取已被自己持有的锁时,重入计数器增加。
  • 超时机制:设置过期时间(expiresAt)来防止客户端在持有锁期间崩溃而无法释放锁。当过期时间到达时,其他客户端可以清除该锁(通过检查并更新expiresAtholder字段)。

4. 释放锁

当客户端完成操作后,它执行以下步骤来释放锁:

  • 再次使用findAndModify命令查询并更新locks集合中的对应锁文档。
  • 更新操作将文档的持有者设置为空(或某个特定的释放标识),并可能更新锁定时间或重入计数器(如果实现了重入性)。
  • 如果需要,还可以更新过期时间字段以清除过期的锁。

分布式系统中,实现锁机制是一项关键任务,用于控制对共享资源的访问,防止数据不一致。MongoDB的findAndModify命令是一种强大的原子操作,可以用于实现简单的分布式锁。下面详细介绍其原理,并在Spring Boot环境中给出一个实现案例。

MongoDB findAndModify原理

findAndModify是MongoDB中的一个命令,它用于查找并更新一个文档,这个操作是原子的,意味着在查找和更新文档期间,不会有其他操作可以修改这个文档。利用这个特性,我们可以创建一个简单的分布式锁:

  1. 锁定机制

    • 在数据库中创建一个集合(例如locks),每个锁由一个文档表示。
    • 每个文档包含锁的关键信息,如锁名(lockName)、持有者(holder)、锁定时间(lockedAt)等。
    • 当需要锁定某个资源时,使用findAndModify尝试更新集合中的一个文档,设置holderlockedAt。如果更新成功,则表示获得了锁;如果失败(例如,因为其他客户端已经设置了holder),则表示锁已被占用。
  2. 释放机制

    • 持有锁的客户端在完成操作后,需要释放锁。这通常通过另一个findAndModify操作来完成,将文档的holder设置为null或某个特定的释放标识。

四、Spring Boot中简单实现

Spring Boot中可以使用Spring Data MongoDB与MongoDB的交互。

1. 定义锁文档
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;@Document(collection = "locks")
public class Lock {@Idprivate String id;private String lockName;private String holder;private Date lockedAt;// Getters and Setters
}
2. 实现锁服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;@Service
public class LockService {@Autowiredprivate MongoTemplate mongoTemplate;public boolean tryLock(String lockName, String clientId) {Query query = new Query();query.addCriteria(Criteria.where("lockName").is(lockName).and("holder").isNull());Update update = new Update();update.set("holder", clientId);update.set("lockedAt", new Date());FindAndModifyOptions options = new FindAndModifyOptions();options.returnNew(true);Lock lock = mongoTemplate.findAndModify(query, update, options, Lock.class);return lock != null && lock.getHolder().equals(clientId);}public void releaseLock(String lockName, String clientId) {Query query = new Query();query.addCriteria(Criteria.where("lockName").is(lockName).and("holder").is(clientId));Update update = new Update();update.set("holder", null);mongoTemplate.findAndModify(query, update, Lock.class);}
}
3. 使用锁服务

在服务层或控制器中,注入LockService并调用tryLockreleaseLock方法来控制对共享资源的访问。

五、注意

  • 锁的粒度:根据实际需求选择合适的锁粒度,避免过细或过粗的锁粒度导致的性能问题或资源竞争。
  • 锁的过期时间:合理设置锁的过期时间,以确保在客户端崩溃或其他异常情况下能够释放锁。
  • 网络延迟和分区:在分布式系统中,网络延迟和分区问题可能会导致findAndModify操作的延迟或失败。需要考虑这些因素对锁的性能和可靠性的影响。

关注公众号[码到三十五]获取更多技术干货 !


http://www.ppmy.cn/news/1519884.html

相关文章

网络是怎样连接的

网络是怎样连接的 HTTPDNS数据通信协议栈——TCP/IPRJ-45接口信号衰减噪声干扰双绞线MDI接口集线器交换机路由器接入网ADSL接入网FTTH接入网用户认证和配置下发DHCPPOPWeb服务器的部署地点防火墙 HTTP HTTP发展史&#xff1a; 1991 – HTTP/0.91996 – HTTP/1.01997 – HTTP/…

人工智能领域面试基础问题整理(二):什么是人工智能?

当你说你的专业是“人工智能”时&#xff0c;面试官问你&#xff1a;“假如我是一个不懂AI的人&#xff0c;你能和我说说&#xff0c;什么是AI吗&#xff1f;”你会怎么回答。 我们可以从以下几个方面入手&#xff1a; 1、人工智能的定义 人工智能&#xff08;Artificial Inte…

Android Studio gradle下载太慢了!怎么办?(已解决)

Android Studio&#xff01;你到底干了什么&#xff1f;&#xff01; 不能高速下载gradle&#xff0c;我等如何进行app编程&#xff1f;&#xff01; 很简单&#xff0c;我修改gradle地址不就是了。 找到gradle-wrapper.properties文件 修改其中distributionUrl的地址。 将 ht…

[Leetcode 51][Hard]-n皇后问题-回溯

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 这种可以算是组合问题的变种&#xff0c;在回溯函数中我们要保存当前已放置皇后的所有位置&#xff0c;同时递归调用时要进行寻找下一个皇后的放置位置。那么我们可以逐行遍历棋盘并作为递归调…

如何完美实现 Go 服务的平滑升级

Go 服务作为常驻进程,如何进行服务升级呢?你可能会觉得这还不简单,先将现有服务停止,再启动新的服务不就可以了。可是将现有服务停止时,如果它还在处理请求,那么这些请求该如何处理?另外,在现有服务已经退出但是新服务还没有启动期间,新的请求到达了又该如何处理? Go…

Logistic分类算法原理及Python实践

一、Logistic分类算法原理 Logistic分类算法&#xff0c;也称为逻辑回归&#xff08;Logistic Regression&#xff09;&#xff0c;是机器学习中的一种经典分类算法&#xff0c;主要用于解决二分类问题。其原理基于线性回归和逻辑函数&#xff08;Sigmoid函数&#xff09;的组…

3.4 数据传送指令

&#x1f393; 微机原理考点专栏&#xff08;通篇免费&#xff09; 欢迎来到我的微机原理专栏&#xff01;我将帮助你在最短时间内掌握微机原理的核心内容&#xff0c;为你的考研或期末考试保驾护航。 为什么选择我的视频&#xff1f; 全程考点讲解&#xff1a;每一节视频都…

使用JavaScript读取手机联系人列表:从理论到实践

更多内容前往个人网站&#xff1a;孔乙己大叔 在现代Web开发中&#xff0c;随着技术的不断进步&#xff0c;以前看似不可能的任务现在变得可行。例如&#xff0c;使用JavaScript读取手机联系人列表这一功能&#xff0c;在几年前几乎是不可想象的&#xff0c;但现在随着Web API的…

MyBatis之XML配置文件(一)

Mbatis是一个ORM框架&#xff0c;可以用XML配置文件或注解映射SQL语句&#xff0c;映射文件是MyBatis框架的核心&#xff0c;本文主要讲述XML 映射文件的结构和使用方法。 一、SQL映射文件 SQL映射文件就是mapperxml配置文件&#xff0c;主要实现SQL语句的配置和映射&#xf…

pdf.js如何支持base64的查看

1.pdf.js 作为一个查看在线阅读pdf的软件&#xff0c;常常被运用到前端开发中&#xff0c;但是如何让pdf支持base64的查看&#xff0c;这边就需要去进行修改一些代码了 这边我们就进行开发修改 首先去下载 https://mozilla.github.io/pdf.js/ 当然了&#xff0c;低版本的可以…

Kubernetes 上安装 Jenkins

安装 Helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash添加 Jenkins Helm 仓库 首先添加 Jenkins Helm 仓库 helm repo add jenkins https://charts.jenkins.io helm repo update安装 Jenkins 使用 Helm 安装 Jenkins 的最新版本&…

基于分布式计算的电商系统设计与实现【系统设计、模型预测、大屏设计、海量数据、Hadoop集群】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目展示项目介绍 目录摘要Abstract1 引言1.1 研究背景1.2 国内外研究现状1.3 研究目的1.4 研究意义 2 关键技术理论介绍2.1 Hadoop相关组件介绍2.2 分布式集群介绍2.3 Pyecharts介绍2.4 Fl…

Android音视频开发,需要学些什么?

如果你想学习 Android 音视频开发&#xff0c;以下是一些需要学习的内容&#xff1a; 一、基础知识 Java 或 Kotlin 编程语言&#xff1a;Android 开发主要使用这两种语言&#xff0c;确保你对其中一种有扎实的掌握&#xff0c;包括语法、面向对象编程概念、数据结构和算法等…

docker-compose 启动的harbor页面能登录,但是不能推送镜像

问题现象&#xff1a; docker-compose 安装的harbor&#xff0c;页面可以正常打开&#xff0c;但是不能推送镜像。 报错信息提示&#xff1a;connect: connection refused 故障原因&#xff1a; harbor.yml 中的external_url参数写错。这个是提供外部访问。页面请求地址和…

Java 面向对象编程的四个基本原则(封装、继承、多态和抽象),并给出一个简单的例子说明如何在 Java 中应用这些原则?

面向对象编程&#xff08;OOP&#xff09;是一种编程范式&#xff0c;它使用“对象”来设计软件。在 Java 中&#xff0c;面向对象编程的四个基本原则是封装、继承、多态和抽象。每个原则都有其特定的目标&#xff0c;帮助开发者构建更加模块化、可维护和可扩展的代码。 封装 …

ImmersiveTranslate:一键中英对照,Google Chrome上不可或缺的翻译利器

ImmersiveTranslate&#xff1a;一键中英对照&#xff0c;Google Chrome上不可或缺的翻译利器 基本介绍 ImmersiveTranslate 是一款为Google Chrome用户设计的翻译插件&#xff0c;旨在帮助用户轻松实现中英对照翻译。这款插件不仅适合普通用户&#xff0c;同时也为开发者提供…

CSS3动画——飞行的小精灵

CSS3动画——飞行的小精灵 今天的这段代码通过多层结构、渐变色、圆角、多种动画效果以及细节处理&#xff0c;成功地创造了一个充满活力和趣味性的飞行小精灵动画效果。 效果如下&#xff1a; 飞行的小精灵 源代码如下&#xff1a; <!DOCTYPE html> <html lang&quo…

呵,老板不过如此,SQL还是得看我

2018年7月&#xff0c;大三暑假进行时&#xff0c;时间过得飞快&#xff0c;我到这边实习都已经一个月了。 我在没工作之前&#xff0c;我老是觉得生产项目的代码跟我平时自学练的会有很大的区别。 以为生产项目代码啥的都会规范很多&#xff0c;比如在接口上会做很多安全性的…

@Tanstack/vue-query 的使用介绍

Tanstack/vue-query 的使用介绍 前言 在今年的vue conf 会议上&#xff0c;提到了vue-query这个库&#xff0c;这里对它的基本使用做一个介绍。 会议资料地址&#xff1a; https://vueconf.cn/ Tanstack-query的前身是react-query&#xff0c;是一个本地的服务端状态管理的库…

【bug】可图文生图模型 KolorsPipeline IndexError: list index out of range

【bug】可图文生图模型 KolorsPipeline IndexError: list index out of range 环境 linux diffusers 0.30.0问题详情 报错详情 from diffusers import KolorsPipelineTraceback (most recent call last):File "Kolors/demo.py", line 6, in <module>pi…