Redis实现方式开启新篇章,解决分布式环境下的资源竞争问题,提升系统稳定性
前言
分布式锁一般有三种实现方式:
- 数据库乐观锁;
- 基于Redis的分布式锁;
- 基于ZooKeeper的分布式锁
本篇博客将介绍第二种方式,基于Redis实现分布式锁。
虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。
可靠性
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
代码实现
组件依赖
首先我们要通过Maven引入Jedis开源组件,在pom.xml文件加入下面的代码:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
加锁代码
正确姿势
先展示代码,再带大家慢慢解释为什么这样实现:
可以看到,我们加锁就一行代码:
jedis.set(String key, String value, String nxxx, String expx, int time)
这个set()方法一共有五个形参:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?
原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。
requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:
- 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
- 已有锁存在,不做任何操作。
心细的童鞋就会发现了,我们的加锁代码满足我们可靠性里描述的三个条件。
首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。
其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。
最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
由于我们只考虑Redis单机部署的场景,所以容错性我们暂不考虑。
错误示例1
比较常见的错误示例就是使用jedis.setnx()和jedis.expire()组合实现加锁
代码如下:
setnx()方法作用就是SET IF NOT EXIST,expire()方法就是给锁加一个过期时间。
乍一看好像和前面的set()方法结果一样,然而由于这是两条Redis命令,不具有原子性,如果程序在执行完setnx()之后突然崩溃,导致锁没有设置过期时间。那么将会发生死锁。
网上之所以有人这样实现,是因为低版本的jedis并不支持多参数的set()方法。
错误示例2
这一种错误示例就比较难以发现问题,而且实现也比较复杂。实现思路:使用jedis.setnx()命令实现加锁,其中key是锁,value是锁的过期时间。
执行过程:
- 通过setnx()方法尝试加锁,如果当前锁不存在,返回加锁成功。
- 如果锁已经存在则获取锁的过期时间,和当前时间比较,如果锁已经过期,则设置新的过期时间,返回加锁成功。
代码如下:
那么这段代码问题在哪里?
1. 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。
2. 当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但这个客户端的锁过期时间可能被其他客户端覆盖。
3. 锁不具备拥有者标识,即任何客户端都可以解锁。
解锁代码
正确姿势
还是先展示代码,再带大家慢慢解释为什么这样实现:
可以看到,我们解锁只需要两行代码就搞定了!
第一行代码,我们写了一个简单的Lua脚本代码,上一次见到这个编程语言还是在《黑客与画家》里,没想到这次居然用上了。
第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。
那么这段Lua代码的功能是什么呢?
其实很简单,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。
那么为什么要使用Lua语言来实现呢?
因为要确保上述操作是原子性的。关于非原子性会带来什么问题,可以阅读【解锁代码-错误示例2】
那么为什么执行eval()方法可以确保原子性,源于Redis的特性,下面是官网对eval命令的部分解释:
简单来说,就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
错误示例1
最常见的解锁代码就是直接使用jedis.del()方法删除锁,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。
public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
jedis.del(lockKey);
}
错误示例2
这种解锁代码乍一看也是没问题,甚至我之前也差点这样实现,与正确姿势差不多,唯一区别的是分成两条命令去执行
代码如下:
如代码注释,问题在于如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。
那么是否真的有这种场景?
答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
总结
本文主要介绍了如何使用Java代码正确实现Redis分布式锁,对于加锁和解锁也分别给出了两个比较经典的错误示例。
其实想要通过Redis实现分布式锁并不难,只要保证能满足可靠性里的四个条件。
相关文章:
Redis实现方式开启新篇章,解决分布式环境下的资源竞争问题,提升系统稳定性
前言 分布式锁一般有三种实现方式: 数据库乐观锁;基于Redis的分布式锁;基于ZooKeeper的分布式锁 本篇博客将介绍第二种方式,基于Redis实现分布式锁。 虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实…...
Go命令行参数操作:os.Args、flag包
Go命令行参数操作:os.Args、flag包 最近在写项目时,需要用到命令行传入的参数,正好借此机会整理一下。 1 os.Args:程序运行时,携带的参数(包含exe本身) package mainimport ("fmt"&q…...
在Go中处理时间数据
获取时间 这可能是软件包中最常用的方法。我们如何知道当前的时间呢?像这样: t : time.Now() fmt.Println(t)ini复制代码2023-04-16 23:54:45.924965 0800 CST m0.000152293这就是当前的时间。这是很麻烦的,所以我将把它分解成几个部分。 …...
SOLIDWORKS PDM 2024数据管理5大新功能
1. 改进的视觉内容 • 通过装配体可视化功能,在 SOLIDWORKS 中以图形方式查看零部件数据,如工作流程状态。• 使用特定图标迅速识别焊件切割清单零部件。 优点:重要数据和系统信息一目了然。 2.增强的数据保护和跟踪功能 •保护“包含”和…...
5G与医疗:开启医疗技术的新篇章
5G与医疗:开启医疗技术的新篇章 随着5G技术的快速发展和普及,它已经在医疗领域产生了深远的影响。5G技术为医疗行业提供了更高效、更准确、更及时的通信方式,从而改变了医疗服务的模式和患者的体验。本文将探讨5G技术在医疗领域的应用场景、优…...
Linux云服务器限制ip进行ssh远程连接
对Linux云服务器限制IP进行SSH远程连接的原因主要有以下几点: 增加安全性:SSH是一种加密的网络传输协议,可以保护数据的机密性和完整性。通过限制SSH连接的IP地址,可以防止未经授权的访问和数据泄露。只有拥有访问权限的IP地址才…...
【100天精通Python】Day72:Python可视化_一文掌握Seaborn库的使用《二》_分类数据可视化,线性模型和参数拟合的可视化,示例+代码
目录 1. 分类数据的可视化 1.1 类别散点图(Categorical Scatter Plot) 1.2 类别分布图(Categorical Distribution Plot) 1.3 类别估计图(Categorical Estimate Plot) 1.4 类别单变量图(Cat…...
(二开)Flink 修改源码拓展 SQL 语法
1、Flink 扩展 calcite 中的语法解析 1)定义需要的 SqlNode 节点类-以 SqlShowCatalogs 为例 a)类位置 flink/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/dql/SqlShowCatalogs.java 核心方法: Override pu…...
java中spi与api的区别
近期看了很多开源组件的源码,发现很多地方地方用到了 spi 的功能,开始思考 spi 与 api 的区别 发现 spi 侧重于抽象层次的概念,目前接触到的就是 java 里大量用到了这个,通过定义的接口来抽象通用的功能,然而 api 是不…...
【Android知识笔记】插件化专题(二)
在上一篇专题【Android知识笔记】插件化专题(一) 中详细介绍了Android三种插件化方案的实现以及它们的优缺点对比总结等。这一篇中主要来看一下一些插件化开源框架的实现原理,当然市场上的插件化框架有很多,层出不穷,如 DiDi VirtualApk、360 Replugin 等。本人在过去的工…...
赶紧收藏!史上最全IDEA快捷键大全
参考 IntelliJ IDEA 的官网,列举出了IntelliJ IDEA(Windows 版)的所有快捷键。 建议收藏,有需要的时候根据关键字来查找! idea专业版获取 kdocs.cn/l/ctYoaM6evJkl 该快捷键共分 16 类,可以方便的按各类…...
IntelliJ IDEA 把package包展开和压缩
想要展开就把对勾取消,想要压缩就勾上...
Python——自动创建文件夹
文章目录 前言一、判断文件夹或者文件是否存在二、创建一级文件夹三、创建多级文件夹四、代码封装前言 利用 Python编程语言实现自动创建文件夹,程序以函数形式封装,直接按要求传参即可调用。 在python中没有直接针对文件夹的操作方法,可以借助模块os,os.path和shutil来操作…...
Leetcode—21.合并两个有序链表【简单】
2023每日刷题(十三) Leetcode—21.合并两个有序链表 直接法实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* mergeTwoLists(struct ListNode* list1, struct…...
数据链路层和DNS之间的那些事~
数据链路层,考虑的是两个节点之间的传输。这里面的典型协议也很多,最知名的就是“以太网”。我们本篇主要介绍的就是以太网协议。这个协议规定了数据链路层,也规定了物理层的内容。 目录 以太网帧格式 帧头 载荷 帧尾 DNS 从输入URL到…...
Spring-声明式事务
声明式事务 一、简介1、准备工作2、测试 二、声明式事务概念1、编程式事务2、声明式事务3、基于注解的声明式事务1.测试无事务情况2.加入事务①Transactional注解标识的位置②事务属性:只读③事务属性:超时④事务属性:回滚策略⑤事务属性&…...
腾讯云轻量服务器地域选择教程,一篇文章就够了
腾讯云轻量应用服务器地域是指轻量服务器数据中心所在的地理位置,如上海、广州和北京等地域,如何选择地域?腾讯云百科txybk.com建议地域选择遵循就近原则,用户距离轻量服务器地域越近,网络延迟越低,速度就越…...
【斗罗二】王东升级三环,戴华斌挑衅,雨浩单手接鼎订下赌约
【侵权联系删除】【文/郑尔巴金】 深度爆料,《绝世唐门》第20集,一场瞩目的战斗即将爆发。王冬,这位一年级的强攻系班长,将与戴华斌进行一场激烈的较量。王冬拥有三大武魂,其中最为人们所熟知的是那光明女神蝶&#x…...
洛谷 B2135:单词替换
【题目来源】https://www.luogu.com.cn/problem/B2135【题目描述】 输入一个字符串,以回车结束(字符串长度 ≤200)。该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写。现需要将其中的某个单词替换…...
IDEA中application.properties文件中文乱码
现象: 原因: 项目编码格式与IDEA编码格式不一致导致的 解决办法: 在File->Settings->Editor->File Encodings选项中,将Global Encoding,Project Encoding,Default encoding for properties files这三个选项置为一致&a…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...
