面试官必问的分布式锁面试题,你答得上来吗?

一、面试聊聊-分布式锁,如何回答?

要分析分布式锁这个问题,我们根据黄金圈法则来分析

黄金圈法则是由美国营销顾问西蒙·斯涅克(Simon Sinek)提出的一种思维模型,用于帮助人们更好地理解和传达信息。黄金圈法则由三个圈组成,分别是:

  • 为什么(Why):为什么要做这件事?这是黄金圈的核心,是一切的起点。
  • 怎么做(How):怎么做这件事?这是黄金圈的中间部分,是实现目标的方法。
  • 做什么(What):做什么这件事?这是黄金圈的外围部分,是具体的行为。


使用3w分析问题思路来分析分布式锁,可以从以下几个方面进行分析:

What:分布式锁是什么?

分布式锁是控制分布式系统之间同步访问共享资源的机制。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

回答时:先说一下概念。分布式锁是用于在分布式系统中控制对共享资源的访问,以避免数据竞争和并发问题。

How:如何实现分布式锁?

分布式锁的实现方法有很多,常见的有以下几种:

  • 数据库锁:使用数据库中的行锁或表锁来实现分布式锁。
  • 文件锁:使用文件来实现分布式锁。
  • Zookeeper锁:使用Zookeeper来实现分布式锁。
  • Redis锁:使用Redis来实现分布式锁。
  • 消息队列锁:使用消息队列来实现分布式锁。

回答时:说一下分布式锁以上的实现方式。

Why:为什么需要分布式锁?

一些需要分布式锁的场景:

  • 分布式数据库事务:在分布式系统中,通常需要使用分布式事务来保证数据的一致性。在分布式事务中,通常会使用分布式锁来保证事务的执行顺序。
  • 分布式资源分配:在分布式系统中,通常需要使用分布式锁来分配共享资源。例如,在抢购场景中,需要使用分布式锁来保证同一商品只能被一个用户购买。
  • 分布式数据同步:在分布式系统中,通常需要使用分布式锁来保证数据的同步。例如,在订单系统中,需要使用分布式锁来保证同一订单只能被一个系统修改。

回答时:说一下分布式锁的应用场景。

二、深入聊聊-分布式锁在项目中的应用

1、Redisson实现分布式锁

Redisson 是一个基于 Redis 的 Java 分布式框架。Redisson 提供了丰富的功能,包括分布式锁、分布式集合、分布式队列等。

以下是使用 Redisson 实现分布式锁的示例:

    @Autowiredprivate RedissonClient redissonClient;public String lock() {// 获取锁RLock lock = redissonClient.getLock("lock");boolean acquired = lock.tryLock(10,-1,TimeUnit.SECONDS);if (acquired) {// 获取锁成功,执行业务逻辑return "获取锁成功,执行业务逻辑...";} else {// 获取锁失败,重试return "获取锁失败,重试...";}}public String unlock() {// 释放锁RLock lock = redissonClient.getLock("lock");lock.unlock();return "释放锁成功...";}

另外,redisson支持锁续期。即在锁键值过期后任务还没执行完成,此时需要把锁键值的时间自动延长。

Redisson提供了的续期机制,只要客户端加锁成功,就会启动一个Watch Dog。可以看到源代码的实现leaseTime不设置为-1时开启监听。如果任务没完成就调用scheduleExpirationRenewal续期方法。

tryLock() 方法用于尝试获取分布式锁,该方法有三个参数:

  • key:锁的键值
  • waitTime:等待获取锁的时间,单位为毫秒
  • leaseTime:锁的过期时间,单位为毫秒

waitTime 参数表示客户端最多等待多长时间来获取锁。如果在 waitTime 时间内没有获取到锁,则会返回 false。

leaseTime 参数表示锁的过期时间。如果锁在 leaseTime 时间内没有被释放,则会自动释放。如果 leaseTime 设置为 -1,则表示锁的过期时间由 renew() 方法来控制。这样,在业务逻辑执行过程中,可以定期调用 lock.renew() 方法来续期锁的过期时间。

tryLock() 方法的返回值是一个 Boolean 值,表示是否成功获取到锁。如果成功获取到锁,则返回 true。否则,返回 false。

2、Springboot+Lettuce

在 SpringBoot 2.7 中,可以通过 spring-boot-starter-data-redis 默认依赖是Lettuce。那么Lettuce是如何实现分布式锁呢

  1. 在 Spring Boot 项目中添加 spring-boot-starter-data-redis 依赖。
  2. 定义一个 RedisLock 类来封装分布式锁的相关操作。
  3. 在 ServiceImpl类中使用 RedisLock 类来获取和释放分布式锁。

以下是 SpringBoot+Lettuce 实现分布式锁的完整代码:

@Component
public class RedisLock {private static final String LOCK_SCRIPT = "if redis.call('exists', KEYS[1]) == 0 then\n" +"    redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]);\n" +"    return 1\n" +"else\n" +"    return 0\n" +"end";private final RedisTemplate<String, String> redisTemplate;public RedisLock(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}//获得锁public boolean acquireLock(String key, long timeout) {String uuid = UUID.randomUUID().toString();Object result = redisTemplate.execute(new DefaultRedisScript(LOCK_SCRIPT, Long.class), Arrays.asList(key), uuid, timeout);return result != null && (long) result == 1;}//释放锁public void releaseLock(String key, String uuid) {redisTemplate.delete(key);}
}

@Service
public class TestServcieImpl implements TestServcie{@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 加锁脚本private static final String LOCK_SCRIPT = "if redis.call ('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call ('expire', KEYS[1], ARGV[2]) else return 0 end";// 解锁脚本private static final String UNLOCK_SCRIPT = "if redis.call ('get', KEYS[1]) == ARGV[1] then return redis.call ('del', KEYS[1]) else return 0 end";// 加锁方法public boolean lock(String key, String value, Long expire) {RedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expire));return result.equals(Long.valueOf(1));}// 解锁方法public boolean unlock(String key, String value) {RedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);return result.equals(Long.valueOf(1));}
}

在上述 demo 中,我们使用 RedisLock 类来封装分布式锁的相关操作。acquireLock() 方法用于获取分布式锁,releaseLock() 方法用于释放分布式锁。

在 ServcieImpl 类中,我们使用 RedisLock 类来获取和释放分布式锁。lock() 方法用于获取锁,unlock() 方法用于释放锁。不像Redisson封装好了相应的方法,Lettuuce如果要实现锁续期就需要自己写监听器及相应的lua脚本。

三、小结

1、什么是Lua?

可以看到不论是Redisson还是Lettuce实现分布式锁都使用的Lua脚本,那我们先来了解一下什么是Lua脚本语言。

Lua 是一个小巧的脚本语言,由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的。Lua 使用标准 C 语言编写并以源代码形式开放,几乎在所有操作系统和平台上都能编译运行。Lua 脚本可以调用 C/C++ 的函数,也可以被 C/C++ 代码调用,所以 Lua 在应用程序中可以被广泛应用。

Lua 的特点如下:

  • 小巧灵活:Lua 的核心只有 200K,非常小巧,可以方便地嵌入到应用程序中。Lua 的语法也非常简单,易于学习和使用。
  • 可扩展性:Lua 可以调用 C/C++ 的函数,也可以被 C/C++ 代码调用,所以 Lua 可以很容易地扩展到应用程序的其他部分。
  • 高性能:Lua 的运行效率非常高,可以满足大多数应用程序的需求。

Lua 在游戏开发、Web 开发、嵌入式系统等领域都有广泛的应用。

以下是 Lua 的一些典型应用:

  • 游戏开发:Lua 常用于游戏中的脚本编写,用于实现游戏的逻辑和特效。
  • Web 开发:Lua 可以用于 Web 开发,用于实现动态页面和游戏。
  • 嵌入式系统:Lua 可以用于嵌入式系统的开发,用于实现控制逻辑和用户界面。

Lua 是一款非常实用的脚本语言,在众多领域都有广泛的应用。

2、为什么使用Lua?

  • Redis 实现分布式锁中,获取锁、释放锁为什么要使用 Lua 脚本?
    • 使用 Lua 脚本的主要原因是为了保证操作的原子性,避免出现并发问题或误解锁的情况。
    • 使用 setnx 命令获取锁,然后使用 expire 命令设置过期时间,这两个命令之间可能会发生网络延迟或者其他异常,导致锁没有正确设置过期时间,从而造成死锁。
    • 使用 del 命令释放锁,需要先判断锁是否属于当前客户端,否则可能会误解其他客户端的锁。

使用 Lua 脚本可以将判断和删除锁的操作合并为一个原子操作,避免了这些问题。Lua 脚本在 Redis 服务器端执行,不会受到网络延迟或者客户端故障的影响,也不会被其他命令打断,因此可以保证操作的原子性。

  • 为什么说 Redis 命令没有原子性?
    • Redis 命令本身是单线程执行的,所以单个命令是具有原子性的。
    • 但是如果要实现分布式锁的功能,通常需要多个命令组合起来执行,例如 setnx + expire 或者 get + del。
    • 这些命令组合在执行过程中可能会被其他客户端发送的命令打断,导致数据不一致或者逻辑错误。

因此,Redis 命令没有原子性是指多个命令组合起来执行时没有原子性。

以下是使用 Lua 脚本实现分布式锁的示例:

-- 获取锁
function acquire_lock(key, uuid, timeout)local value = redis.call("GET", key)if value == nil thenredis.call("SET", key, uuid, "NX", "PX", timeout)return 1elsereturn 0end
end-- 释放锁
function release_lock(key, uuid)redis.call("DEL", key)
end

上述脚本实现了简单的 SETNX 和 DEL 操作,可以保证同一时刻只有一个客户端可以获取到锁。

在实际使用中,可以根据具体的业务场景来调整 Lua 脚本的实现。


如果文章对你有帮助,欢迎关注+点赞!!!

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

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

相关文章

自定义类型:结构体,枚举,联合

目录 前言 一、结构体 1.构体的声明 1.1 结构的基础知识 1.2 结构的声明 1.3 特殊的声明 1.4 结构的自引用 1.5 结构体变量的定义和初始化 1.6 结构体内存对齐 1.7 修改默认对齐数 2. 位段 2.1 什么是位段 2.2 位段的内存分配 2.3 位段的跨平台问题 2.4 位段的应…

网络基础知识面试题1

VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)

Spring-事务管理-加强

目录 开启事务 编程式事务 声明式事务 声明式事务的优点 声明式事务的粒度问题 声明式事务用不对容易失效 Spring事务失效可能是哪些原因 Transactional(rollbackFor Exception.class)注解 Spring 事务的实现原理 事务传播机制 介绍 用法 rollbackFor 场景举例 …

深入探究C++编程中的资源泄漏问题

目录 1、GDI对象泄漏 1.1、何为GDI资源泄漏&#xff1f; 1.2、使用GDIView工具排查GDI对象泄漏 1.3、有时可能需要结合其他方法去排查 1.4、如何保证没有GDI对象泄漏&#xff1f; 2、进程句柄泄漏 2.1、何为进程句柄泄漏&#xff1f; 2.2、创建线程时的线程句柄泄漏 …

CleanMyMacX 永久版下载激活码破解版

CleanMyMac X最新破解版V4.9.2是一款出色的Mac 系统垃圾清理程序。 该软件具有强大的垃圾清理功能&#xff0c;可以释放数 GB 的空间&#xff0c;让您的 Mac 焕然一新。 带有激活码的完整破解版 CleanmyMac 可加速您的 Mac 设备。 此外&#xff0c;cleanmymac还能帮助您从 Mac…

小谈设计模式(17)—状态模式

小谈设计模式&#xff08;17&#xff09;—状态模式 专栏介绍专栏地址专栏介绍 状态模式关键角色上下文(Context)抽象状态(State)具体状态(Concrete State) 核心思想Java程序实现首先&#xff0c;我们定义一个抽象状态类 State&#xff0c;其中包含一个处理请求的方法 handleRe…

Centos7 安装mysql 8.0.34并设置不区分大小写

索引 Centos7 安装mysql 8.0.34准备工作安装教程安装并配置配置MySQL配置远程访问重新启动MySQL服务 为已安装的MySQL8设置不区分大小写背景操作步骤 Centos7 安装mysql 8.0.34 准备工作 centos7 服务器 xshell 安装教程 安装并配置 在安装MySQL之前&#xff0c;我们应该…

【无标题】This project has been opened by another efinity instance

This project has been opened by another efinity instance 说明&#xff1a;&#xff08;1&#xff09;软件自动即出可能有些进程没有关闭 &#xff08;2&#xff09;目录中有中文路径。

POJ 2104 K-th Number 平方分割 / 线段树

一、题目大意 长度为n&#xff08;n<100000&#xff09;的数组&#xff0c;进行m次查询&#xff08;m<5000&#xff09;&#xff0c;每次查询时&#xff0c;输入为 i j k&#xff0c;返回为数组 [i,j] 的分片里第k大数字&#xff08;1<i<j<n,k<j-i1) 二、解…

vue3 中使用echarts图表——柱状图

柱状图是比较常用的图形结构&#xff0c;所以我先收集一些精美的柱状图 一、柱状图&#xff1a;设置圆角和颜色 <template><div class"box" ref"chartDom"></div> </template> <script setup> import { ref, onMounted } fr…

力扣第100题 相同的数 c++ 二叉 简单易懂+注释

题目 100. 相同的树 简单 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出…

RabbitMQ之Direct(直连)Exchange解读

目录 基本介绍 使用场景 springboot代码演示 演示架构 工程概述 RabbitConfig配置类&#xff1a;创建队列及交换机并进行绑定 MessageService业务类&#xff1a;发送消息及接收消息 主启动类RabbitMq01Application&#xff1a;实现ApplicationRunner接口 基本介绍 在r…

功能测试复习

一。测试流程 1.需求评审 确保各部门需求理解一致 2.计划编写 测什么&#xff0c;谁来测&#xff0c;怎么测 3.用例设计 验证项目是否符合需求的操作文档 4.用例执行 项目模块开发完成开始执行用例文档实施测试 5.缺陷管理 对缺陷进行…

[晕事]今天做了件晕事21;设置代理访问网站的时候需注意的问题

今天在家上班&#xff0c;设置好VPN&#xff0c;通过代理来访问公司内部的一个系统浏览器的反应如下&#xff1a; Hmmm… can’t reach this page ***.com refused to connect. 这个返回的错误&#xff0c;非常的具有迷惑性&#xff0c;提示的意思&#xff1a;拒绝链接&#xf…

李沐深度学习记录2:10多层感知机

一.简要知识记录 x.numel()&#xff1a;看向量或矩阵里元素个数 A.sum()&#xff1a;向量或矩阵求和&#xff0c;axis参数可对某维度求和&#xff0c;keepdims参数设置是否保持维度不变 A.cumsum&#xff1a;axis参数设置沿某一维度计算矩阵累计和x*y:向量的按元素乘法 torch.…

时间序列常用数据处理

1.组合技巧Compose 1.2 实例应用及其解释 # 用于组合多个数据处理方法 class Compose(object):def __init__(self, transforms):self.transforms transformsdef __call__(self, seq):for t in self.transforms:seq t(seq)return seq 这段Python代码定义了一个名为Compose的…

【Spring Boot】日志文件

日志文件 一. 日志文件有什么用二. 日志怎么用三. ⾃定义⽇志打印1. 在程序中得到⽇志对象2. 使⽤⽇志对象打印⽇志3. ⽇志格式说明 四. 日志级别1. ⽇志级别有什么⽤2. ⽇志级别的分类与使⽤ 五. 日志持久化六. 更简单的⽇志输出—lombok1. 添加 lombok 依赖2. 输出⽇志3. lom…

Javascript - 轮播图

轮播图也称banner图、广告图、焦点图、滑片。是指在一个模块或者窗口,通过鼠标点击或手指滑动后,可以看到多张图片。这些图片统称为轮播图,这个模块叫做轮播模块。可以通过运用 javascript去实现定时自动转换图片。以下通过一个小Demo演示如何运用Javascript实现。 <!DOCTYP…

《计算机视觉中的多视图几何》笔记(12)

12 Structure Computation 本章讲述如何在已知基本矩阵 F F F和两幅图像中若干对对应点 x ↔ x ′ x \leftrightarrow x x↔x′的情况下计算三维空间点 X X X的位置。 文章目录 12 Structure Computation12.1 Problem statement12.2 Linear triangulation methods12.3 Geomet…

【Java】内部类

目录 概念&#xff1a; 内部类访问特点 示例代码&#xff1a; 运行结果&#xff1a; 内部类分类 1. 成员内部类 示例代码&#xff1a; 2. 静态内部类 示例代码&#xff1a; 3. 方法内部类(局部内部类) 示例代码&#xff1a; 4. 匿名内部类 示例代码&#xff1a; 概…