分布式锁简单实现
分布式锁
Redis分布式锁最简单的实现
想要实现分布式锁,必须要求 Redis 有「互斥」的能力,我们可以使用 SETNX 命令,这个命令表示SET if Not Exists,即如果 key 不存在,才会设置它的值,否则什么也不做。
两个客户端进程可以执行这个命令,达到互斥,就可以实现一个分布式锁。
客户端 1 申请加锁,加锁成功:
客户端 2 申请加锁,因为它后到达,加锁失败:

此时,加锁成功的客户端,就可以去操作「共享资源」,例如,修改 MySQL 的某一行数据,或者调用一个 API 请求。
操作完成后,还要及时释放锁,给后来者让出操作共享资源的机会。如何释放锁呢?
也很简单,直接使用 DEL 命令删除这个 key 即可,这个逻辑非常简单。

但是,它存在一个很大的问题,当客户端 1 拿到锁后,如果发生下面的场景,就会造成「死锁」:
1、程序处理业务逻辑异常,没及时释放锁
2、进程挂了,没机会释放锁
这时,这个客户端就会一直占用这个锁,而其它客户端就「永远」拿不到这把锁了。怎么解决这个问题呢?
如何避免死锁?
我们很容易想到的方案是,在申请锁时,给这把锁设置一个「租期」。
在 Redis 中实现时,就是给这个 key 设置一个「过期时间」。这里我们假设,操作共享资源的时间不会超过 10s,那么在加锁时,给这个 key 设置 10s 过期即可:
SETNX lock 1 // 加锁
EXPIRE lock 10 // 10s后自动过期

这样一来,无论客户端是否异常,这个锁都可以在 10s 后被「自动释放」,其它客户端依旧可以拿到锁。
但现在还是有问题:
现在的操作,加锁、设置过期是 2 条命令,有没有可能只执行了第一条,第二条却「来不及」执行的情况发生呢?例如:
- SETNX 执行成功,执行EXPIRE 时由于网络问题,执行失败
- SETNX 执行成功,Redis 异常宕机,EXPIRE 没有机会执行
- SETNX 执行成功,客户端异常崩溃,EXPIRE也没有机会执行
总之,这两条命令不能保证是原子操作(一起成功),就有潜在的风险导致过期时间设置失败,依旧发生「死锁」问题。
在 Redis 2.6.12 之后,Redis 扩展了 SET 命令的参数,用这一条命令就可以了:
SET lock 1 EX 10 NX

锁被别人释放怎么办?
上面的命令执行时,每个客户端在释放锁时,都是「无脑」操作,并没有检查这把锁是否还「归自己持有」,所以就会发生释放别人锁的风险,这样的解锁流程,很不「严谨」!如何解决这个问题呢?
解决办法是:客户端在加锁时,设置一个只有自己知道的「唯一标识」进去。
例如,可以是自己的线程 ID,也可以是一个 UUID(随机且唯一),这里我们以UUID 举例:
SET lock $uuid EX 20 NX
之后,在释放锁时,要先判断这把锁是否还归自己持有,伪代码可以这么写:
if redis.get("lock") == $uuid:redis.del("lock")
这里释放锁使用的是 GET + DEL 两条命令,这时,又会遇到我们前面讲的原子性问题了。这里可以使用lua脚本来解决。
安全释放锁的 Lua 脚本如下:
if redis.call("GET",KEYS[1]) == ARGV[1]
thenreturn redis.call("DEL",KEYS[1])
elsereturn 0
end
好了,这样一路优化,整个的加锁、解锁的流程就更「严谨」了。
这里我们先小结一下,基于 Redis 实现的分布式锁,一个严谨的的流程如下:
1、加锁
SET lock_key $unique_id EX $expire_time NX
2、操作共享资源
3、释放锁:Lua 脚本,先 GET 判断锁是否归属自己,再DEL 释放锁
Java代码实现分布式锁
package com.msb.redis.lock;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 分布式锁的实现*/
@Component
public class RedisDistLock implements Lock {private final static int LOCK_TIME = 5*1000;private final static String RS_DISTLOCK_NS = "tdln:";/*if redis.call('get',KEYS[1])==ARGV[1] thenreturn redis.call('del', KEYS[1])else return 0 end*/private final static String RELEASE_LOCK_LUA ="if redis.call('get',KEYS[1])==ARGV[1] then\n" +" return redis.call('del', KEYS[1])\n" +" else return 0 end";/*保存每个线程的独有的ID值*/private ThreadLocal<String> lockerId = new ThreadLocal<>();/*解决锁的重入*/private Thread ownerThread;private String lockName = "lock";@Autowiredprivate JedisPool jedisPool;public String getLockName() {return lockName;}public void setLockName(String lockName) {this.lockName = lockName;}public Thread getOwnerThread() {return ownerThread;}public void setOwnerThread(Thread ownerThread) {this.ownerThread = ownerThread;}@Overridepublic void lock() {while(!tryLock()){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic void lockInterruptibly() throws InterruptedException {throw new UnsupportedOperationException("不支持可中断获取锁!");}@Overridepublic boolean tryLock() {Thread t = Thread.currentThread();if(ownerThread==t){/*说明本线程持有锁*/return true;}else if(ownerThread!=null){/*本进程里有其他线程持有分布式锁*/return false;}Jedis jedis = null;try {String id = UUID.randomUUID().toString();SetParams params = new SetParams();params.px(LOCK_TIME);params.nx();synchronized (this){/*线程们,本地抢锁*/if((ownerThread==null)&&"OK".equals(jedis.set(RS_DISTLOCK_NS+lockName,id,params))){lockerId.set(id);setOwnerThread(t);return true;}else{return false;}}} catch (Exception e) {throw new RuntimeException("分布式锁尝试加锁失败!");} finally {jedis.close();}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new UnsupportedOperationException("不支持等待尝试获取锁!");}@Overridepublic void unlock() {if(ownerThread!=Thread.currentThread()) {throw new RuntimeException("试图释放无所有权的锁!");}Jedis jedis = null;try {jedis = jedisPool.getResource();Long result = (Long)jedis.eval(RELEASE_LOCK_LUA,Arrays.asList(RS_DISTLOCK_NS+lockName),Arrays.asList(lockerId.get()));if(result.longValue()!=0L){System.out.println("Redis上的锁已释放!");}else{System.out.println("Redis上的锁释放失败!");}} catch (Exception e) {throw new RuntimeException("释放锁失败!",e);} finally {if(jedis!=null) jedis.close();lockerId.remove();setOwnerThread(null);System.out.println("本地锁所有权已释放!");}}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("不支持等待通知操作!");}}相关文章:
分布式锁简单实现
分布式锁 Redis分布式锁最简单的实现 想要实现分布式锁,必须要求 Redis 有「互斥」的能力,我们可以使用 SETNX 命令,这个命令表示SET if Not Exists,即如果 key 不存在,才会设置它的值,否则什么也不做。 …...
BM23 二叉树的前序遍历
public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * param root TreeNode类 * return int整型一维数组*/public void preorder(List<Integer> list,TreeNode root){if(root null)return;l…...
阿里云代理仓库地址
在天朝使用jcenter、mavenCentral及google三个远程仓库,Gradle Sync会很慢,google仓库甚至需要科学上网才能访问。为了加快Gradle Sync速度,一招教你优先用 阿里云仓库服务 的仓库作为下载源。 一劳永逸之道 将本项目的gradle/init.d/init.g…...
nginx的location规则与其他功能
1. nginx中location规则: 规则描述~表示执行一个正则匹配,区分大小写~*表示执行一个正则匹配,不区分大小写^~表示普通字符匹配,如果该选项匹配,只匹配该选项,不匹配别的选项,一般用来匹配目录进…...
用汇编进行字符串匹配
用汇编进行字符串匹配 2、试编写一程序,要求比较两个字符串 STRING1 和 STRING2 所含字符是否完全相同,若相同则显示 MATCH,若不相同则显示 NO MATCH。 .model small .dataSTRING1 db hello world!,0STRING2 db hello china!,0matchString d…...
回归预测 | Matlab基于SAO-BiLSTM雪融算法优化双向长短期记忆神经网络的数据多输入单输出回归预测
回归预测 | Matlab基于SAO-BiLSTM雪融算法优化双向长短期记忆神经网络的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SAO-BiLSTM雪融算法优化双向长短期记忆神经网络的数据多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于SAO-B…...
mysql数据库的索引管理
目录 一、索引的概述 1、索引的概念 2、索引的作用 3、索引的副作用 4、创建索引的原则依据 5、索引优化 6、索引的分类 7、数据文件与索引文件 二、管理数据库索引 1、查询索引 2、创建索引 2.1 创建普通索引 2.2 创建唯一索引 2.3 创建主键索引 2.4 创建组合…...
VUE+Vant实现H5组织架构选人选公司组件
提醒自己: 这是之前的逻辑,或许你重新写会有更好的方法,可以参考逻辑!!! 功能介绍 1.有面包屑点击切换 2.有公司、部门、人员 3.单选、多选实现 4.编辑/回显 5.使用随意切换层级和跳转到指定层级回显等功…...
【以图搜图】GPUNPU适配万物识别模型和Milvus向量数据库
目录 以图搜图介绍项目地址Milvuscv_resnest101_general_recognition 代码使用流程结果展示模型部署环境Milvus部署及使用docker安装docker-compose安装Milvus可视化工具Attu进入网页端 Data数据示例点个赞再走呗!比心💞️ 以图搜图 • 🤖 Mo…...
迷茫了!去大厂还是创业?
大家好,我是麦叔,最近我创建了一个 学习圈子 有球友在 星球 里提问。 大厂的layout岗位和小厂的硬件工程师岗位,该如何选择? 这个问题我曾经也纠结过,不过现在的我,I am awake! 肯定是有大点大。…...
Qt源码分析: QEventLoop实现原理
QEventLoop屏蔽了底层消息循环实现细节,向上提供了与平台无关的消息/事件循环。 本文拟对Windows系统下QEventLoop的实现原理予以分析。 注1:限于研究水平,分析难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 …...
痛失offer的八股
java面试八股 mysql篇: 事物的性质: 事物的性质有acid四特性。 a:automic,原子性,要么全部成功,要么全部失败,mysql的undolog,事物在执行的时候,mysql会进行一个快照读…...
【Git】第一课:Git的介绍
简介 什么是Git? Git是一个开源的分布式版本控制系统,用于跟踪代码的改变和协同开发。它最初由Linus Torvalds为了管理Linux内核开发而创建,现已成为开源软件开发中最流行的版本控制系统,没有之一。Git允许多人同时在不同的分支上工作&…...
知识蒸馏——深度学习的简化之道 !!
文章目录 前言 1、什么是知识蒸馏 2、知识蒸馏的原理 3、知识蒸馏的架构 4、应用 结论 前言 在深度学习的世界里,大型神经网络因其出色的性能和准确性而备受青睐。然而,这些网络通常包含数百万甚至数十亿个参数,使得它们在资源受限的环境下&…...
【爬虫】Selenium打开新tab页截图并关闭
如果说 你曾苦过我的甜 我愿活成你的愿 愿不枉啊 愿勇往啊 这盛世每一天 山河无恙 烟火寻常 可是你如愿的眺望 孩子们啊 安睡梦乡 像你深爱的那样 🎵 王菲《如愿》 在自动化测试和网页抓取中,Selenium WebDriver 是一个强大的工具&…...
09 事务和连接池
文章目录 properties文件连接池service层实现类dao层实现类dao层实现类 连接池类: 创建线程池静态常量,用于放连接。 创建Properties静态常量,用于解析properties文件 静态代码块中,解析properties文件,将解析结果用于创建连接池 …...
P4344 [SHOI2015] 脑洞治疗仪 线段树+二分
主要是维护一个连续区间,比较经典的题目,还要考虑一下二分的情况,否则很难处理,比较有难度。这里和序列操作一题的区别是不需要考虑1的个数,因为不需要取反。传送门https://www.luogu.com.cn/problem/P4344 #include&…...
解决大型语言模型中的幻觉问题:前沿技术的综述
大型语言模型中的幻觉问题及其解决技术综述 摘要 大型语言模型(LLM)如GPT-4、PaLM和Llama在自然语言生成能力方面取得了显著进步。然而,它们倾向于产生看似连贯但实际上不正确或与输入上下文脱节的幻觉内容,这限制了它们的可靠性和安全部署。随着LLM在…...
机器学习流程—AutoML
文章目录 机器学习流程—AutoMLAutoML工具Auto-SKLearnMLBoxTPOTRapidMinerPyCaretAuto-KerasH2OAutoML谷歌AutoML云Uber LudwigTransmogrifAIAutoGluonAutoWekaDataRobot...
Ubuntu 23.10 tar包安装和配置Elasticsearch kibana 7.13.3
目录 一、环境说明 二、准备工作 三、安装elasticsearch 3.1 安装elasticsearch 3.2 添加服务和设置开机启动 四、安装kibana 4.1. 安装kibana 4.2 添加服务和设置开机启动 出于工作需要,需要在Ubuntu 23.10系统上通过tar包方式安…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...
2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版
1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...
