超详细:Redis分布式锁
如何基于 Redis 实现一个最简易的分布式锁?
不论是本地锁还是分布式锁,核心都在于“互斥”。
在 Redis 中, SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent 方法),如果 key 不存在的话,才会设置 key 的值。如果 key 已经存在,SETNX 啥也不做。
SETNX lockKey uniqueValue
(integer) 1
SETNX lockKey uniqueValue
(integer) 0
释放锁的话,直接通过 DEL命令删除对应的 key 即可。
DEL lockKey
(integer) 1
为了防止误删到其他的锁,这里我们建议使用 Lua 脚本通过 key 对应的 value(唯一值)来判断。
选用 Lua 脚本是为了保证解锁操作的原子性。因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,从而保证了锁释放操作的原子性。
// 释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end
这是一种最简易的 Redis 分布式锁实现,实现方式比较简单,性能也很高效。不过,这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉,可能会导致锁无法被释放,进而造成共享资源无法再被其他线程/进程访问。
为什么要给锁设置一个过期时间?
为了避免锁无法被释放,我们可以想到的一个解决办法就是:给这个 key(也就是锁) 设置一个过期时间 。
127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX
OK
- lockKey:加锁的锁名;
- uniqueValue:能够唯一标识锁的随机字符串;
- NX:只有当 lockKey 对应的 key 值不存在的时候才能 SET 成功;
- EX:过期时间设置(秒为单位)EX 3 标示这个锁有一个 3 秒的自动过期时间。与 EX 对应的是 PX(毫秒为单位),这两个都是过期时间设置。
一定要保证设置指定 key 的值和过期时间是一个原子操作!!! 不然的话,依然可能会出现锁无法被释放的问题。
这样确实可以解决问题,不过,这种解决办法同样存在漏洞:如果操作共享资源的时间大于过期时间,就会出现锁提前过期的问题,进而导致分布式锁直接失效。如果锁的超时时间设置过长,又会影响到性能。
你或许在想:如果操作共享资源的操作还未完成,锁过期时间能够自己续期就好了!
如何实现锁的优雅续期?
对于 Java 开发的小伙伴来说,已经有了现成的解决方案: Redisson。
Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。并且,Redisson 还支持 Redis 单机、Redis Sentinel、Redis Cluster 等多种部署架构。
Redisson 中的分布式锁自带自动续期机制,使用起来非常简单,原理也比较简单,其提供了一个专门用来监控和续期锁的 Watch Dog( 看门狗),如果操作共享资源的线程还未执行完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。
看门狗名字的由来于 getLockWatchdogTimeout() 方法,这个方法返回的是看门狗给锁续期的过期时间,默认为 30 秒。
//默认 30秒,支持修改
private long lockWatchdogTimeout = 30 * 1000;public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {this.lockWatchdogTimeout = lockWatchdogTimeout;return this;
}
public long getLockWatchdogTimeout() {return lockWatchdogTimeout;
}
renewExpiration() 方法包含了看门狗的主要逻辑:
private void renewExpiration() {//......Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {//......// 异步续期,基于 Lua 脚本CompletionStage<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) {// 无法续期log.error("Can't update lock " + getRawName() + " expiration", e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// 递归调用实现续期renewExpiration();} else {// 取消续期cancelExpirationRenewal(null);}});}// 延迟 internalLockLeaseTime/3(默认 10s,也就是 30/3) 再调用}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}
默认情况下,每过 10 秒,看门狗就会执行续期操作,将锁的超时时间设置为 30 秒。看门狗续期前也会先判断是否需要执行续期操作,需要才会执行续期,否则取消续期操作。
Watch Dog 通过调用 renewExpirationAsync() 方法实现锁的异步续期:
protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,// 判断是否为持锁线程,如果是就执行续期操作,就锁的过期时间设置为 30s(默认)"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return 1; " +"end; " +"return 0;",Collections.singletonList(getRawName()),internalLockLeaseTime, getLockName(threadId));
}
可以看出, renewExpirationAsync 方法其实是调用 Lua 脚本实现的续期,这样做主要是为了保证续期操作的原子性。
我这里以 Redisson 的分布式可重入锁 RLock
为例来说明如何使用 Redisson 实现分布式锁:
// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();
只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制。
// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制
lock.lock(10, TimeUnit.SECONDS);
如果使用 Redis 来实现分布式锁的话,还是比较推荐直接基于 Redisson 来做的。
如何实现可重入锁?
所谓可重入锁指的是在一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法即可重入 ,而无需重新获得锁。像 Java 中的 synchronized 和 ReentrantLock 都属于可重入锁。
不可重入的分布式锁基本可以满足绝大部分业务场景了,一些特殊的场景可能会需要使用可重入的分布式锁。
可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。
Redis 如何解决集群情况下分布式锁的可靠性?
为了避免单点故障,生产环境下的 Redis 服务通常是集群化部署的。
Redis 集群下,上面介绍到的分布式锁的实现会存在一些问题。由于 Redis 集群数据同步到各个节点时是异步的,如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。
针对这个问题,Redis 之父 antirez 设计了 Redlock 算法 来解决。
Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。
即使部分 Redis 节点出现问题,只要保证 Redis 集群中有半数以上的 Redis 节点可用,分布式锁服务就是正常的。
Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
Redlock 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。
实际项目中不建议使用 Redlock 算法,成本和收益不成正比,可以考虑基于 Redis 主从复制+哨兵模式实现分布式锁。
相关文章:

超详细:Redis分布式锁
如何基于 Redis 实现一个最简易的分布式锁? 不论是本地锁还是分布式锁,核心都在于“互斥”。 在 Redis 中, SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent 方法),如果 key 不存在…...

Vue与React的Suspense组件对比
在Vue和React中都内置了Suspense组件,该组件用于处理异步组件加载。当Suspense包裹的实际组件内容尚未加载完成时会先展示后备内容,等待组件内容加载完成后再切换成实际组件内容。这可以显著提升用户体验,适用于大数据加载、组件懒加载等场景…...

Spring框架深度剖析:特性、安全与优化
文章目录 Spring框架简介主要特性1. 依赖注入(Dependency Injection, DI)2. 面向切面编程(Aspect-Oriented Programming, AOP)3. 声明式事务管理4. 强大的MVC框架5. 集成测试支持6. 多种数据访问技术的支持 安全性1. 认证…...

硬盘文件误删:全面解析、恢复方案与预防策略
一、硬盘文件误删现象概述 在日常使用电脑的过程中,硬盘文件误删是许多用户都曾遇到过的问题。这种意外的数据丢失,不仅可能让我们辛苦编辑的文档、珍贵的照片和视频等瞬间消失,还可能对工作和生活造成重大影响。硬盘文件误删,如…...

tcpdump抓包 wireShark
TCPdump抓包工具介绍 TCPdump,全称dump the traffic on anetwork,是一个运行在linux平台可以根据使用者需求对网络上传输的数据包进行捕获的抓包工具。 tcpdump可以支持的功能: 1、在Linux平台将网络中传输的数据包全部捕获过来进行分析 2、支持网络层…...

Android system_server进程
目录 一、system_server进程介绍 二、system_server进程启动流程 2.1 startBootstrapServices 2.2 startCoreServices 2.3 startOtherServices 2.4 startApexServices 三、如何使用系统服务 3.1 app进程调用系统服务 3.2 native进程调用系统服务 3.3 system_server进…...

Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
1、前言 在 Vue 3 项目中结合 vue-i18n 和 Element Plus 实现中英文切换是一个常见的需求。下面是一个详细的步骤指南,帮助你完成这个任务。 安装引入 1. 安装依赖 首先,你需要安装 vue-i18n 和 Element Plus。 npm install vue-i18nnext element-p…...

python实现猜数字游戏( 可视化easygui窗口版本 )
1.先上源代码 import random import easygui as egdef guess_ordinary():answer random.randint(0, 11)user_answer int(eg.enterbox(msg "请在0-10中选择一个整数: ", title "猜数字"))if user_answer answer:eg.msgbox(msg "恭喜你ÿ…...

自由学习记录(23)
Lua的学习 table.concat(tb,";") 如果表里带表,则不能拼接,表里带nil也不能,都会报错 true和false也不可以,数字和字符串可以 if要和一个end配对,所以 if a>b then return true end end 两个end …...

Java语言程序设计 选填题知识点总结
第一章 javac.exe是JDK提供的编译器public static void main (String args[])是Java应用程序主类中正确的main方法Java源文件是由若干个书写形式互相独立的类组成的Java语言的名字是印度尼西亚一个盛产咖啡的岛名Java源文件中可以有一个或多个类Java源文件的扩展名是.java如果…...

鸿蒙生态:开发者的新蓝海与挑战
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...

4.3 MySQL 存储函数
存储函数是一种数据库对象,允许用户将常用的 SQL 逻辑封装为可复用的函数,通过调用函数完成特定的计算或业务逻辑。 1. 简介 1.1 什么是存储函数 存储函数(Stored Function)是用户定义的一段 SQL 逻辑,返回一个值&am…...

【Python刷题】动态规划相关问题
动态规划(Dynamic Programming,简称 DP)是一种用于解决多阶段决策最优化问题的算法策略。它通过把原问题分解为相对简单的子问题,记录子问题的解(通常使用表格等数据结构存储),避免重复计算&…...

2024年9月中国电子学会青少年软件编程(Python)等级考试试卷(六级)答案 + 解析
一、单选题 1、下面代码运行后出现的图像是?( ) import matplotlib.pyplot as plt import numpy as np x np.array([A, B, C, D]) y np.array([30, 25, 15, 35]) plt.bar(x, y) plt.show() A. B. C. D. 正确答案:A 答案…...

论文阅读:SIMBA: single-cell embedding along with features
Chen, H., Ryu, J., Vinyard, M.E. et al. SIMBA: single-cell embedding along with features. Nat Methods 21, 1003–1013 (2024). 论文地址:https://doi.org/10.1038/s41592-023-01899-8 代码地址:https://github.com/pinellolab/simba. 摘要 大多…...

d3-quadtree 的属性、方法、示例
D3.js 的 d3-quadtree 模块提供了用于构建二维空间索引的数据结构,即四叉树(Quadtree)。四叉树可以高效地存储和查询大量点数据。下面列出了 d3-quadtree 的主要属性和方法,并提供了一个简单的 Vue 组件示例,展示如何使…...

初次体验加猜测信息安全管理与评估国赛阶段训练习
[第一部分] 网络安全事件响应 window操作系统服务器应急响应流程_windows 服务器应急响应靶场_云无迹的博客-CSDN博客 0、请提交攻击者攻击成功的第一时间,格式:YY:MM:DD hh:mm:ss1、请提交攻击者的浏览器版本2、请提交攻击者目录扫描所使用的工具名称…...

在WSUS中删除更新
WSUS中更新的管理逻辑 如果你探索过WSUS控制台界面,就会发现WSUS只给你提供了批准(Approve)和拒绝(Decline)更新的选项,并无办法删除更新。 如果你去WSUS服务器清理导向(WSUS Server Cleanup …...

5分钟轻松搭建Immich图片管理软件并实现公网远程传输照片
文章目录 前言1.关于Immich2.安装Docker3.本地部署Immich4.Immich体验5.安装cpolar内网穿透6.创建远程链接公网地址7.使用固定公网地址远程访问 前言 本篇文章介绍如何在本地搭建lmmich图片管理软件,并结合cpolar内网穿透实现公网远程访问到局域网内的lmmich&#…...

数据集-目标检测系列- 昙花(昙花一现) 检测数据集 epiphyllum >> DataBall
数据集-目标检测系列- 昙花(昙花一现) 检测数据集 epiphyllum >> DataBall DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 贵在坚持! 数据样例项目地址: * 相关…...

开源POC库推荐
声明 学习视频来自 B 站UP主泷羽sec,如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。 ✍🏻作者简介:致…...

vue3项目部署在阿里云轻量应用服务器上
文章目录 概要整体部署流程技术细节小结 概要 vue3前端项目部署在阿里云轻量服务器 整体部署流程 首先有一个Vue3前端项目和阿里云应用服务器 确保环境准备 如果是新的服务器,在服务器内运行以下命令更新软件包 sudo apt update && sudo apt upgrade -y …...

javascrip页面交互
元素的三大系列 offset系列 offset初相识 offset系列属性 作用 element.offsetParent 返回作为该元素带有定位的父级元素,如果父级没有定位,则返回body element.offsetTop 返回元素相对于有定位父元素上方的偏移量 element.offsetLeft 返回元素…...

【U盘车载音乐】某宝198的3068首车载专用音乐合集【高音质】24G
「【U盘车载音乐】某宝198的3068首车载专用音乐合集【高音质】24G」 复制下方口令,打开最新版「夸克APP」即可获取保存(防止和谐!!!) 口令: 动作懿范鉴真渡多好备用口令: /~19dc35…...
【论文阅读】WGSR
0. 摘要 0.1. 问题提出 1.超分辨率(SR)是一个不适定逆问题,可行解众多。 2.超分辨率(SR)算法在可行解中寻找一个在保真度和感知质量之间取得平衡的“良好”解。 3.现有的方法重建高频细节时会产生伪影和幻觉,模型区分图像细节与伪影仍是难题。 0.2. …...

打造智能化在线教育平台详解:教培网校APP的架构设计与实现
本篇文章,小编将以教培网校APP的架构设计与实现为核心,深入探讨如何打造一套智能化的在线教育平台,为企业和教育机构提供落地参考。 一、在线教育平台的核心功能需求 构建一个高效的教培网校APP,首先需要明确其核心功能需求。一…...

使用同一个链接,如何实现PC打开是web应用,手机打开是一个H5应用
当我们希望通过同一个 URL,根据访问设备展示不同的页面时,可以选择以下几种方法: 方法一:通过 User-Agent 前端判断设备类型并跳转 利用前端 JavaScript 获取浏览器的 User-Agent 字符串,判断设备类型,跳转…...

STM32-- 调试 -日志输出
在调试嵌入式程序时,输出日志是非常重要的环节,可以帮助开发者定位问题、监控程序状态和性能。以下是几种常见的日志输出方式及其适用场景: 1. 使用串口(UART)输出日志 实现方式: 通过串口将日志输出到主…...

Python爬虫案例八:抓取597招聘网信息并用xlutils进行excel数据的保存
excel保存数据的三种方式: 1、pandas保存excel数据,后缀名为xlsx; 举例: import pandas as pddic {姓名: [张三, 李四, 王五, 赵六],年龄: [18, 19, 20, 21],住址: [广州, 青岛, 南京, 重庆] } dic_file pd.DataFrame(dic) dic_file…...

小试牛刀-Anchor安装和基础测试
目录 一、编写目的 二、安装步骤 2.1 安装Rust 设置rustup镜像 安装Rust 2.2 安装node.js 2.3 安装Solana-CLI 2.4 安装Anchor CLI 三、Program测试 四、可能出现的问题 Welcome to Code Blocks blog 本篇文章主要介绍了 [Anchor安装和基础测试] 博主广交技术好友&…...