分布式锁的详细解释

什么是分布式锁

   分布式锁是一种用于协调分布式系统中多个进程或线程之间访问共享资源的机制。在分布式系统中,多个进程或线程可能同时竞争访问某个共享资源,为了避免并发访问导致的数据不一致或冲突,需要使用分布式锁来保证资源的独占性。

分布式锁使用互斥的方式来控制对共享资源的访问,通常通过加锁和释放锁的操作来实现。加锁操作会阻塞其他进程或线程的访问请求,直到当前进程或线程释放锁。这样可以确保在任意时刻只有一个进程或线程可以访问共享资源,从而避免并发访问导致的数据不一致或冲突。

分布式锁可以通过多种方式来实现,包括基于数据库、基于缓存、基于分布式协议等。常见的实现方式包括使用分布式缓存如Redis实现的锁、使用数据库的行级锁、使用ZooKeeper或etcd等分布式协调服务来实现的锁等。这些实现方式都具有一定的优缺点,选择合适的实现方式需要考虑系统的需求和限制。

分布式锁的解决方案      

1、数据库解决方案思路:
  a.数据库建一张表,字段方法名并且作为唯一性,当一个方法执行时插入,则相当于获得锁,其他线程将无法访问,方法执行完则释放锁。
但是上面这种存在问题:
1、数据库单点,出现故障则将导致系统不可用。
2、没有失效时间,一旦操作方法异常,导致一直没有解锁,也将导致其他不可用用。
   b.使用select * from user u where username = '' for update 
来对记录加上排他锁。操作完成后使用commit命令释放锁。


2、基于缓存实现:  通常有Memcached、Redis实现等,以下以Redis实现分布式锁为例
思路:主要用到的redis函数是setnx(),这个应该是实现分布式锁最主要的函数。首先是将某一任务标识名(这里用Lock:order作为标识名的例子)作为键存到redis里,并为其设个过期时间,如果是还有Lock:order请求过来,先是通过setnx()看看是否能将Lock:order插入到redis里,可以的话就返回true,不可以就返回false

3.Zookeeper分布式锁
大致思路:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 
判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 
当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上,所以性能上不如基于缓存实现。

分布式锁的应用场景

  1. 避免资源竞争:在分布式系统中,多个进程或节点可能需要同时访问共享资源。使用分布式锁可以确保同一时间只有一个进程或节点能够访问共享资源,避免资源竞争和数据不一致。

  2. 避免重复操作:在某些场景下,比如定时任务或消息队列处理,可能会有多个进程或节点同时执行某个操作。使用分布式锁可以保证只有一个进程或节点能够执行该操作,避免重复执行。

  3. 保证数据一致性:在分布式系统中,多个进程或节点可能需要同时修改某个共享数据。使用分布式锁可以确保同一时间只有一个进程或节点能够修改共享数据,避免数据不一致。

  4. 避免死锁:在分布式系统中,可能会出现多个进程或节点之间的循环依赖锁的情况,导致死锁。使用分布式锁可以避免这种情况的发生。

  5. 控制并发访问:在某些场景下,需要限制同时访问某个资源的并发数。使用分布式锁可以实现对并发访问的控制,保证系统的稳定性和性能。

实例

import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class DistributedLock implements Watcher {private ZooKeeper zooKeeper;private String lockPath;private String currentPath;private String waitPath;private CountDownLatch countDownLatch;public DistributedLock(String address, String lockPath) {try {this.zooKeeper = new ZooKeeper(address, 5000, this);this.lockPath = lockPath;if (zooKeeper.exists(lockPath, false) == null) {zooKeeper.create(lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}} catch (IOException | InterruptedException | KeeperException e) {e.printStackTrace();}}public void lock() {try {currentPath = zooKeeper.create(lockPath + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);List<String> children = zooKeeper.getChildren(lockPath, false);Collections.sort(children);if (currentPath.equals(lockPath + "/" + children.get(0))) {return;}String currentNode = currentPath.substring(currentPath.lastIndexOf("/") + 1);waitPath = lockPath + "/" + children.get(Collections.binarySearch(children, currentNode) - 1);countDownLatch = new CountDownLatch(1);zooKeeper.exists(waitPath, true);countDownLatch.await();} catch (InterruptedException | KeeperException e) {e.printStackTrace();}}public void unlock() {try {zooKeeper.delete(currentPath, -1);} catch (InterruptedException | KeeperException e) {e.printStackTrace();}}@Overridepublic void process(WatchedEvent event) {if (countDownLatch != null && event.getPath().equals(waitPath) && event.getType() == Event.EventType.NodeDeleted) {countDownLatch.countDown();}}
}

       在上述示例中,首先创建了一个ZooKeeper实例,连接到ZooKeeper服务。然后创建了一个指定路径的永久节点,用于保存分布式锁。在lock方法中,首先创建一个临时有序节点,然后获取锁路径下的所有子节点,并对子节点进行排序。如果当前节点是所有子节点中最小的节点,则表示获取到了锁;否则,使用二分查找找到当前节点的前一个节点,并监听其删除事件,在该节点被删除时释放锁,调用countDownLatch.countDown()方法唤醒等待的线程。在unlock方法中,删除当前节点即可释放锁。

使用示例:

public class Main {public static void main(String[] args) {DistributedLock lock = new DistributedLock("localhost:2181", "/distributed-lock");try {lock.lock();// 执行需要同步的代码块} finally {lock.unlock();}}
}

     在上述示例中,创建了一个DistributedLock实例,然后在需要同步访问的代码块前调用lock方法获取锁,在代码块执行完毕后调用unlock方法释放锁。这样就可以保证同一时刻只有一个线程能够执行该代码块,实现了分布式环境下的同步访问。

总结

  1. 基于数据库的分布式锁:可以通过在数据库中创建一张锁表,对需要加锁的资源在该表中插入一条记录,当其他进程或线程尝试获取锁时,通过数据库事务机制来保证原子性和互斥性;

  2. 基于缓存的分布式锁:使用分布式缓存,如Redis或Memcache,将锁作为一个缓存键的值进行存储。其他进程或线程在获取锁时,先尝试设置该缓存键,如果设置成功,则表示获取到锁;

  3. 基于ZooKeeper的分布式锁:使用ZooKeeper这样的分布式协调服务,通过创建临时有序节点来实现锁。其他进程或线程在获取锁时,需要通过比较自身创建的节点是否是最小的节点来判断是否获取到锁;

分布式锁需要考虑以下几个问题:

  1. 死锁问题:由于网络延迟或其他异常情况,可能会导致锁没有正确释放,从而造成死锁。可以通过设置锁的超时时间或引入心跳机制来解决;

  2. 锁竞争问题:多个进程或线程同时竞争同一个锁时,可能会导致频繁的锁竞争,从而影响性能。可以通过引入重试机制、公平锁等来减少锁竞争;

  3. 容错问题:分布式系统中,可能存在节点故障、网络分区等情况,需要确保分布式锁在这些异常情况下仍能正常工作。可以通过引入选举机制、多副本存储等来提高容错性。

  

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

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

相关文章

5.1.1、【AI技术新纪元:Spring AI解码】Openai chat

OpenAI 聊天 Spring AI 支持由 OpenAI 开发的人工智能语言模型 ChatGPT。ChatGPT 在激发人们对 AI 驱动文本生成的兴趣方面发挥了重要作用,这归功于它创建的行业领先的文本生成模型和嵌入技术。 先决条件 您需要与 OpenAI 创建一个 API 来访问 ChatGPT 模型。在 OpenAI 注册页…

第十三届蓝桥杯省赛CC++ 研究生组

蓝桥杯2022年第十三届省赛真题-裁纸刀 蓝桥杯2022年第十三届省赛真题-灭鼠先锋 蓝桥杯2022年第十三届省赛真题-质因数个数 求个数&#xff0c;则只需要计数即可。求啥算啥&#xff0c;尽量不要搞多余操作 蓝桥杯2022年第十三届省赛真题-选数异或 蓝桥杯2022年第十三届省赛真题…

深入理解并优化Android中的文件描述符(FD)

一、文件描述符&#xff08;FD&#xff09;概述 文件描述符&#xff08;File Descriptor&#xff0c;简称FD&#xff09;是Unix和类Unix操作系统&#xff08;包括Android&#xff09;中的一个关键概念。它是一个非负整数&#xff0c;用于标识操作系统分配的文件或其他输入/输出…

内网渗透学习-环境搭建

1、环境搭建测试 虚拟机网络环境配置&#xff0c;模拟外网和内网 主机操作系统网络内网ip外网ip物理主机window10vmnet8192.168.70.1攻击机kali Linuxvmnet8192.168.70.134域控主机win server 2008 r2vmnet0192.168.52.138域成员主机win server 2k3vmnet0192.168.52.141服务器…

HarmonyOS NEXT应用开发之swiper指示器导航点位于swiper下方

介绍 本示例介绍通过分割swiper区域&#xff0c;实现指示器导航点位于swiper下方的效果。 效果预览图 使用说明 加载完成后swiper指示器导航点&#xff0c;位于显示内容下方。 实现思路 将swiper区域分割为两块区域&#xff0c;上方为内容区域&#xff0c;下方为空白区域。…

el-input添加keyup事件无响应

<el-input type"password" v-model"formData.password" keyup.enter"onSubmit"></el-input>使用 .native修饰符 有时&#xff0c;Vue 组件内部可能会处理某些事件&#xff0c;导致你不能直接在组件上监听这些事件。 在这种情况下&am…

【代码随想录Day28】

Day 28 回溯 Part04 今日任务 93.复原IP地址78.子集90.子集II 代码实现 今天的题出乎意料的简单 复原IP地址&#xff0c;和昨天的分割回文串比较像 一个是分割&#xff0c;一个是判断ip有效 List<String> result new ArrayList<>();List<String> path …

【数据结构】链表力扣刷题详解

前言 题目链接 移除链表元素 链表的中间结点 反转链表 分割链表 环形链表的约瑟夫问题 ​ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 移除链表元素 题述 给你一个链表的头节点 head 和一个整数 val &#xff0c;请…

【Spring Cloud Gateway】路由配置uri三种方式及区别

websocket配置方式 ws:// 或 wss:// 开头的 URI&#xff0c;表示配置的是支持 Websocket 协议的目标地址。 这种方式适用于需要与客户端建立长连接、实现双向通信的场景&#xff0c;比如实时消息推送、即时聊天等。 使用 Websocket 配置方式可以让 Spring Cloud Gateway 能够…

从基础入门到学穿C++

前言知识 C简介 C是一门什么样的语言&#xff0c;它与C语言有着什么样的关系&#xff1f; C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解…

代理IP品质对Tik Tok代理的重要性

随着Tik Tok的迅速崛起&#xff0c;越来越多的人开始关注如何透过Tik Tok进行行销和推广。其中&#xff0c;使用Tik Tok代理程式是常见的方法。 然而&#xff0c;在选择和使用代理时&#xff0c;IP品质是一个不可忽视的因素。本文将探讨IP品质对Tik Tok代理的重要性&#xff0…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:GridRow)

栅格布局可以为布局提供规律性的结构&#xff0c;解决多尺寸多设备的动态布局问题&#xff0c;保证不同设备上各个模块的布局一致性。 栅格容器组件&#xff0c;仅可以和栅格子组件(GridCol)在栅格布局场景中使用。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本…

初中分班怎么分按什么标准

初中是学生生涯中一个重要的阶段&#xff0c;它承接着小学的基础教育&#xff0c;同时为高中的深入学习打下基础。在这个关键时期&#xff0c;分班成为学校、家长和学生共同关注的焦点。那么&#xff0c;初中分班是按照什么标准来进行的呢&#xff1f; 学业成绩是初中分班的首要…

HarmonyOS 网络请求工具库封装,直接无脑用!!!

前言 HarmonyOS 原生网络请求的用法比较麻烦&#xff0c;还是有必要封装下&#xff0c;先看它的原生写法&#xff1a; // 引入包名 import http from ohos.net.http;// 每一个httpRequest对应一个HTTP请求任务&#xff0c;不可复用 let httpRequest http.createHttp(); // 用…

考研复习C语言进阶(4)

1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的方式有两个特点&#xff1a; 1. 空间开辟大小是固定的。 2. 数组在申明的时候&#…

同步方法和同步块,哪个是更好的选择?什么是线程同步和线程互斥,有哪几种实现方式?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 同步方法和同步块,哪个是更好的选择 在 Java 中,同步方法和同步块都是用于保护共享资源、避免线程之间互相干扰的机制。它们都可以用来实现线程安全的操…

Java基础---映射框架

1.数据结构 数组 特点:存储区间连续的&#xff0c;内存占用严重优点:随机读取和修改效率高。 查询来说时间复杂度O(1)缺点:插入和删除速度慢。时间复杂度为O(N) 链表 特点:存储区间是离散的。内存利用率高优点:插入和删除速度快缺点&#xff1a;不能随机查找。查询效率低 …

Docker知识--01

虚拟化 # 什么是虚拟化 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;是将计算机的各种实体资源&#xff0c;如服务器、网络、内存及存储等&#xff0c;予以抽象、转换后呈现出来&#xff0c;打…

深度学习模型参数的计算

1. 模型尺寸与参数量 根据神经网络模型中的参数数量估计模型体积的大小涉及多个考虑因素&#xff0c;包括参数的数据类型、每种数据类型的存储要求以及存储模型结构或元数据的任何额外开销。下面是估计模型大小的一般方法&#xff1a; 1.1.数据类型 神经网络参数通常存储为浮…

Vue3使用高德地图(3分钟快速上手)

一.、在高德开发平台注册账号 高德开放平台 | 高德地图API (amap.com) 二、我的 > 管理管理中添加Key 三、安装依赖 npm i amap/amap-jsapi-loader --save 四、创建一个放置地图的容器 <template><div class"container"></div> </template…