003 redis分布式锁 jedis分布式锁 Redisson分布式锁 分段锁
文章目录
- Redis分布式锁原理
- 1.使用set的命令时,同时设置过期时间
- 2.使用lua脚本,将加锁的命令放在lua脚本中原子性的执行
- Jedis分布式锁实现
- pom.xml
- RedisCommandLock.java
- RedisCommandLockTest.java
- 锁过期问题
- 1乐观锁方式,增加版本号(增加版本号需要调整业务逻辑,与之配合,所以会入侵代码)
- 2watch do,自动延期(不会侵入业务代码,redisson就是采用这种方案)
- Redisson分布式锁
- 加锁解锁
- 锁重入
- 锁的存储结构
- Redisson加锁原理
- Redisson释放锁原理
- watch dog自动延期
- 分段锁
在同一个JVM内部,大家往往采用synchronized或者Lock的方式来解决多线程间的安全问题,但是在分布式架构下,在JVM之间,那么就需要一种更加高级的锁机制,来处理这种跨JVM进程之间的线程安全问题,解决方案就是:使用分布式锁。
Redis分布式锁原理
Redis分布式锁机制,主要借助setnx和expire两个命令完成
setnx:当key不存在,将key设置为value,存在不做任何操作,返回0
客户端如果宕机,锁谁也加不上,即死锁。当持有锁的客户端宕机时,它可能没有机会释放锁,导致其他客户端无法获取锁。
expire:设置key过期时间
原理解析:
1key不存在时创建,并设置value和过期时间,返回值为1;成功获取到锁
2如果key存在时直接返回0,抢锁失败
3持有锁的线程释放锁时,手动删除key;或者过期时间到,key自动删除,锁释放
加锁的问题
setnx成功
expire失败
如果没有手动释放,那么这个锁永远被占用,其他线程永远也抢不到锁
解决方案:
1.使用set的命令时,同时设置过期时间
命令:set lock ‘123’ EX 100 NX
SET lock_key unique_value NX PX 30000
NX 表示只有当 key 不存在时才设置它。
PX 30000 表示 key 的过期时间为 30,000 毫秒(即 30 秒)
如果正在使用较旧版本的 Redis,或者出于某种原因需要使用 SETNX,可以考虑结合 EXPIRE 命令来手动为 key 设置过期时间。但这种方法的一个缺点是,在 SETNX 和 EXPIRE 之间存在一个小的时间窗口,其中如果客户端宕机,可能会导致 key 没有设置过期时间。因此,使用 SET 命令的 NX 和 PX 选项是更安全和推荐的方法。
set 同时设置过期时间命令
set key value[EX seconds][PX milliseconds][NX|XX]EX seconds:设置失效时长,单位秒
PX milliseconds设置失效时长,单位毫秒
NX key不存在时设置value,成功返回OK,失败返回nil
XX key存在时设置value,成功返回OK,失败返回nil
2.使用lua脚本,将加锁的命令放在lua脚本中原子性的执行
EVAL:对Lua脚本进行求值,命令如下:
EVAL script numkeys key [key ...] arg [arg ...]>eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
>1) "key1" 2)"key2" 3)"first" 4)"second"
1script:参数是一段Lua5.1脚本程序,它会被运行在Redis服务器上下文中
2numkeys:参数用于指定键名参数的个数
这个命令的含义如下:
EVAL 是执行 Lua 脚本的命令。
“return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 是要执行的 Lua 脚本。这个脚本很简单,它只是返回一个包含四个元素的表(在 Lua 中,表是唯一的复合数据类型,类似于其他语言中的数组或字典)。这四个元素分别是脚本接收到的前两个 key 和前两个 argv。
2 是传递给 Lua 脚本的 key 的数量。这告诉 Redis,接下来的两个参数(key1 和 key2)应该被视为 key。
key1 和 key2 是传递给 Lua 脚本的两个 key。在 Lua 脚本中,它们可以通过 KEYS[1] 和 KEYS[2] 来访问。
first 和 second 是传递给 Lua 脚本的两个参数值。在 Lua 脚本中,它们可以通过 ARGV[1] 和 ARGV[2] 来访问。
所以,当你执行这个命令时,Lua 脚本会返回一个表,包含这四个值:key1, key2, first, second。
在Lua脚本中,可以使用redis.call()函数来执行Redis命令
#这段脚本实现了将键stock的值设为no
>eval "return redis.call('set',KEYS[1],ARGV[1])" 1 stock no
Jedis分布式锁实现
加锁:就是调用SET key PX NX命令
set key value [EX seconds] [PX milliseconds] [NX|XX]
key 加锁的key
value UUID.randomUUID().toString(),代表加锁的客户端请求标识
nxxx NX,表示SET IF NOT EXIST
expx PX,表示毫秒
pom.xml
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.4</version></dependency>
RedisCommandLock.java
package com.example.demo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.Jedis;import java.util.Collections;@Slf4j
@Data
@AllArgsConstructor
public class RedisCommandLock {private RedisTemplate redisTemplate;private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";/*** 尝试获取分布式锁* @param jedis Redis客户端* @param lockKey 锁* @param requestId 请求标识* @param expireTime 超期时间* @return 是否获取成功*/public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}private static final Long RELEASE_SUCCESS = 1L;/*** 释放分布式锁* @param jedis Redis客户端* @param lockKey 锁* @param requestId 请求标识* @return 是否释放成功*/public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));if (RELEASE_SUCCESS.equals(result)) {return true;}return false;}/*** 最常见的解锁代码就是直接使用 jedis.del() 方法删除锁,* 这种不先判断锁的拥有者而直接解锁的方式,* 会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。* @param jedis* @param lockKey*/public static void wrongReleaseLock1(Jedis jedis, String lockKey) {jedis.del(lockKey);}/*** 这种解锁代码乍一看也是没问题,与正确姿势差不多,唯一区别的是分成两条命令去执行,代码如下:* 它首先检查锁的拥有者(通过 requestId.equals(jedis.get(lockKey))),然后如果条件满足,它会删除锁。但是,这两个操作(get 和 del)是分开的,不是原子的。这意味着,在检查锁的拥有者和删除锁之间,其他客户端可能已经更改了锁的状态。** 具体来说,以下是一个可能的问题场景:** 客户端A获取了锁,并在某个时间点尝试释放它。* 客户端A调用 jedis.get(lockKey) 来检查它是否仍然拥有锁。假设它仍然拥有锁。* 在客户端A执行 jedis.del(lockKey) 之前,另一个客户端B可能已经获取了该锁(因为客户端A还没有释放它)。* 客户端A继续执行 jedis.del(lockKey),此时它实际上删除了客户端B刚刚获取的锁。* @param jedis* @param lockKey* @param requestId*/public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {// 判断加锁与解锁是不是同一个客户端if (requestId.equals(jedis.get(lockKey))) {// 若在此时,这把锁突然不是这个客户端的,则会误解锁jedis.del(lockKey);}}}
RedisCommandLockTest.java
package com.example.demo;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;import java.util.UUID;import static org.junit.jupiter.api.Assertions.*;@Slf4j
public class RedisCommandLockTest {private Jedis jedis;@BeforeEachvoid setUp() {jedis = new Jedis("127.0.0.1", 6379);
// jedis.auth("123456");}@Testpublic void testTryGetDistributedLock() {boolean result = RedisCommandLock.tryGetDistributedLock(jedis, "test:lock", UUID.randomUUID().toString(), 300000);System.out.println(result);}@Testpublic void testReleaseDistributedLock() {boolean result = RedisCommandLock.releaseDistributedLock(jedis, "test:lock", "3c8feabb-befd-4552-805f-6a78bc7c43b4");System.out.println(result);}}
锁过期问题
预估业务操作10秒,锁设置20秒,各种原因,比如STW问题,业务操作执行超过20秒,业务会在无锁状态下运行,就会发生数据紊乱
注:STW:Java中Stop-The-World机制简称STW,常发送于fullGC,这时Java应用程序的其他所有线程都被挂起(除了垃圾收集器之外)
1乐观锁方式,增加版本号(增加版本号需要调整业务逻辑,与之配合,所以会入侵代码)
2watch do,自动延期(不会侵入业务代码,redisson就是采用这种方案)
客户端1加锁的key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁
只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间
Redisson分布式锁
Redisson是基于Netty的Redis客户端。不但能操作原生的Redis数据结构,还为使用者提供了一系列具有分布式特性的常用工具类,实现了分布式锁。
Redis分布式锁和JUC的Lock方法相似。RLock接口继承了Lock接口
加锁解锁
@Testpublic void testLockDemo() {RLock disLock = client.getLock("DISLOCK");boolean isLock = false;try {disLock.lock(); //默认30s
// isLock = disLock.tryLock(20000, 1500000, TimeUnit.MILLISECONDS);System.out.println(isLock);if (isLock) {//TODO if get lock success, do something;Thread.sleep(15000);}} catch (Exception e) {} finally {// 无论如何, 最后都要解锁disLock.unlock();}}
锁重入
@Testpublic void testLockDemo2() {RLock disLock = client.getLock("DISLOCK");boolean isLock = false;try {isLock = disLock.tryLock(2000, 1500000, TimeUnit.MILLISECONDS);isLock = disLock.tryLock(2000, 1500000, TimeUnit.MILLISECONDS);isLock = disLock.tryLock(2000, 1500000, TimeUnit.MILLISECONDS);} catch (Exception e) {} finally {// 无论如何, 最后都要解锁disLock.unlock();disLock.unlock();disLock.unlock();}}
锁的存储结构
锁的结构是Hash
key:锁的名字
字段: UUID+threadId
值:表示重入的次数
Redisson加锁原理
RedissonLock的tryLockInnerAsync是Redisson加锁的关键方法
加锁
1.判断有没有"DISLOCK"
2.如果没有,设置UUID:1=1
3.设置它的过期时间
锁重入
1.KEY和字段都存在,锁重入
2.执行命令incrby UUID:1 1
3.结果: DISLOCK: {UUID:1 2}
锁互斥
1.客户端2进入
2.判断有KEY,没有字段
3.返回过期时间
4.客户端2自旋等待
1.key不存在,加锁设置字段=1,过段时间
2.key、字段都存在,锁重入,字段值+1
3.key存在,字段不存在,抢锁失败
Redisson释放锁原理
RedissonLock的unlockInnerAsync是Redisson释放锁的关键方法
1.判断KEY是否存在
2.如果不存在,返回nil
3.如果存在,使用hincrby-1,减1
4.减完后,counter>0值仍大于0,则返回0
5.减完后,counter<=0,删除key
6.用publish广播锁释放消息
订阅channel源码
watch dog自动延期
watch dog:当加锁成功后,同时开启守护线程,默认有效期是30秒,每隔10秒就会给锁续期到30秒
watchDog只有在未显示指定加锁时间时才会生效
lockWatchdogTimeout:可以设置超时时间
分段锁
思想来源map/reduce,ConcurrentHashMap
相关文章:
003 redis分布式锁 jedis分布式锁 Redisson分布式锁 分段锁
文章目录 Redis分布式锁原理1.使用set的命令时,同时设置过期时间2.使用lua脚本,将加锁的命令放在lua脚本中原子性的执行 Jedis分布式锁实现pom.xmlRedisCommandLock.javaRedisCommandLockTest.java 锁过期问题1乐观锁方式,增加版本号(增加版本…...
Jackson工具,java对象和json字符串之间的互相转换
一、maven依赖引入jackson <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.5</version></dependency>jackson-databind依赖见下: <depend…...
【设计模式】之装饰器模式
系列文章目录 【设计模式】之模板方法模式 【设计模式】之责任链模式 【设计模式】之策略模式 【设计模式】之工厂模式(三种) 前言 今天给大家介绍23种设计模式中的装饰器模式。🌈 一、什么是装饰器模式 装饰器模式(Decora…...
leetcode_46.全排列
46. 全排列 题目描述:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1: 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#…...
【牛客】[HNOI2003]激光炸弹
原题链接:登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 注意从(1,1)开始存即可,所以每次输入x,y之后,要x,y。 因为m的范围最大为…...
Docker与Harbor:构建企业级私有Docker镜像仓库
目录 引言 一、本地私有仓库 (一)基本概述 (二)搭建本地私有仓库 1.下载registry镜像 2.启动容器 3.上传本地镜像 4.客户端下载镜像 二、Harbor简介 (一)什么是 Harbor (二ÿ…...
推荐几个傻瓜式短视频去水印在线网站
在数字化时代,短视频已成为信息传播的重要方式之一。随着TikTok、Instagram Reels、抖音等平台的流行,短视频的制作和分享成为了日常生活的一部分。然而,在分享或编辑这些短视频时,去除水印成为了一项不可或缺的需求。水印是视频原…...
大模型LLM之SFT微调总结
一. SFT微调是什么 在大模型的加持下现有的语义理解系统的效果有一个质的飞跃;相对于之前的有监督的Pre-Train模型;大模型在某些特定的任务中碾压式的超过传统nlp效果;由于常见的大模型参数量巨大;在实际工作中很难直接对大模型训…...
【RocketMQ问题总结-2】
RocketMQ 消息持久化 Broker通过底层的Netty服务器获取到一条消息后,会把这条消息的内容写入到一个CommitLog文件里去(一个Broker进程就只有一个CommitLog文件,也就是说这个Broker上所有Topic的消息都会写入这个文件)。 同时&…...
掌握Android Fragment开发之魂:Fragment的深度解析(上)
Fragment是Android开发中用于构建动态和灵活界面的基石。它不仅提升了应用的模块化程度,还增强了用户界面的动态性和交互性,允许开发者将应用界面划分为多个独立、可重用的部分,每个部分都可以独立于其他部分进行操作。本文将从以下几个方面深…...
深度解读DreamFusion:一站式AI解决方案
DreamFusion是一款备受瞩目的人工智能解决方案,它整合了多种AI技术,为用户提供了一站式的解决方案。本文将全面解读DreamFusion,探讨其特点、功能和应用场景,助您深入了解这一创新工具。 1. 特点概述 DreamFusion具备以下显著特…...
JVM-02
字节码文件是一种特殊的文件格式,它包含了将源代码转换为机器可执行代码所需的指令集。字节码文件通常是由编译器将源代码编译为字节码的中间表示形式。 在Java中,字节码文件的扩展名为.class,它存储了编译后的Java代码。这些字节码文件可以在…...
【一起深度学习——NIN】
NIN神经网络 原理图:代码实现:输出结果: 原理图: 代码实现: import torch from torch import nn from d2l import torch as d2ldef nin_block(in_channels, out_channels, kernel_size, strides, padding):return nn.…...
数字工厂管理系统如何助力企业数据采集与分析
随着科技的不断进步,数字化已成为企业发展的重要趋势。在制造业领域,数字工厂管理系统的应用日益广泛,它不仅提升了生产效率,更在数据采集与分析方面发挥着举足轻重的作用。本文旨在探讨数字工厂管理系统如何助力企业数据采集与分…...
uniap之微信公众号支付
近来用uniapp开发H5的时候,需要接入支付,原来都是基于后端框架来做的,所以可谓是一路坑中过,今天整理下大致流程分享给大家。 先封装util.js,便于后面调用 const isWechat function(){return String(navigator.userA…...
Django知识点总结
因为最近在搞一个Python项目,使用的Django框架。所以快速学习了一下这个web框架。并做一些总结。 Django官网的介绍:Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Built by experience…...
算法(C++
题目:螺旋矩阵(59. 螺旋矩阵 II - 力扣(LeetCode)) 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1: 输入&am…...
Python专题:六、循环语句(1)
补充知识 代码的注释 #描述性文字 阅读代码的人更好的理解代码 while循环语句 x<100条件控制语句,Totalx,Total自增加x,x1,x自增加1,x<100此条件满足时,执行while循环,当x101时,x101条…...
力扣2105---给植物浇水II(Java、模拟、双指针)
题目描述: Alice 和 Bob 打算给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x i 。 每一株植物都需要浇特定量的水。Alice 和 Bob 每人有一个水罐,最初是…...
Windows设置Redis为开机自启动
前言 Redis作为当前最常用的当前缓存技术,基本上Web应用中都有使用。所以,每次我们在本地启动项目前,都必须将Redis服务端启动。但是,每次都要去启动Redis就很麻烦,有没有办法做到开机自动启动Redis呢?这当…...
行业早报5.10
1.鸿蒙智行 4 月交付 29632 辆蝉联中国新势力月销冠,问界 M9 超 13000 辆; 2.三星收购胎儿超声 AI 软件公司 Sonio,巩固尖端医疗设备领域的领先地位; 3.蔚来汽车 4 月交付 15620 辆新车,同比增长 134.6%; 4…...
Java+SpringBoot+JSP实现在线心理评测与咨询系统
前言介绍 随着互联网技术的高速发展,人们生活的各方面都受到互联网技术的影响。现在人们可以通过互联网技术就能实现不出家门就可以通过网络进行系统管理,交易等,而且过程简单、快捷。同样的,在人们的工作生活中,也就…...
机器学习算法应用——K近邻分类器(KNN)
K近邻分类器(KNN)(4-2) K近邻分类器(K-Nearest Neighbor,简称KNN)是一种基本的机器学习分类算法。它的工作原理是:在特征空间中,如果一个样本在特征空间中的K个最相邻的样…...
python数据分析——数据的选择和运算
数据的选择和运算 前言一、数据选择NumPy的数据选择一维数组元素提取示例 多维数组行列选择、区域选择示例 花式索引与布尔值索引布尔索引示例一示例二 花式索引示例一示例二 Pandas数据选择Series数据获取DataFrame数据获取列索引取值示例一示例二 取行方式示例loc() 方法示例…...
《CKA/CKAD应试指南/从docker到kubernetes 完全攻略》学习笔记 第8章 deployment
目录 前言 8.1创建和删除deployment 8.1.1通过yaml文件的方式创建deployment 8.1.2 deployment 健壮性测试...
步态识别论文(6)GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation
摘要: 视角变化导致步态外观存在显着差异。因此,识别跨视图场景中的步态是非常具有挑战性的。最近的方法要么在进行识别之前将步态从原始视图转换为目标视图,要么通过蛮力学习或解耦学习提取与相机视图无关的步态特征。然而,这些方法有许多约…...
K8S中的弹性云服务如何搭建,可能遇到的问题,如何解决!(稳啦!!!!全都稳啦!!!)
首先我们先来了解一下这玩意儿~~~ 啥是弹性云服务(Elastic Cloud Service)???? 弹性云服务(ECS)是一种基于云计算技术的虚拟服务器,由vCPU、内存、磁盘等组成的获取方便…...
新增分类——后端
实现功能: 代码开发逻辑: 页面发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端服务端Controller接收页面提交的数据并调用Service将数据进行保存Service调用Mapper操作数据库,保存数据 代码实现: Con…...
20232801 2023-2024-2 《网络攻防实践》实践九报告
20232801 2023-2024-2 《网络攻防实践》实践九报告 1.实践内容 (1)手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。 (2)利用foo函数的Bof漏洞,构造一个攻击输入字符串…...
类和对象--this引用原理
看如下代码 public class Date {public int year;public int month;public int day;public void setDate(int y, int m, int d) {year y;month m;day d;}public void printDate(){System.out.println(year "年" month "月" day "日");}…...
模板网站视频/电商网站建设
近年来,有很多轻量级的骨干网络问世,尤其最近两年,各种 NAS 搜索出的网络层出不穷,这些网络要么主打 FLOPs 或者 Params 上的优势,要么主打 ARM 设备上的推理速度的优势,很少有网络专门针对 Intel CPU 做特…...
怎么做传奇网站/社交网络推广方法
文章目录文章参考案例描述定义modeleffects 参数介绍connect关联model和UI组件启动文件引入 model配置文章参考 https://dvajs.com/guide/introduce-class.html#reducerJavaScript异步编程:Generator与Async 案例描述 定义model 前面写过redux相关的笔记…...
哈尔滨微网站建设/sem和seo是什么职业岗位
C语言中,所有的指针都必须进行初始化,包括结构体中的成员! 代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> struct student{ char *name; int score; struct stu…...
上海品牌网站建设公司/软文标题
在Oracle中判断一个用户自定义对象是否存在,可以通过如下Sql语句来实现:select * from user_objects where object_name YOUROBJECT;这样可以取得对象的所有信息. 需要注意的是:1.查询条件中的对象名要大写2.对象名有可能重复,所以需要再指定一个OBJECT_TYPE条件,才能更精确的判…...
网站兼容9/有没有可以代理推广的平台
如今互联网技术在不断地发展和进步。同时Web3D产品建模技术也越来越承受,被广泛应用于诸多行业当中。商迪3D构建的Web3D产品建模可视化三维模型在产品建模和宣传展示中,Web3D技术发挥了极大的作用,并为产品建模和展示注入了新的活力。电商产品…...
网站规划作品/营销网站设计
海尔笔记本X3超级本拆机增加内存图文教程(解决后盖开不了问题)参考文章: (1)海尔笔记本X3超级本拆机增加内存图文教程(解决后盖开不了问题) (2)https://www.cnblogs.co…...