分布式锁之redis6
一、分布式锁介绍
之前我们都是使用本地锁(synchronize、lock等)来避免共享资源并发操作导致数据问题,这种是锁在当前进程内。
那么在集群部署下,对于多个节点,我们要使用分布式锁来避免共享资源并发操作导致数据问题,虽然还是锁,但是是多个进程共用的锁标记,可以用Redis、Zookeeper、Mysql等都可以实现。
案例:优惠券领劵限制张数、商品库存超卖。
我们设计分布式锁应该要考虑的东西:
-
排他性:在分布式应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
-
容错性:分布式锁一定能得到释放,比如客户端奔溃或者网络中断,可能会导致锁一直不被释放,从而导致死锁,我们可以设置锁的过期时间。
-
满足可重入、高性能、高可用(集群部署)。
-
注意分布式锁的开销、锁粒度。
二、分布式锁的实现
实现分布式锁可以用 Redis、Zookeeper、Mysql数据库这几种 , 性能最好的是Redis且是最容易理解。
分布式锁离不开 key - value 设置,key 是锁的唯一标识,一般按业务来决定命名,比如想要给一种优惠券活动加锁,key 命名为 “coupon:id” 。value就可以使用固定值,比如设置成1。
基于redis实现分布式锁:
(1)、加锁 setnx key value:
setnx 的含义就是 set if not exists,有两个参数 setnx(key, value),该方法是原子性操作,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。
(2)、解锁 del (key):
得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key)。
(3)、配置锁超时 expire (key,30s):
客户端奔溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。
综合的伪代码:
method(){String key = "coupon:id"
if(setnx(key,1) == 1){expire(key,30,TimeUnit.MILLISECONDS)try {//做对应的业务逻辑//查询用户是否已经领券//如果没有则扣减库存//新增领劵记录} finally {del(key)}}else{
//睡眠100毫秒,然后自旋调用本方法method()}
}
三、 基于Redis实现分布式锁的几种坑
上面我们写的伪代码中有几个坑,我们分别来分析一下。
1、多个命令之间不是原子性操作,如setnx
和expire
之间,如果setnx
成功,但是expire
失败,且宕机了,则这个资源就是死锁。
解决方法:使用原子命令来设置和配置过期时间 setnx / setex,在java里面是
redisTemplate.opsForValue().setIfAbsent("key","value",30,TimeUnit.MILLISECONDS)
成功了返回true,失败了返回false。
2、业务超时,存在其他线程勿删,设置key30秒过期,假如线程A执行很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执行完成,而B还没执行完成,结果就是线程A删除了线程B加的锁,所以我们的value不能单单只是1。
解决方法:可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁, 那 value 应该是当前线程的标识或者uuid。
String key = "coupon:id"
String value = Thread.currentThread().getId()
if(setnx(key,value) == 1){expire(key,30,TimeUnit.MILLISECONDS)try {//做对应的业务逻辑} finally {//删除锁,判断是否是当前线程加的if(get(key).equals(value)){//还存在时间间隔del(key)}}
}else{//睡眠100毫秒,然后自旋调用本方法
}
3、进一步细化误删,当线程A获取到正常值value时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调用del方法,结果就是删除了新设置的线程B的值。
解决办法:由于redis没有相关的原子性api,所以采用 lua脚本+redis来实现多个命令的原子性。由于【判断和删除】是lua脚本执行,所以要么全成功,要么全失败。
总结:核心是保证多个指令原子性,加锁使用setnx setex 可以保证原子性,解锁采用 lua脚本+redis来保证原子性。
【判断和删除】的lua脚本:
//获取lock的值和传递的值一样,调用删除操作返回1,否则返回0
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//Arrays.asList(lockKey)是key列表,uuid是参数
Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), uuid);
四、原生分布式锁的具体实现
@RestController
@RequestMapping("/api/v1/coupon")
public class CouponController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@GetMapping("add")public JsonData saveCoupon(@RequestParam (value = "coupon_id",required = true)int couponId){//防止其他线程误删String uuid = UUID.randomUUID().toString();String lockKey = "lock:coupon:" + couponId;lock(couponId,uuid,lockKey);return JsonData.buildSuccess();}private void lock(int couponId,String uuid,String lockKey){//lua脚本String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Boolean nativeLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,uuid, Duration.ofSeconds(30));System.out.println(uuid+"加锁状态:"+nativeLock);if(nativeLock){//加锁成功try{//TODO 做相关业务逻辑TimeUnit.SECONDS.sleep(10L);} catch (InterruptedException e) {} finally {//解锁Long result = stringRedisTemplate.execute( new DefaultRedisScript<>(script,Long.class), Arrays.asList(lockKey),uuid);System.out.println("解锁状态:"+result);}}else {//自旋操作try {System.out.println("加锁失败,睡眠5秒 进行自旋");TimeUnit.MILLISECONDS.sleep(5000);} catch (InterruptedException e) { }//睡眠一会再尝试获取锁lock(couponId,uuid,lockKey);}}}
运行结果:
d124ae03-5de6-4e25-82b8-fb0b30d7c7fc加锁状态:true
54041d23-ab3c-492e-977b-99c9b531534f加锁状态:false
加锁失败,睡眠5秒 进行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
54041d23-ab3c-492e-977b-99c9b531534f加锁状态:false
加锁失败,睡眠5秒 进行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
解锁状态:1
54041d23-ab3c-492e-977b-99c9b531534f加锁状态:true
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
解锁状态:1
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:true
解锁状态:1
相关文章:
分布式锁之redis6
一、分布式锁介绍 之前我们都是使用本地锁(synchronize、lock等)来避免共享资源并发操作导致数据问题,这种是锁在当前进程内。 那么在集群部署下,对于多个节点,我们要使用分布式锁来避免共享资源并发操作导致数据问题…...
数据框的添加
在地图制图中,地图全图显示的同时希望也能够显示局部放大图,以方便查看地物空间位置的同时,也能查看地物具体的相对位置。例如,在一个名为airport的数据集全图制图过程中,希望能附上机场区域范围的局部地图,…...
SQL Server 2022 读写分离问题整合
跟着热点整理一下遇到过的SQL Server的问题,这篇来聊聊读写分离遇到的和听说过的问题。 一、读写分离实现方法 1. 原生高可用方案 1.1 Always On 可用性组(推荐方案) 配置步骤: -- 1. 启用Always On功能 USE [master] GO ALT…...
启服云云端专利管理系统:解锁专利管理新境界
在当今竞争激烈的商业环境中,专利作为企业的核心资产,其管理的重要性不言而喻。启服云云端专利管理系统以其卓越的性能和独特的优势,成为企业专利管理的得力助手,为企业的创新发展保驾护航。 便捷高效,突破时空限制 启…...
记录一下零零散散的的东西-ImageNet
ImageNet 是一个非常著名的大型图像识别数据集, 数据集基本信息 内容说明📸 图像数量超过 1400万张图片(包含各类子集)🏷️ 类别数量常用的是 ImageNet-1K(1000类)🧑Ἶ…...
全连接RNN反向传播梯度计算
全连接RNN反向传播梯度计算 RNN数学表达式BPTT(随时间的反向传播算法)参数关系网络图L对V的梯度L对U的梯度L对W和b的梯度 RNN数学表达式 BPTT(随时间的反向传播算法) 参数关系网络图 L对V的梯度 L对U的梯度 L对W和b的梯度...
基于BusyBox构建ISO镜像
1. 准备 CentOS 7.9 3.10.0-957.el7.x86_64VMware Workstation 建议:系统内核<3.10.0 使用busybox < 1.33.2版本 2. 安装busybox # 安装依赖 yum install syslinux xorriso kernel-devel kernel-headers glibc-static ncurses-devel -y# 下载 wget https://…...
使用python完成手写数字识别
入门图像识别的第一个案例,看到好多小伙伴分享,也把自己当初的思路捋捋,写成一篇博客,作为记录和分享,也欢迎各位交流讨论。 实现思路 数据集:MNIST(包含60,000个训练样本和10,000个测试样本) 深度学习框架:Keras(基于TensorFlow) 模型架构:卷积神经网络(CNN) 实…...
Vue2 过滤器 Filters
提示:Vue 过滤器是用于格式化文本的 JavaScript 函数,通过管道符 | 在模板中使用 文章目录 前言过滤器的核心特性1. 链式调用2. 参数传递3. 纯函数特性 过滤器的常见使用场景1. 文本格式化2. 数字处理3. 日期/时间格式化4. 样式控制(结合组件…...
Java 大视界 -- 基于 Java 的大数据分布式存储在视频监控数据管理中的应用优化(170)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
c++中cin.ignore()的作用
在 C 中,cin.ignore() 是用于忽略(丢弃)输入流中的字符的函数,通常用来清除输入缓冲区中的残留内容(如换行符、多余输入等),以避免影响后续的输入操作。 基本用法 cin.ignore(n, delim);n&…...
讲一下resblock的跳跃连接,以及连接前后的shape保持(通过padding保持shape不变)
ResNet 残差块(ResBlock)的跳跃连接 & 形状保持 ResNet(Residual Network)通过 残差块(Residual Block, ResBlock) 解决了深度网络的梯度消失问题。其核心是跳跃连接(Skip Connection&…...
Unity中优化绘制调用整理
DrawCall 指的是 CPU 向 GPU 发送渲染指令的过程,在 Unity 中,每次渲染一个网格时,CPU 都需要向 GPU 发送一系列的渲染指令,这个过程被称为一次绘制调用(Draw Call)。 1.GPU实例化 使用: 2.绘…...
Coco-AI 支持嵌入,让你的网站拥有 AI 搜索力
在之前的实践中,我们已经成功地把 Hexo、Hugo 等静态博客和 Coco-AI 检索系统打通了:只要完成向量化索引,就可以通过客户端问答界面实现基于内容的智能检索。 这一层已经很好用了,但总觉得少了点什么—— 比如用户还得专门打开一…...
深入解析Java哈希表:从理论到实践
哈希表(Hash Table)是计算机科学中最重要的数据结构之一,也是Java集合框架的核心组件。本文将以HashMap为切入点,深入剖析Java哈希表的实现原理、使用技巧和底层机制。 一、哈希表基础原理 1. 核心概念 键值对存储:通…...
leetcode76.最小覆盖子串
思路源于 【小白都能听懂的算法课】【力扣】【LeetCode 76】最小覆盖子串|滑动窗口|字符串 初始化先创建t的哈希表记录t中的字符以及它出现的次数,t的have记录t中有几种字符 s的哈希表记录窗口中涵盖t的字符以及它出现的次数,初始…...
【HTB】Windwos-easy-Legacy靶机渗透
靶机介绍,一台很简单的WIndows靶机入门 知识点 msfconsole利用 SMB历史漏洞利用 WIndows命令使用,type查看命令 目录标题 一、信息收集二、边界突破三、权限提升 一、信息收集 靶机ip:10.10.10.4攻击机ip:10.10.16.26 扫描TC…...
一问讲透redis持久化机制-rdb aof
一问讲透redis持久化机制-rdb aof 文章目录 一问讲透redis持久化机制-rdb aof前言一、RDB二、AOF二、原理 前言 提示:这里可以添加本文要记录的大概内容: redis作为内存数据库,常常作为系统的缓存使用,但是内存是断电清空数据的…...
【大模型基础_毛玉仁】6.4 生成增强
目录 6.4 生成增强6.4.1 何时增强1)外部观测法2)内部观测法 6.4.2 何处增强6.4.3 多次增强6.4.4 降本增效1)去除冗余文本2)复用计算结果 6.4 生成增强 检索器得到相关信息后,将其传递给大语言模型以期增强模型的生成能…...
4.1-泛型编程深入指南
4.1 泛型编程深入指南 本节将带您深入探索C#泛型机制在游戏开发中的高级应用。通过游戏对象池、数据管理器、事件系统等实际案例,您将学习如何运用泛型构建高效、灵活的游戏系统。掌握这些知识将帮助您开发出性能更好、架构更清晰的游戏。 前置知识 在学习本节内容…...
《K230 从熟悉到...》识别机器码(AprilTag)
《K230 从熟悉到...》识别机器码(aprirltag) tag id 《庐山派 K230 从熟悉到...》 识别机器码(AprilTag) AprilTag是一种基于二维码的视觉标记系统,最早是由麻省理工学院(MIT)在2008年开发的。A…...
操作系统(二):实时系统介绍与实例分析
目录 一.概念 1.1 分类 1.2 主要指标 二.实现原理 三.主流实时系统对比 一.概念 实时系统(Real-Time System, RTS)是一类以时间确定性为核心目标的计算机系统,其设计需确保在严格的时间约束内完成任务响应。 1.1 分类 根据时间约束的严…...
虚拟电商-话费充值业务(六)话费充值业务回调补偿
一、话费充值回调业务补偿 业务需求:供应商对接下单成功后充吧系统将订单状态更改为:等待确认中,此时等待供应商系统进行回调,当供应商系统回调时说明供应商充值成功,供应商回调充吧系统将充吧的订单改为充值成功&…...
加密解密工具箱 - 专业的在线加密解密工具
加密解密工具箱 - 专业的在线加密解密工具 您可以通过以下地址访问该工具: https://toolxq.com/static/hub/secret/index.html 工具简介 加密解密工具箱是一个功能强大的在线加密解密工具,支持多种主流加密算法,包括 Base64、AES、RSA、DES…...
印度股票实时数据API接口选型指南:iTick.org如何成为开发者优选
在全球金融数字化浪潮中,印度股票市场因其高速增长潜力备受关注。对于量化交易开发者、金融科技公司而言,稳定可靠的股票报价API接口是获取市场数据的核心基础设施。本文将深度对比主流印度股票API,并揭示iTick在数据服务领域的独特优势。 一…...
使用python实现视频播放器(支持拖动播放位置跳转)
使用python实现视频播放器(支持拖动播放位置跳转) Python实现视频播放器,在我早期的博文中介绍或作为资料记录过 Python实现视频播放器 https://blog.csdn.net/cnds123/article/details/145926189 Python实现本地视频/音频播放器https://bl…...
Python星球日记 - 第2天:数据类型与变量
🌟引言: 上一篇:Python星球日记 - 第1天:欢迎来到Python星球 名人说:莫听穿林打叶声,何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和…...
CyclicBarrier、Semaphore、CountDownLatch的区别,适用场景
CyclicBarrier、Semaphore 和 CountDownLatch 是 Java 并发包中用于线程协作的工具类,它们虽然都与线程同步相关,但设计目的和使用场景有显著差异。以下是它们的核心区别和典型应用场景: 1. CountDownLatch 核心机制 一次性计数器…...
【愚公系列】《高效使用DeepSeek》050-外汇交易辅助
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
java短连接,长连接
在网络通信中,短连接(Short Connection)是指客户端与服务器建立连接后,仅完成一次或几次数据交互就立即断开连接的通信方式。以下是关于短链接的详细说明: 一、短链接的核心特点 连接短暂 数据传输完成后立即关闭连接…...
从零开始训练Codebook:基于ViT的图像重建实践
完整代码在文末,可以一键运行。 1. 核心原理 Codebook是一种离散表征学习方法,其核心思想是将连续特征空间映射到离散的码本空间。我们的实现方案包含三个关键组件: 1.1 ViT编码器 class ViTEncoder(nn.Module):def __init__(self, codebo…...
每日一题洛谷P8664 [蓝桥杯 2018 省 A] 付账问题c++
P8664 [蓝桥杯 2018 省 A] 付账问题 - 洛谷 (luogu.com.cn) 思路:要使方差小,那么钱不能一下付的太多,可以让钱少的全付玩,剩下还需要的钱再让钱多的付(把钱少的补上)。 将钱排序,遍历一遍&…...
蓝桥杯真题——传送阵
原题连接:蓝桥杯2024年第十五届省赛真题-传送阵 - C语言网 知识点:并查集 题目描述 小蓝在环球旅行时来到了一座古代遗迹,里面并排放置了 n 个传送阵,进入第 i 个传送阵会被传送到第 ai 个传送阵前,并且可以随时选择…...
解释回溯算法,如何应用回溯算法解决组合优化问题?
一、回溯算法核心原理 回溯算法本质是暴力穷举的优化版本,采用"试错剪枝"策略解决问题。其核心流程如下: 路径构建:记录当前选择路径选择列表:确定可用候选元素终止条件:确定递归结束时机剪枝优化…...
opencv连接vs2015
需要改的地方: 1.debug x64 2.vc目录 包含目录:D:\softword\opencv\opencv3416\opencv\build\include 3.vc目录 库目录:D:\softword\opencv\opencv3416\opencv\build\x64\vc14\lib 4.链接器——输入:D:\softword\ope…...
用matlab搭建一个简单的图像分类网络
文章目录 1、数据集准备2、网络搭建3、训练网络4、测试神经网络5、进行预测6、完整代码 1、数据集准备 首先准备一个包含十个数字文件夹的DigitsData,每个数字文件夹里包含1000张对应这个数字的图片,图片的尺寸都是 28281 像素的,如下图所示…...
移动端六大语言速记:第6部分 - 错误处理与调试
移动端六大语言速记:第6部分 - 错误处理与调试 本文将对比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift这六种移动端开发语言在错误处理与调试方面的特性,帮助开发者理解和掌握各语言的异常处理机制。 6. 错误处理与调试 6.1 异常处理 各语言异常处理的语法对比:…...
【数据库】达梦arm64安装
话不多说,快速开始~ 1.下载 进入官网: 产品下载 | 达梦在线服务平台 下载安装包。 选飞腾、鲲鹏都可以,都是arm架构的。我选择的是: 直接下载地址是https://download.dameng.com/eco/adapter/DM8/202502/dm8_20250117_HWarm920…...
QTableWidget 中insertRow(0)(头插)和 insertRow(rowCount())(尾插)的性能差异
一、目的 在 Qt 的 QTableWidget 中,insertRow(0) (头插)和 insertRow(rowCount())(尾插)在性能上存在显著差异。 二、QAbstractItemModel:: insertRows 原文解释 QAbstractItemModel Class | Qt Core 5.15.18 AI 解…...
使用MFC ActiveX开发KingScada控件(OCX)
最近有个需求,要在KingScada上面开发一个控件。 原来是用的WinCC,WinCC本身是支持调用.net控件,就是winform控件的,winform控件开发简单,相对功能也更丰富。奈何WinCC不是国产的。 话说KingScada,国产组态软…...
大模型学习二:DeepSeek R1+蒸馏模型组本地部署与调用
一、说明 DeepSeek R1蒸馏模型组是基于DeepSeek-R1模型体系,通过知识蒸馏技术优化形成的系列模型,旨在平衡性能与效率。 1、技术路径与核心能力 基础架构与训练方法 DeepSeek-R1-Zero:通过强化学习(RL)训练&…...
通过 Markdown 改进 RAG 文档处理
通过 Markdown 改进 RAG 文档处理 作者:Tableau 原文地址:https://zhuanlan.zhihu.com/p/29139791931 通过 Markdown 改进 RAG 文档处理https://mp.weixin.qq.com/s/LOBOKNA71dANXHuwxe7yxw 如何将 PDF 转换为 Markdown 以获得更好的 LLM RAG 结果 Mar…...
Java学习总结-IO流
什么IO流? 以内存为主体。input:磁盘向内存输入内容。output:内存向磁盘输入内容。 IO流的分类:...
python发送qq邮件
1.发送邮件的前提是你的qq邮箱设置能够用程序访问 这个服务点打开 就在 设置->账号 中 可以找到 # 导入 smtplib 库,用于实现 SMTP 协议,可实现邮件的发送功能 import smtplib # 从 email.mime.multipart 模块导入 MIMEMultipart 类,用…...
使用Deployment运行无状态应用
使用Deployment运行无状态应用 文章目录 使用Deployment运行无状态应用[toc]一、工作负载资源与控制器二、ReplicationController、ReplicaSet和Deployment1. ReplicationController(已淘汰)2. ReplicaSet(ReplicationController 的增强版&am…...
QT Quick(C++)跨平台应用程序项目实战教程 6 — 弹出框
目录 1. Popup组件介绍 2. 使用 上一章内容完成了音乐播放器程序的基本界面框架设计。本小节完成一个简单的功能。单击该播放器顶部菜单栏的“关于”按钮,弹出该程序的相关版本信息。我们将使用Qt Quick的Popup组件来实现。 1. Popup组件介绍 Qt 中的 Popup 组件…...
Design Compiler:库特征分析(ALIB)
相关阅读 Design Compilerhttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 简介 在使用Design Compiler时,可以对目标逻辑库进行特征分析,并创建一个称为ALIB的伪库(可以被认为是缓存)&…...
2025高频面试设计模型总结篇
文章目录 设计模型概念单例模式工厂模式策略模式责任链模式 设计模型概念 设计模式是前人总结的软件设计经验和解决问题的最佳方案,它们为我们提供了一套可复用、易维护、可扩展的设计思路。 (1)定义: 设计模式是一套经过验证的…...
41. 评论日记
越复杂的结构越脆弱,你不能因为有智驾有只能,你就全交给它了,手机永久了还发热呢,你全交给它那你要死了也怪不了谁。 这年头的手机基本都有防水,但是你天天拿着这个在泳池里玩,哪天炸了我都只能说炸的响炸的…...
Python第七章09:自定义python包.py
# 自定义python包# 从物理上看,包就是一个文件夹,在该文件夹下包含了一个_init_.py文件,该文件夹可用于包含多个模块文件 # 从逻辑上看,包的本质依然是模块 # _init_.py 标识python包,没有就是普通文件夹࿰…...