首页 > 其他分享 >分布式锁的详细解释

分布式锁的详细解释

时间:2024-03-18 23:04:52浏览次数:24  
标签:解释 lockPath lock 线程 详细 进程 节点 分布式

什么是分布式锁

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

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

分布式锁可以通过多种方式来实现,包括基于数据库、基于缓存、基于分布式协议等。常见的实现方式包括使用分布式缓存如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();
        }
    }

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

  

标签:解释,lockPath,lock,线程,详细,进程,节点,分布式
From: https://blog.csdn.net/Flying_Fish_roe/article/details/136824627

相关文章

  • 【SQL Server】超详细SQLServer日期转换、字符串、数学、聚合等常用函数大全(最新版)
    文章目录一、字符串函数1、获取uuid2、字符串截取3、字符串拼接4、字符串去空格5、大小写转换6、格式化数字为字符串7、字符串替换、转换8、查找与定位9、ISNULL判空取值二、日期时间函数1、获取当前日期和时间2、提取日期部分3、DATENAME(datepart,date_expr)函数,返......
  • SAM/广义 SAM 非常偷懒的解释
    这里是模板题P6139。进行了一个广义SAM的学习。离线部分OiWiki讲得很好,但是在线部分没有。我做一些补充。首先SAM是什么?简单来说,SAM是一个有向无环图,节点是状态,对应一个endpos(\(S\)子串\(t\)的结尾位置集合)。边标有字符。SAM是有\(t_0\)初始状态,若干个结尾......
  • rabbitma 详细介绍、集群搭建、镜像队列,很全缺啥和我说
    一、rabbitma介绍1、rabbitmq简介RabbitMQ是一个开源的消息代理和队列服务器,它用于通过轻量且可靠的消息在服务器之间进行通信。RabbitMQ实现了高级消息队列协议(AMQP),这一协议最初由摩根大通牵头设计,随后被多家公司采纳并推广。作为AMQP协议的开源实现,RabbitMQ可以跨多种语言......
  • Java 文件处理完全指南:创建、读取、写入和删除文件详细解析
    Java文件操作文件处理简介文件处理是任何应用程序的重要部分。Java提供了许多用于创建、读取、更新和删除文件的方法。Java文件处理Java中的文件处理主要通过java.io包中的File类完成。该类允许我们处理文件,包括创建、读取、写入和删除文件。创建File对象要使用F......
  • Python 递归函数实现二分法,带思路解释
            二分法可以大大提升对有序数列的查找,传统的迭代查找会挨个比较数列中的值,如果数列较为庞大会影响查询效率。二分法每次取数列的中间数与待查找数字比较大小,以升序排列为例子 首先要考虑数列长度的奇偶性。        奇数取中间位置的数字,如果比待查找......
  • Java详细安装教程--Java(jdk)安装附jdk安装包 不用登录oracle官网
    Java详细安装教程--Java(jdk)安装一、java历史简介1991年Sun公司的JamesGosling等人开始开发名称为Oak(橡树)的语言。希望用于控制嵌入在有线电视交换盒、PDA等的微处理器,1994年将Oak语言更名为Java1998年JDK1.2时,更名为Java2Platform分为标准版J2SE,企业版J2EE,微型版J2ME......
  • 【详细带你了解软件系统架构的演变】
    带你了解软件系统架构的演变`1.介绍``2.传统的单体架构(MonolithicArchitecture)``3.分层架构(LayeredArchitecture)``4.客户端-服务器架构(Client-ServerArchitecture)``5.服务导向架构(Service-OrientedArchitecture,SOA)``6.微服务架构(MicroservicesArchitecture)``7......
  • 搭建完全分布式
    下载网络工具1.进入rootsuroot2.进入指定路径3.编辑文件viifcfg-eno*no改为yesvi文件时,i编辑模式,esc退出编辑模式,:wq保存退出,:q!退出4.重启网络服务servicenetworkrestart5.下载网络工具yuminstall-ynet-tools使用SecureCRT使用时请求超时,虚拟机可以ping主机,......
  • 从单机到分布式微服务,大文件校验上传的通用解决方案
    一、先说结论本文将结合我的工作实战经历,总结和提炼一种从单体架构到分布式微服务都适用的一种文件上传和校验的通用解决方案,形成一个完整的方法论。本文主要解决手段包括多线程、设计模式、分而治之、MapReduce等,虽然文中使用的编程语言为Java,但解决问题和优化思路是互通的,......
  • openGauss的WDR报告详细解读
    openGauss的WDR报告详细解读openGauss数据库自2020年6月30日开源至今已有10个月了,在这短短的10个月内,openGauss社区用户下载量已达13W+、issue合并2000+、发行商业版本6个。仅3月份就有11家企业完成CLA签署,包括虚谷伟业、云和恩墨、优炫软件、海量数据......