【面试】分布式锁专题

1.你说一下什么是分布式锁

分布式锁是一种在分布式系统环境下实现的锁机制,它主要用于解决,多个分布式节点之间对共享资源的互斥访问问题,确保在分布式系统中,即使存在有多个不同节点上的进程或线程,同一时刻也只有一个节点可以获得锁并对共享资源进行操作,从而维护数据的一致性和完整性。

扩展:

分布式锁的特点

当然要实现一个分布式锁还需要考虑一些东西,比如Redis的健壮性,它不能随便挂掉,这里总结一下分布式锁的一些要素,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性:同一时间只能一个节点获取到锁,其他节点需要等待获取到锁的节点释放了锁才可以获取到锁,而这里的等待一般是通过阻塞,和自旋两种方式
  2. 安全性:解铃还须系铃人,只能释放自己的锁不能误删别人的锁
  3. 死锁:比如在节点宕机时最容易出现锁没被释放的问题,然后出现死锁,所以做锁的过期
  4. 容错:当Redis宕机,客户端仍然可以释放锁
  5. 可重入:获取锁失败可以重新尝试获取锁

2.分布式锁常用的三种方案

基于数据库实现:通常基于主键,或者唯一索引来实现分布式锁,但是性能比较差,一般不建议使用

基于Redis :可以使用setnx来加锁 ,但是需要设置锁的自动删除来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合。

另外释放锁在finallly中调用del删除锁,而删除锁前需要判断该锁是否是当前线程加的锁以免误删除锁,需要通过get获取锁然后进行判断,但是需要保证get判断或和del删除锁的原子性,可以使用LUA脚本实现。

基于zookeeper : 使用临时顺序节点实现,线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁,后面的节点监听自己的上一个节点的删除事件,如果第一个节点被删除,释放锁第二个节点就成为第一个节点,获取到锁。

在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案。

扩展

zookeeper

存储结构

Zookeeper会维护一个具有层次关系的树状的数据结构,它非常类似于一个标准的文件系统,如下图所示:同一个目录下不能有相同名称的目录节点

ZooKeeper 节点是有生命周期的这取决于节点的类型,在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。

  1. 持久节点(PERSISTENT)所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
  2. 持久顺序节点(PERSISTENT_SEQUENTIAL)这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。
  3. 临时节点(EPHEMERAL)和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
  4. 临时顺序节点(EPHEMERAL_SEQUENTIAL)在临时几点的基础上增加了顺序,可以用来实现分布式锁

顺序节点可以用来为所有的事件进行全局排序,这样客户端可以通过序号推断事件的顺序。

zookeeper分布式锁原理

分布式锁就是基于zk的 临时顺序节点+watch监听机制完成的。临时顺序节点特点是客户端断开节点释放,且自己维护节点顺序值,当多个线程同时创建节点我们就可以按照顺序创建N个顺序临时节点,然后依次从第一个往后获取锁。只不过能拿到锁的只能是第一个节点的线程,所以后面的线程需要监听自己上一个节点的节点释放。轮到谁,谁就拿到锁。

3.Redis如何实现分布式锁,用什么命令

可以使用setnx来加锁 ,但是需要设置锁的自动删除来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合。

扩展:

setnx命令,只会在key不存在时,将将键的key设置为value。若已经存在则不做操作。

例如:果三个服务同时抢锁,服务A抢先一步执行setnx(lock_stock,1)加上锁,那么当服务B在执行setnx(lock_stock,1)加锁的时候就会失败,服务C也一样,服务A抢到锁执行完业务逻辑后就会释放锁,可以使用del(lock_stock)删除锁,其他服务就可以执行setnx(lock_stock,1)加锁了


if(jedis.setnx(lock_stock,1) == 1){	//获取锁expire(lock_stock,5)		 //设置锁超时try {业务代码} finally {jedis.del(lock_stock)			 //释放锁}
}


4.Redis实现分布式锁可能会出现什么问题,如何解决

  1. 锁超时问题,加锁和释放锁的原子性问题,锁的误删除问题,get获取锁和删除锁的原子性问题,集群模式中redis节点宕机问题
  2. 添加锁和设置过期时间可以使用set命令进行组合,达到原子性加锁
  3. 需要用lua解决删除和判断锁的原子性,否则可能会删除掉别人的锁。
  4. Redis集群环境中,redis节点挂掉可能会导致加锁失败,可以使用Redisson的红锁来解决。

扩展:

锁超时问题

这里有一个问题,如果获取到锁的服务在释放锁的时候宕机了,那么Redis中lock-stock不就永远存在,那锁不就释放不了么,别的服务也就没办法获取到锁,就造成了死锁,为了解决这个问题,我们需要设置锁的自动超时也就是Key的超时自动删除,即使服务宕机没有调用del释放锁,那么锁本身也有超时时间,可以自动删除锁,别的服务就可以获取锁了,Redis中Key的过期时间可以使用Redis的 expire(lock_stock,30)命令实现,这里给出伪代码如下

if(jedis.setnx(lock_stock,1) == 1){	//获取锁expire(lock_stock,5)		 //设置锁超时try {业务代码} finally {jedis.del(lock_stock)			 //释放锁}
}

setnx和expire操作的原子性问题

上面的代码依然有问题,就是setnx获取锁和expire不是原子性操作,假设有一极端情况,当线程通过setnx(lock_stock,1)获取到锁,还没来得及执行expire(lock_stock,30)设置锁的过期时间,服务就宕机了,那是不是锁也永远得不到释放呢???又变成了死锁,这个问题可以使用set命令解决,我们先来看一下这个命令的语法

SET key value [EX seconds] [PX milliseconds] [NX|XX]
  • EX seconds:设置时间单位为秒
  • PX milliseconds:设置时间单位为毫秒
  • NX:即setnx中的nx,就是key值不存在时才去执行
  • XX : 只在键已经存在时, 才对键进行设置操作。

也就是说该命令可以当做setnx和expire的组合命令来使用,而且是原子性的,改造代码如

if(set(lock_stock,1,"NX","EX",5) == 1){	//获取锁并设置超时try {业务代码} finally {del(lock_stock)			 		//释放锁}
}

锁的误删除问题

上面的方案依然有问题,就是在del释放锁的时候可能会误删除别人加的锁,例如服务A获取到锁lock_stock,过期时间为 5s,如果在服务A执行业务逻辑的这一段时间内,锁到期自动删除,且别的服务获取到了锁lock_stock,那么服务A业务执行完成执行del(lock_stock)有可能会把别人的锁给删除掉

解决方案: 我们可以在删除锁的时候先判断一下要删除的锁是不是自己上的锁,比如可以把锁的值使用一个UUID,在释放锁的时候先获取一下锁的值和当前业务中创建的UUID是不是同一个,如果是才执行·del删除锁,当然也可以使用线程的ID替代UUID,代码如:

String uuid = UUID.randomUUID().toString();
if(jedis.set(lock_stock,uuid,"NX","EX",5) == 1){	//获取锁并设置超时try {业务代码} finally {String lockValue = jedis.get(lock_stock);	//获取锁的值if(lockValue.equals(uuid)){			//判断是不是自己的锁jedis.del(lock_stock)			 	  //释放锁}}
}

lua脚本保证操作的原子性

但是上面的代码依然有问题,就是判断锁的代码和删除锁的代码也不是原子性的,依然可能会导致锁的误删除问题,比如服务A在判断锁成功准备删除锁时,锁自动过期,别的服务B获取到了锁,然后服务A执行DEL就可能会把服务B的锁给删除掉,所以,我们必须保证 获取锁 -> 判断锁 -> 删除锁 的操作是原子性的才可以,解决方案可以使用Redis+Lua脚本来解决一致性问题

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  • redis.call(‘get’, KEYS[1]) :是调用redis的get命令,key可以通过参数传入
  • == ARGV[1] :意思是是否和 某个值相等,这里的值也可以参数传入
  • then return redis.call(‘del’, KEYS[1]) :如果相等就执行 redis.call('del', KEYS[1]) 删除操作
  • else return 0 end :否则就返回 0

如果我们把数据带入KEYS[1]的值为“lock_stock”,ARGV[1]的值为UUID如“xoxoxo”,所以大概的含义是如果调用get(“lock_stock”)获取到的值 等于 “xoxoxo” ,那就调用 del(“lock_stock”),否则就返回 0 。 说白了就是把我们上面的判断锁和删除锁的动作使用Lua脚本去执行而已,现在代码可以这样写了

String uuid = UUID.randomUUID().toString();
if(jedis.set(lock_stock,uuid,"NX","EX",5) == 1){	//获取锁并设置超时try {业务代码} finally {//lua脚本String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//执行脚本jedis.eval(script, Collections.singletonList("lock_stock"),Collections.singletonList(uuid));}
}


5.你项目中怎么使用分布式锁的

自己封装Redis的分布式锁是很麻烦的,我们可以使用Redissoin来实现分布式锁,Redissoin已经封装好了

扩展:

  • Redisson加锁自动有过期时间30s,监控锁的看门狗发现业务没执行完,会自动进行锁的续期(重回30s),这样做的好处是防止在程序执行期间锁自动过期被删除问题
  • 当业务执行完成不再给锁续期,即使没有手动释放锁,锁的过期时间到了也会自动释放锁

Redisson执行流程

如果没有设置过期时间,Redisson以 30s 作为锁的默认过期时间,获取锁成功后(底层也用到了Lua脚本保证原子性)会开启一个定时任务定时进行锁过期时间续约,即每次都把过期时间设置成 30s,定时任务 10s执行一次(看门狗)

如果设置了过期时间,直接把设定的过期时间作为锁的过期时间,然后使用Lua脚本获取锁,没获取到锁的线程会while自旋重入不停地尝试获取锁

这里需要注意,rLock.lock(10, TimeUnit.SECONDS)指定了解锁时间,Redisson就不会再自动续期,那么如果在线程A业务还没执行完就自动解锁了,这时候线程B获取到锁,继续执行业务,那么等线程A业务执行完释放锁就可能会把线程B的锁删除,当然这种情况Redisson会报异常,但是这种情况是没有把所有线程都锁住的,所以如果要手动设定过期时间需要让过期时间比业务逻辑执行的时间长才对

红锁(RedLock)

Redis常用的方式有单节点、主从模式、哨兵模式、集群模式,在后三种模式中可能会出现 ,异步数据丢失,脑裂问题,Redis官方提供了解决方案:RedLock,RedLock是基于redis实现的分布式

锁,它能够保证以下特性:

  • 容错性:只要多数节点的redis实例正常运行就能够对外提供服务,加锁释放锁
  • 互斥性:只能有一个客户端能获取锁,即使发生了网络分区或者客户端宕机,也不会发生死锁

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
lock.unlock();

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

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

相关文章

Error relaunching VirtualBox VM process: 5 启动虚拟机时发生了错误

出现错误 一大早起来发现虚拟机打不开,看了虚拟机日志是正常的,还回了个档都不行。 最后我突然想起之前在哪看到过:“完美游戏平台会导致虚拟机的问题。” 解决方法 于是我把完美游戏卸载了,发现,真的&#xf…

MCU、ARM体系结构,单片机基础,单片机操作

计算机基础 计算机的组成 输入设备、输出设备、存储器、运算器、控制器 输入设备:将其他信号转换为计算机可以识别的信号(电信号)。输出设备:将电信号(0、1)转为人或其他设备能理解的…

从零开始:如何在.NET Core Web API中完美配置Swagger文档

目录 新建项目 RestFul Swagger配置 注释展示 版本控制 Token传值 方法封装 新建项目 打开visual studio创建新项目,这里我们选择.net core web api模板,然后输入项目名称及其解决方案创建新项目 这里使用配置一些其他信息,根据自己情…

百度搜索应适用中文域名国家标准,修复中文网址展示BUG

12月1日中文域名国家标准正式实施。该标准“明确了中文域名在编码、解析、注册、字表等方面的技术要求,适用于中文域名注册管理机构、注册服务机构、网络软硬件服务商及终端用户”。 00:23 显然,百度作为网络软硬件服务商,是包括在国家标准的…

Windows安装elasticsearch、Kibana以及IK分词器

一、下载 1.下载elasticsearch 访问官网Download Elasticsearch | Elastic,下载elasticsearch 2.下载 Kibana 访问Download Kibana Free | Get Started Now | Elastic ,下载 Kibana 3. IK分词器下载 访问Gitee 极速下载/elasticsearch-analysis-ik选…

第一个C++程序--(蓝桥杯备考版)

第一个C程序 基础程序 #include <iostream>//头⽂件 using namespace std;//使⽤std的名字空间 int main()//main函数 {cout << "hello world!" << endl; //输出&#xff1a;在屏幕打印"hello world!" return 0;}main函数 main 函数是…

Elasticsearch Serverless 中的数据流自动分片

作者&#xff1a;来自 Elastic Andrei Dan 在 Elastic Cloud Serverless 中&#xff0c;我们根据索引负载自动为数据流配置最佳分片数量&#xff0c;从而使用户无需摆弄分片。 传统上&#xff0c;用户会更改数据流的分片配置&#xff0c;以处理各种工作负载并充分利用可用资源。…

TcpServer 服务器优化之后,加了多线程,对心跳包进行优化

TcpServer 服务器优化之后&#xff0c;加了多线程&#xff0c;对心跳包进行优化 TcpServer.h #ifndef TCPSERVER_H #define TCPSERVER_H#include <iostream> #include <winsock2.h> #include <ws2tcpip.h> #include <vector> #include <map> #…

python进阶-05-利用Selenium来实现动态爬虫

python进阶-05-利用Selenium来实现动态爬虫 一.说明 这是python进阶部分05&#xff0c;我们上一篇文章学习了Scrapy来爬取网站&#xff0c;但是很多网站需要登录才能爬取有用的信息&#xff0c;或者网站的静态部分是一个空壳&#xff0c;内容是js动态加载的,或者人机验证&…

Linux —— vim 编辑器

一、什么是vim vim是一个功能强大、高度可定制的文本编辑器。以下是对vim编辑器的具体介绍&#xff1a; 历史背景&#xff1a;vim最初由Bram Moolenaar在1991年开发&#xff0c;作为vi编辑器的增强版&#xff0c;增加了许多新的特性和改进。它继承了vi的基本编辑功能和键盘快捷…

高效率同步降压转换器 - YB2416D: 实现快速充电和高效能供电的利器

概述: YB2416是一款输入耐压超过40V&#xff0c;在4.5V~30V输入电压条件下正常工作&#xff0c;并且能够实现精确恒压以及恒流的同步降压型DC-DC转换器。 内部集成80m2的上管和40m2的下管&#xff0c;无需外部肖特基二极管&#xff0c;可连续输出3A电流。输出3A电流时系统转换…

Repo管理

文章目录 前言Repo介绍清单仓库清单仓库的组成 初始化Repo同步远程仓库Repo实际应用 前言 我们知道&#xff0c;Git是用来管理某一个仓库&#xff0c;那当一个项目用到了多个仓库时&#xff0c;怎么来同步管理这些仓库呢&#xff1f;这个时候就可以引入Repo管理。 Repo介绍 …

神经网络的起源与工作原理

神经网络起源&#xff1a;一个生物神经网络是由一组化学上相连或功能上相关的神经元组成。一个神经元可能与许多其他神经元相连&#xff0c;网络中的神经元和连接的总数可能很广泛。连接&#xff0c;称为突触&#xff0c;通常是从轴突到树突形成的&#xff0c;尽管树突和其他连…

Qwen2.5-7B-Instruct vLLM 部署调用

Qwen2.5-7B-Instruct vLLM 部署调用 vLLM 简介 vLLM 框架是一个高效的大语言模型推理和部署服务系统&#xff0c;具备以下特性&#xff1a; 高效的内存管理&#xff1a;通过 PagedAttention 算法&#xff0c;vLLM 实现了对 KV 缓存的高效管理&#xff0c;减少了内存浪费&…

解决 Mac(M1/M2)芯片,使用node 14版本

前言 nvm 在安装 Node.js v14.21.3 时&#xff0c;报错&#xff1a; nvm install 14 Downloading and installing node v14.21.3... Downloading https://nodejs.org/dist/v14.21.3/node-v14.21.3-darwin-arm64.tar.xz... curl: (56) The requested URL returned error: 404Bin…

TesseractOCR-GUI:基于WPF/C#构建TesseractOCR简单易用的用户界面

前言 前篇文章使用Tesseract进行图片文字识别介绍了如何安装TesseractOCR与TesseractOCR的命令行使用。但在日常使用过程中&#xff0c;命令行使用还是不太方便的&#xff0c;因此今天介绍一下如何使用WPF/C#构建TesseractOCR简单易用的用户界面。 普通用户使用 参照上一篇教…

【ETCD】【源码阅读】configurePeerListeners() 函数解析

configurePeerListeners 是 ETCD 的一个核心函数&#xff0c;用于为集群中节点之间的通信配置监听器&#xff08;Peer Listener&#xff09;。这些监听器主要负责 Raft 协议的消息传递、日志复制等功能。函数返回一个包含所有监听器的列表。 函数签名 func configurePeerList…

uniapp改成用vue起项目

目的&#xff1a;让项目按照vue的打包流程跑流水线 1.按照uniapp官网教程执行 2.执行第二条命令时报错 ERROR Failed to get response from true/vue-cli-version-marker 3.解决方式 报错可能跟yarn有关&#xff0c;然后切换成npm 找到自己本地电脑的这个文件 按照截图修…

【SH】微信小程序调用EasyDL零门槛AI开发平台的图像分类研发笔记

文章目录 微信小程序字符串字符串模板字符串拼接 上传图片编写JS代码编写wxml代码编写wxss代码 GET请求测试编写测试代码域名不合法问题 GET和POST请求测试编写JS代码编写wxml代码编写wxss代码 效果展示 微信小程序字符串 字符串模板 这是ES6引入的特性&#xff0c;允许你通过…

[小白系列]Ubuntu安装教程-安装prometheus和Grafana

Docker安装prometheus 拉取镜像 docker pull prom/prometheus 配置文件prometheus.yml 在/data/prometheus/建立prometheus.yml配置文件。&#xff08;/data/prometheus/可根据自己需要调整&#xff09; global:scrape_interval: 15s # By default, scrape targets ev…