当前位置: 首页 > news >正文

深入学习 Redis - 分布式锁底层实现原理,以及实际应用

目录

一、Redis 分布式锁

1.1、什么是分布式锁

1.2、分布式锁的基础实现

1.2.1、引入场景

1.2.2、基础实现思想

1.2.3、引入 setnx

1.3、引入过期时间

1.4、引入校验 id

1.5、引入 lua 脚本

1.5.1、引入 lua 脚本的原因

1.5.2、lua 脚本介绍

1.6、过期时间续约问题(看门狗 Watch Dog)

1.7、引入 redlock 算法

1.8、分布式锁扩展


一、Redis 分布式锁


1.1、什么是分布式锁

锁就是用来解决线程安全的,分布式锁又是什么呢?

之前所学过的 synchronized 本质上都是只能在一个进程内部生效的,而在分布式系统中,是有很多进程的(每个服务器都是一个独立的进程),多个进程之间的执行顺序也是不确定的(随机的 ),之前的锁,就很应对分布式系统中多个进程之间产生的制约.

因此,就需要引入 “分布式锁” 来解决上述问题.

分布式锁本质上就是一个公共的服务器,用来记录锁的状态。

Ps:这个公共的服务器可以是Redis, 也可以是其他组件(比如 MySQL 或者 ZooKeeper 等), 还可以是我们自己写的⼀个服务

1.2、分布式锁的基础实现

1.2.1、引入场景

想象这样一个场景:买车票.

实现思路:先查询剩余票数,如果剩余票数 > 0,则设置剩余票数 -= 1.

在没有引入分布式锁之前,就有可能出现以下买票场景:

客户端1 先执行查询余票,发现剩余 1 张,在即将执行 剩余票数 -= 1 过程之前,客户端2 也执行了查询余票,发现也是剩余 1 张,客户端2  也会执行 剩余票数 -= 1. 的过程.

这里就出现了 “超卖” 的场景!!!1 张票,卖给了两个人.

1.2.2、基础实现思想

分布式锁的实现思路很简单,本质上是使用一个/一组 单独的服务器程序,通过用一个键值对来标识锁的状态,来给其他服务器提供 “加锁” 这样的服务.

对于上述场景,进行买票操作过程中,就需要先加锁.

具体的,往 redis 上设置一个特殊的 key - value,接着完成买票操作,再把这个 key - value 删除掉.  如果在 客户端1 买票的期间,客户端2 也想去买票,就也会尝试设置 key - value,如果发现  key - value 已经存在,就分为 “加锁失败”(是放弃还是阻塞,就要看具体的实现策略了).

这样就保证了 第一个服务器执行 “查询 -> 更新” 过程中,第二个服务器不会执行 “查询” ,也就解决了上述 “超卖” 问题.

Ps:买票的场景使用 mysql 的事务,也可以批量执行 查询 + 修改 操作. 但是分布式系统中,要访问的共享资源不一定是 mysql ....... 也可能是其他存储介质,没有事务. 也可能是执行一段特定的操作,是通过统一的服务器完成指定动作.

1.2.3、引入 setnx

针对于刚刚买票的场景:“key 不存在就设置成功,不存在就设置失败”. 使用 setnx 就可以达到 “加锁” 的效果. 针对解锁,就可以使用 del 命令来完成.

如果某个服务器,加锁成功了(setnx 成功),执行后续逻辑中,还没来得及执行 “解锁” 程序就崩溃了,怎么办?

以前在一个进程中,为了保证解锁的操作能执行到,可以把解锁的操作放到 finally 中,但是这种做法,只是针对进程内的锁有效,针对分布式锁,无效!

比如,服务器直接掉电,进程直接异常终止,这就会导致 redis 上设置的 key 无人删除,也就导致其他服务器无法获取到锁了.

1.3、引入过期时间

针对上述 “没来得及解锁,服务器宕机的情况”,我们可以给 key 设置一个过期时间.

通过 set ex nx 这样的命令完成设置,一旦时间到了,key 就会自动被删除掉.

比如,设置 key 的过期时间,为 1000ms,那么即使出现极端情况,某个服务器挂了,没有真正释放锁,这个锁最多保持 1000ms,也就自动释放了.

 可以通过先 setnx ,再使用 expire 的方式设置过期时间么?

不可以!!!务必要使用 set ex nx 的方式来设置!

redis 上多个命令之间,无法保证原子性,即使使用 事务,也不能保证这两个操作都能成功(redis 的事务只能保证不被 “插队”,不能保证操作成功). 此时就有可能出现 setnx 成功,expire 失败的场景.

1.4、引入校验 id

所谓锁,就是 redis 上的普通键值对.

所谓加锁,就是给 redis 上设置一个 key - value.

所谓解锁,就是把 redis 上这个 key - value 删除掉.

是否可能出现 服务器1  执行了加锁,服务器2 执行了解锁?

正常来说,肯定不是故意的,但是代码总会有 bug,不小心执行了解锁操作,就让这锁形同虚设,带来严重后果(比如 超卖).

为了解决上述问题,就需要引入一点校验机制.

具体的,如下步骤:

1. 给服务器编号,让每个服务器都有一个自己的身份标识.

2. 进行加锁的时候,设置 key - value, key 就表示要针对哪个资源加锁,value 就表示服务器的编号.

后续在解锁的时候,就可以进行校验了.  解锁的时候,先查询一下这个锁对应的服务器编号,然后判定这个编号是否就是当前执行解锁的服务器编号,如果是,才真正执行 del,如果不是,就失效.

通过上述操作,就可以有效避免 “误解锁”.

1.5、引入 lua 脚本

1.5.1、引入 lua 脚本的原因

一个服务器内部,也可能是多线程的. 此时,就可能两个线程都在执行 “解锁” 操作.

例如如下场景: 

首先我们知道,解锁的操作分为两步,先通过 GET 服务器编号进行校验,校验成功后在进行 DEL.

在 服务器1 中,线程A 执行 GET 后 线程 B 也执行 GET,然后 线程A 执行 DEL 解锁,此时 线程 B 也执行 DEL 解锁.

上述情况,看起来好像重复执行 DEL 好像问题不大?实则不然!

如果此时还有一个服务器,执行加锁,就可能出问题了.

在 线程A 执行完 DEL 之后,线程 B 执行 DEL 之前,服务器2 的 线程C 正好要执行 加锁(set ex nx),此时,由于 A 已经解锁了,C 的加锁能成功,但是紧接着,线程 B DEL 就来了,就把 服务器2 刚刚的加锁操作给解除了.

归根结底,还是因为 get 和 del 不是一条原子操作产生的问题.

使用事务,虽然可以解决上述问题(redis 事务虽然弱,但是能够避免插队),但是实践中,往往使用更好的方案 —— lua 脚本.

1.5.2、lua 脚本介绍

lua 语言特别轻量(实现一个 lua 解释器,消耗的体积非常小),可以使用 lua 编写一些逻辑,把这个脚本上传到 redis 服务器上,然后就可以让客户端来控制 redis 执行上述脚本了.

最重要的一点就是,redis 执行一个 lua 脚本,就相当于在 redis 上执行一个命令一样,是原子的. 并且 redis 官方文档中也明确说,lua 就属于是 事务 的替代方案.

例如前面的 “买票” 案例.

if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) 
else return 0 
end;

ARGV[1]:表示调用脚本给定的参数,此处要传入一个服务器的 id.

如果 id 和 get 到参数匹配,就进行删除操作.

1.6、过期时间续约问题(看门狗 Watch Dog)

加锁的时候,给 key 设定 过期时间,设置成多少合适?

  • 如果设置的时间过短,就可能在 业务逻辑 还没有执行完,就释放锁了.
  • 如果设置的时间太长,就可能导致 “锁释放的不及时” 的问题.

最好的方式就是 “动态续约”.

具体的,初始情况下,设置一个过期时间(比如 1s),在还剩 300ms 的时候(这里的时间不一定是 300ms,数据灵活调整),如果当前任务还没有执行完,就把过期时间续上 1s,等到时间快到了,任务还没执行完,就再续(无限续杯)~

上述过程中,如何知道当前任务还没有执行完,要进行续杯呢?实际上,服务器这边有一个专门的线程,负责续约这个事情,这个线程也叫做 “看门狗”(这是一个比较广义的概念,很多涉及到过期时间的操作都会引入 “看门狗” ).

这样,即使服务器中途崩溃了,没人负责续约了,锁也能在短时间内自动释放.

这就好比,吃自助餐,老板都是鼓励大家,每次少拿点,少量多次~  怕的就是你一次拿太多,吃不完,大部分都剩下了. 如果每次少拿点,即使吃不下了,浪费的也不多了.

1.7、引入 redlock 算法

使用 redis 作为分布式锁,redis 本身有没有可能挂了呢?

是很有可能的!

实际工作中的 redis 都是以集群的方式部署的(至少是主从,不会是单机),那么就有可能出现以下大冤种的情况:

服务器1 向 master 节点进行加锁操作. 这个写⼊ key 的过程刚刚完成, master 挂了; slave 节点升级成了新的 master 节点. 但是由于刚才写⼊的这个 key 尚未来得及同步给 slave(主节点和从节点之间的数据同步,是存在延迟的), 此时 就相当于服务器1 的加锁操作形同虚设了, 服务器2 仍然可以进行加锁.

为了解决以上问题,就提出了 redlock 算法(redis 作者给出的方案)

  1. 此处加锁,就是按照一定的顺序,针对这组 redis 都进行加锁操作. 
  2. 如果某个节点加不上锁,没关系,可能是 redis 挂了,继续给下一个节点加锁即可.
  3. 如果写入 key 成功的节点个数超过总数的一半,就视为 加锁成功.
  4. 同理,进行解锁的时候,也会把上述节点都解锁一遍.

1.8、分布式锁扩展

上面介绍的只是简单的 “互斥锁”.

锁这里还涉及到一些其他情况:

1.  读写锁.

2.  公平锁.

3.  可重入锁

........

基于 redis 也可以实现上述锁的特性,这里大家下来可以自己尝试实现以下~

相关文章:

深入学习 Redis - 分布式锁底层实现原理,以及实际应用

目录 一、Redis 分布式锁 1.1、什么是分布式锁 1.2、分布式锁的基础实现 1.2.1、引入场景 1.2.2、基础实现思想 1.2.3、引入 setnx 1.3、引入过期时间 1.4、引入校验 id 1.5、引入 lua 脚本 1.5.1、引入 lua 脚本的原因 1.5.2、lua 脚本介绍 1.6、过期时间续约问题&…...

Hive行转列[一行拆分成多行/一列拆分成多列]

场景: hive有张表armmttxn_tmp,其中有一个字段lot_number,该字段以逗号分隔开多个值,每个值又以冒号来分割料号和数量,如:A3220089:-40,A3220090:-40,A3220091:-40,A3220083:-40,A3220087:-40,A3220086:-4…...

TypeScript系列之类型 string

文章の目录 背景写在最后 背景 与JavaScript不同的是,TypeScript使用的是静态类型,比如说它指定了变量可以保存的数据类型。如下面代码所示,如果在JavaScript中,指定变量可以保存的数据类型,会报错:类型注…...

【C++】动态内存管理 ③ ( C++ 对象的动态创建和释放 | new 运算符 为类对象 分配内存 | delete 运算符 释放对象内存 )

文章目录 一、C 对象的动态创建和释放1、C 语言 对象的动态创建和释放 的方式2、C 语言 对象的动态创建和释放 的方式 二、代码示例 - 对象的动态创建和释放 一、C 对象的动态创建和释放 使用 C 语言中的 malloc 函数 可以为 类对象 分配内存 ; 使用 free 函数可以释放上述分配…...

AMS爆炸来袭,上线即巅峰

1.关于首发项目Antmons(AMS)空投结果 Gate.io Startup 首发项目Antmons代币AMS于Aug15th,AM 07:00开始下单,24小时内下单同等对待总共有15,950人下单,下单总价值超过1,000万美金分发系数约为0.001640495298341。根据上线规则AMS项目认购成功,…...

是面试官放水,还是公司实在是太缺人?这都没挂,华为原来这么容易进...

华为是大企业,是不是很难进去啊?” “在华为做软件测试,能得到很好的发展吗? 一进去就有9.5K,其实也没有想的那么难” 直到现在,心情都还是无比激动! 本人211非科班,之前在字节和腾…...

怒刷LeetCode的第2天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一:滑动窗口 方法二:双指针加哈希表 第二题 题目来源 题目内容 解决方法 方法一:二分查找 方法二:归并排序 方法三:分治法 第三题 题目来源 题目内容 解…...

AUTOSAR汽车电子嵌入式编程精讲300篇-车载CAN总线网络的异常检测(续)

目录 车载 CAN 总线网络异常检测技术 3.1 车载 CAN 总线网络异常检测技术概述 3.1.1基于统计的异...

mojo安装

docker安装mojo 官网 https://developer.modular.com/login 很奇怪登录页面不显示 类似于网站劫持 docker 安装mojo带jupyterlab的方式 https://hub.docker.com/r/lmq886/mojojupyterlab 拉取镜像 docker pull lmq886/mojojupyterlab docker pull lmq886/mojojupyterlab:1.2 启…...

【探索Linux】—— 强大的命令行工具 P.8(进程地址空间)

阅读导航 前言一、内存空间分布二、什么是进程地址空间1. 概念2. 进程地址空间的组成 三、进程地址空间的设计原理1. 基本原理2. 虚拟地址空间 概念 大小和范围 作用 虚拟地址空间的优点 3. 页表 四、为什么要有地址空间五、总结温馨提示 前言 前面我们讲了C语言的基础知识&am…...

vue3 - Element Plus 切换主题色及el-button hover颜色不生效的解决方法

GitHub Demo 地址 在线预览 Element Plus 自定义主题官方文档 如果您想要通过 js 控制 css 变量,可以这样做: // document.documentElement 是全局变量时 const el document.documentElement // const el document.getElementById(xxx)// 获取 css 变…...

【C++面向对象侯捷】1.C++编程简介

文章目录 视频来源:我的百度网盘...

年龄大了转嵌入式有机会吗?

年龄大了转嵌入式有机会吗? 首先,说下结论:年龄并不是限制转行嵌入式软件开发的因素,只要具备一定的编程和电子基础知识,认真学习和实践,是可以成为优秀的嵌入式软件开发工程师的。最近很多小伙伴找我&…...

Mysql高级——索引优化和查询优化(2)

5. 排序优化 5.1 排序优化 问题:在 WHERE 条件字段上加索引,但是为什么在 ORDER BY 字段上还要加索引呢? 优化建议: SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中避免全表…...

SpringMVC的拦截器和JSR303的使用

目录 一、JSR303 二、拦截器(interceptor) 一、JSR303 1.1.什么是JSR303 JSR 303,它是Java EE(现在称为Jakarta EE)规范中的一部分。JSR 303定义了一种用于验证Java对象的标准规范,也称为Bean验证。 Bean验…...

servlet中doGet方法无法读取body中的数据

servlet中doGet方法不支持读取body中的数据。...

Ubuntu MongoDB账户密码设置

1.创建用户 在MongoDB中,可以使用db.createUser()方法来创建用户。该方法接受一个包含用户名、密码和角色等信息的文档作为参数。 // 连接到MongoDB数据库 mongo// 切换到admin数据库 use admin// 创建用户 db.createUser({user: "admin",pwd: "adm…...

指针进阶(3)

9. 模拟实现排序函数 这里我们使用冒泡排序算法,模拟实现一个排序函数,可以排序任意类型的数据。 这段代码可以排序整型数据,我们需要在这段代码的基础上进行改进,使得它可以排序任意类型的数据。 #define _CRT_SECURE_NO_WARN…...

信息检索与数据挖掘 | (二)布尔检索与倒排索引

文章目录 📚词项-文档关联矩阵🐇相关名词🐇词项-文档关联矩阵的布尔查询处理 📚倒排索引🐇关于索引🐇建立索引🐇基于倒排索引的布尔查询处理🐇查询优化 📚字典数据结构&a…...

【学习笔记】EC-Final 2022 K. Magic

最近的题都只会抄题解😅 首先,操作顺序会影响答案,因此不能直接贪心。其次,因为是求贡献最大,所以可以考虑枚举最终哪些位置对答案产生了贡献,进而转化为全局贡献。 1.1 1.1 1.1 如果 [ l 1 , r 1 ) ⊆ [ …...

MySQL数据库笔记

文章目录 一、初识MySQL1.1、什么是数据库1.2、数据库分类1.3、MySQL简介 二、操作数据库2.1、操作数据库(了解)2.2、数据库的列类型2.3、数据库的字段属性(重点)2.4、创建数据库表(重点)2.5、数据表的类型…...

大数据之Hive(三)

分区表 概念和常用操作 将一个大表的数据按照业务需要分散存储到多个目录,每个目录称为该表的一个分区。一般来说是按照日期来作为分区的标准。在查询时可以通过where子句来选择查询所需要的分区,这样查询效率会提高很多。 ①创建分区表 hive (defau…...

让高分辨率的相机芯片输出低分辨率的图片对于像素级的值有什么影响?

很多图像传感器可以输出多个分辨率的图像,如果选择低分辨率格式的图像输出,对于图像本身会有什么影响呢? 传感器本身还是使用全部像素区域进行感光,但是在像素数据输出时会进行所谓的降采样(down-sampling&#xff09…...

FastGPT 接入飞书(不用写一行代码)

FastGPT V4 版本已经发布,可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景,例如联网谷歌搜索,操作数据库等等,功能非常强大,还没用过的同学赶紧去试试吧。 飞书相比同类产品算是体验非常好的办…...

蓝桥杯 题库 简单 每日十题 day6

01 删除字符 题目描述 给定一个单词,请问在单词中删除t个字母后,能得到的字典序最小的单词是什么? 输入描述 输入的第一行包含一个单词,由大写英文字母组成。 第二行包含一个正整数t。 其中,单词长度不超过100&#x…...

使用Arduino简单测试HC-08蓝牙模块

目录 模块简介模块测试接线代码测试现象 总结 模块简介 HC-08 蓝牙串口通信模块是新一代的基于 Bluetooth Specification V4.0 BLE 蓝牙协议的数传模块。无线工作频段为 2.4GHz ISM,调制方式是 GFSK。模块最大发射功率为4dBm,接收灵度-93dBm&#xff0c…...

如何在 CentOS 8 上安装 OpenCV?

OpenCV( 开源计算机视觉库)是一个开放源代码计算机视觉库,支持所有主要操作系统。它可以利用多核处理的优势,并具有 GPU 加速功能以实现实时操作。 OpenCV 的用途非常广泛,包括医学图像分析,拼接街景图像,监视视频&am…...

一台主机外接两台显示器

一台主机外接两台显示器 写在最前面双屏配置软件双屏跳转 写在最前面 在使用电脑时需要运行多个程序,时不时就要频繁的切换,很麻烦 但就能用双屏显示来解决这个问题,用一台主机控制,同时外接两台显示器并显示不同画面。 参考&a…...

笔记-搭建和使用docker-registry私有镜像仓库

笔记-搭建和使用docker-registry私有镜像仓库 拉取/安装registry镜像 和 对应的ui镜像 如果有网络可以直接拉取镜像 docker pull registry docker pull hyper/docker-registry-web没有网络可以使用我导出好的离线镜像tar包, 下载地址https://wwzt.lanzoul.com/i3im1194z12d …...

爬虫框架Scrapy学习笔记-2

前言 Scrapy是一个功能强大的Python爬虫框架,它被广泛用于抓取和处理互联网上的数据。本文将介绍Scrapy框架的架构概览、工作流程、安装步骤以及一个示例爬虫的详细说明,旨在帮助初学者了解如何使用Scrapy来构建和运行自己的网络爬虫。 爬虫框架Scrapy学…...

网站建设公司 南京/网络建站优化科技

安装php(fastcgi模式)的时候,常常有这样一句命令:/usr/local/webserver/php/bin/phpize 一、phpize是干嘛的? phpize是什么东西呢?php官方的说明: https://php.net/manual/en/install.pecl.phpi…...

视频网站如何做引流/手机百度app最新版下载

兄弟姐妹们好,又是好久没有更新了,今天给大家简单介绍代理模式,一个很简单的设计模式,旨在不改变原对象的情况下通过代理对象来控制对原对象的访问。代理模式根据具体情况还可以分为远程代理、虚拟代理、保护代理等,下…...

lol做视频那个网站好/百度投诉热线中心客服

时间函数select curdate(); 返回2014-09-12,不包含时分秒select curtime(); 返回14:13:22,不包含年月日select now(); 返回2014-09-12 10:46:17select unix_timestamp(now());unix_timestamp(date)返回date的UNIX时间戳 select unix_timestamp(2013-09-01)…...

科讯cms怎么做网站地图/关键词优化排名哪家好

目录前言第一种解法:先排序再找第二种解法:线性布局总结前言 这题太简单了,就是从数组里找出三个数乘积最大项目网址:https://leetcode-cn.com/problems/maximum-product-of-three-numbers/ 第一种解法:先排序再找 …...

贵州省网站建设/上海最新新闻事件今天国内

LM3S之boot loader学习笔记-1 彭会锋 (首先声明,此系列文章编写参考了很多资料,其中一些内容是原版内容的引用和整理,并加入了一些自己的见解,我已经尽量标明引用部分,如有未全部标注部分,还望见…...

分析网站做的好坏/seo入门培训学多久

1.新建访问的控制器动作返回视图,在视图中使用easyui的treegrid插件来得到后台得到的json数据显示多级菜单 public ActionResult Menu(){return View();} View Code视图: {ViewBag.Title "Menu";Layout "~/Views/Shared/_GridView.csht…...