【Redis知识点总结】(五)——Redis实现分布式锁
Redis知识点总结(五)——Redis实现分布式锁
- setnx
- setnx + expire
- setnx + expire + lua脚本
- set nx ex
- set nx ex + 随机值
- set nx ex + 随机值 + lua脚本
- set ex nx + 随机值 + lua脚本 + 锁续期
- Redisson
- RedLock
在Redis的众多应用场景中,分布式锁是Redis比较重要的一个应用场景,今天我们就来了解一下如何用Redis实现一个分布式锁。
setnx
Redis提供了一个setnx命令,它的作用是在给缓存设置值时,判断key是否存在,如果key已经存在,则不执行操作,只有当缓存中不存在指定key时,才设置该缓存。
使用setnx命令,就可以实现一个简单的分布式锁。我们指定一个特定的值作为setnx命令的key,然后当一个线程要获取分布式锁时,就要通过setnx命令在redis中设置该key,如果设置成功,代表获取分布式锁成功,如果设置失败,表示获取分布式锁失败,那么当前线程就要通过自旋加sleep的机制等待别的线程释放锁。
一个线程通过setnx命令获取到分布式锁后,通过del命令释放锁。释放锁之后,其他线程就可以再次尝试获取锁。

但是这种方案有一个严重的问题,就是当一个线程获取到锁后,在处理业务逻辑时发生了异常,或者直接宕机了,没能执行del命令释放锁,那么其他线程就会一直获取不到锁。
setnx + expire
单靠setnx命令实现的分布式锁,存在锁得不到释放的风险。因此,我们可以引入expire命令,给key设置一个过期时间。当一个线程执行setnx命令成功后,就通过expire命令给该key设置一个过期时间。假如该线程执行业务逻辑出错,没能执行del命令,那么等该key的过期时间到后,该key也会被失效清理,锁最终还是能得到释放的。

这样问题就解决了吗?
显然是没有的,因为setnx和expire是两条命令,是非原子性的,如果setnx执行成功,expire命令执行失败,那么锁还是得不到释放。
setnx + expire + lua脚本
setnx命令与expire命令的非原子性问题,可以通过lua脚本去解决,把setnx和expire两条命令包在一个lua脚本里面,就可以保证这两条命令同时成功或者同时失败,这就就具备了原子性。

除了使用lua脚本,Redis还可以通过一条命令实现setnx加expire两条命令的功能。
set nx ex
我们可以通过以下命令实现setnx命令加expire命令的功能,并且可以保证原子性:
set key value nx ex {过期时间}

这样,分布式锁得不到释放的问题就彻底解决了。
但是,这样就没问题了吗?显然不是的,还会出现以下这种情况:

线程1获取分布式锁成功,但是业务处理时间过长,锁早已过期。而线程2随后获取分布式锁成功,进行业务处理。然后这时候线程1处理完了,于是执行del命令释放锁,而此时它释放的是线程2加的锁,并且线程2还没有处理完。线程2的锁被线程1释放后,如果线程3到来,也获取了分布式锁,那么等线程2处理完毕,又会释放掉线程3获取的锁,以此类推,这个分布式锁就失效了。
set nx ex + 随机值
锁错误释放的问题,可以通过给value指定一个随机值来解决,实现方案是这样:

执行set命令时生成一个随机值作为value值,并且当前线程要保存该随机值。等到当前线程处理完业务逻辑要准备释放锁时,先拿着自己保存的随机值跟redis中的value比对一下,如果相等,才执行del命令释放锁,否则就不能执行del命令。
这样看似可以解决锁误删的问题,一般情况下也确实如此,但是在高并发场景下,有可能会出现以下这种情况:

线程1执行完业务逻辑后,判断自己的value随机值与redis中的value相等,正准备释放锁,此时正好锁过期了,然后线程2又刚好获取到了锁,然后线程1才执行del命令,那么线程2的锁又被误释放了。
这是低概率事件,非高并发场景其实不用考虑这个问题,但是在高并发场景,还是有可能出现的。问题的原因是if判断与del命令是两个操作,非原子性。
set nx ex + 随机值 + lua脚本
if判断与del命令的非原子性问题,可以通过lua脚本解决,把if判断与del命令用lua脚本包起来,就可以彻底解决锁误释放的问题。

现在获取锁与释放锁的操作都是原子性的,我们的分布式锁也逐步变得完善,但是还有一个问题,那就是锁过期,也就是业务处理还未结束,锁就过期的这种情况。
我们可以给锁设置一个较长的过期时间,但是我们无法百分之一百保证所有的业务处理都在锁过期前得到释放,如果在某种特殊情况下处理时间比平常的要长,锁还是过期了,那么还是有问题的。
set ex nx + 随机值 + lua脚本 + 锁续期
锁过期的解决办法,就是开一个“看门狗”线程,进行锁续期,这个线程会定时地不断的检测主线程释放处理完毕并且释放了锁,如果主线程还没有处理完毕,那么这个“看门狗”线程就会执行expire命令进行锁续期。

此时,我们的分布式锁就完美了。但是,这样太复杂了吧?我们为了实现一个分布式锁,居然要处理这么多问题,使得原本简单的代码变得非常复杂,那有没有一个开源框架,可以帮我们实现这一切,我们只需要简单的调用一下就完事了呢?
当然是有的,那就是使用Redisson的分布式锁。
Redisson
Redisson是一个用Java语言实现的用于操作Redis的客户端工具包,比起原生的Jedis工具包,Redisson是更好用,功能更强大的。
Redisson就自带了分布式锁的实现,把我们上面说到的获取锁的原子性、释放锁的原子性、利用看门狗线程进行锁续期等一系列的操作都包装起来,对外暴露了简单的获取锁和释放锁的方法,我们无需手动编码实现这复杂的获取锁与释放锁的逻辑,只要使用Redisson提供的分布式锁的方法,就可以实现分布式锁的功能。

使用Redisson,我们就可以轻松简单的实现分布式锁的功能,但是如果Redis是单节点的话,Redis宕机了,那么所有线程都无法获取到锁了。

解决Redis单节点的问题,自然是上多个Redis节点。但是上了多个Redis节点之后,分布式锁又怎么实现呢?
RedLock
Redis的作者提出了一个多redis节点的分布式锁实现方案,那就是RedLock。
首先,我们要启5个redis节点,但是这5个节点是独立的节点,互相之间没有主从关系,也没有组成集群。
当一个线程加锁时,需要按顺序的访问这五个节点,还是利用set nx ex或set nx px命令进行加锁。
当一个线程在至少3个以上的redis节点成功执行了set命令,那么就表示获取锁成功,此时该线程要计算锁的有效期,需要用锁的有效期减去加锁耗费的时间,如果还有剩余时间,表示获取锁成功,如果没有剩余时间了,那么表示获取锁失败。
如果一个线程获取锁失败,要按相反的顺序向所有redis节点执行del命令释放锁。

这个RedLock的功能,同样可以通过Redisson实现,只需要调用Redisson提供的简单的API方法即可,无需我们编写复杂的RedLock代码。
相关文章:
【Redis知识点总结】(五)——Redis实现分布式锁
Redis知识点总结(五)——Redis实现分布式锁 setnxsetnx expiresetnx expire lua脚本set nx exset nx ex 随机值set nx ex 随机值 lua脚本set ex nx 随机值 lua脚本 锁续期RedissonRedLock 在Redis的众多应用场景中,分布式锁是Redis比…...
CSS 绝对定位 position:absolute
什么是CSS绝对定位absolute定位? 绝对定位absolute定位是CSS中的一种定位方式,可以将元素精确定位到一个确定的点,这与元素在文档流上的自然位置无关。相比起其他定位方式,绝对定位很灵活性,它可以将元素脱离文档流&am…...
鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:RelativeContainer)
相对布局组件,用于复杂场景中元素对齐的布局。 说明: 该组件从API Version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 规则说明 容器内子组件区分水平方向,垂直方向: 水平方向为left&…...
Android制作微信添加多个图片,放大图片
1.添加依赖 implementation com.github.bumptech.glide:glide:4.12.0 //裁剪图片等等 implementation androidx.recyclerview:recyclerview:1.1.0 //recycleview依赖 2.使用recycleview <androidx.recyclerview.widget.RecyclerViewandroid:id"id/recyclerView"…...
iOS runtime理解和应用场景
一、runtime的动态性 OC的运行时系统(Runtime System)提供了丰富的动态特性,包括类与对象的创建、消息发送与转发、方法的动态添加与替换、属性的动态合成等。通过使用运行时库提供的API,可以在运行时获取和操作类与对象的信息,实现各种动态性的功能。 我对 Runtime 的理…...
画图实战-Python实现某产品全年销量数据多种样式可视化
画图实战-Python实现某产品全年销量数据多种样式可视化 学习心得Matplotlib说明什么是Matplotlib?Matplotlib特性Matplotlib安装 产品订单量-折线图某产品全年订单量数据数据提取和分析绘制折线图 产品订单&销售额-条形图某产品全年订单&销售额数据绘制条形…...
YOLOv9详解
1.概述 在逐层进行特征提取和空间转换的过程中,会损失大量信息,例如图中的马在建模过程中逐渐变得模糊,从而影响到最终的性能。YOLOv9尝试使用可编程梯度信息PGI解决这一问题。 具体来说, PGI包含三个部分,࿰…...
CRON 定时任务
检测是否安装了 cron systemctl status crond 如果没有安装使用 sudo yum install cronie 编辑 crontab -e * * * * * php /path/your.php Esc键 然后输入 :q 退出 :wq 保存并退出 第一个 * 表示分钟,表示每分钟执行一次。第二个 * 表示小时,表示每…...
环境安装篇 之 Kind 搭建 kubernetes 测试集群
云原生学习路线导航页(持续更新中) 本文是 环境安装 系列文章,介绍 使用Kind工具 快速安装 kubernetes 测试集群的详细步骤 1.Kind简介 Kind 是一个使用 Docker 容器“节点”运行本地 Kubernetes 集群的工具。Kind 主要用于测试kubernetes本…...
每日五道java面试题之mybatis篇(四)
目录: 第一题. 映射器#{}和${}的区别第二题. 模糊查询like语句该怎么写?第三题. 在mapper中如何传递多个参数?第四题. Mybatis如何执行批量操作第五题 MyBatis框架适用场景 第一题. 映射器#{}和${}的区别 #{}是占位符,预编译处理;${}是拼接…...
camunda流程引擎的插件如何使用
camunda工作流引擎是一个开放的架构,除了流程引擎默认提供的功能外,开发者可以通过流程插件机制,对流程引擎功能进行扩展。即流程引擎插件是流程引擎配置的扩展。插件必须提供 ProcessEnginePlugin 接口的实现。 下面以全局任务事件监听器为…...
Vue打包问题汇总:legacy、runtime.js
问题一:Vue3.x的版本中build后dist文件中出现legacy的js文件 解决办法是添加兼容的浏览器 package.json "browserslist": ["> 1%","last 2 versions","not dead","not ie 11" ]参考 Vue3.x的版本中build后…...
挑战杯 车位识别车道线检测 - python opencv
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 深度学习 机器视觉 车位识别车道线检测 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🥇学长这里给一个题目综合评分(每项满分5分) …...
c++面经
1. 僵尸进程 僵尸进程(Zombie Process)在操作系统中指的是那些已经执行完毕,但其父进程尚未对其进行善后处理(例如读取子进程的状态信息或者执行回收资源的操作)的进程。在Unix和类Unix系统࿰…...
js中副作用的消除还解决了并行计算带来的竞争问题,具体是如何解决的
在JavaScript中,副作用是指对外部环境产生的可观察的变化,例如修改全局变量、修改DOM元素等。副作用的存在可能导致代码的可维护性和可测试性下降,并且在并行计算中可能引发竞争问题。 不纯的函数有可能访问同一块资源,如果先后调…...
3/14/24数据结构、线性表
目录 数据结构 数据结构三要素 逻辑结构 存储结构 数据运算 时间复杂度 空间复杂度 线性表 线性表定义 静态分配 动态分配 线性表插入 线性表删除 十天的时间学完了C语言督学课程,最后终于是可以投入到408的科目学习当中。关于数据结构和算法的学习很多部…...
软件测试面试200问,面试看这就够了。。。
🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 Part1 1、你的测试职业发展是什么? 测试经验越多,测试能力越高。所以我…...
力扣● 583. 两个字符串的删除操作 ● 72. 编辑距离 ● 编辑距离总结篇
● 583. 两个字符串的删除操作 注意审题: 给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 删除最少的字符使两者相同,说明留下来的就是最大公共子序列。不要求…...
Git速成
文章目录 Git 分布式版本控制工具课程内容1. 前言1.1 什么是Git1.2 使用Git能做什么 2. Git概述2.1 Git简介2.2 Git下载与安装 3. Git代码托管服务3.1 常用的Git代码托管服务3.2 码云代码托管服务3.2.1 注册码云账号3.2.2 登录码云3.2.3 创建远程仓库3.2.4 邀请其他用户成为仓库…...
一文看懂softmax loss
文章目录 softmax loss1.softmax函数2.交叉熵损失函数3.softmax loss损失函数(重点)4.带有temperature参数的softmax loss参考 softmax loss 1.softmax函数 softmax函数是一种常用的激活函数,通常用于多分类任务中。给定一个向量࿰…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
