当前位置: 首页 > news >正文

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自旋等待

redisson源码 加锁
1.key不存在,加锁设置字段=1,过段时间
2.key、字段都存在,锁重入,字段值+1
3.key存在,字段不存在,抢锁失败
redisson源码 加锁

Redisson释放锁原理

RedissonLock的unlockInnerAsync是Redisson释放锁的关键方法
1.判断KEY是否存在
2.如果不存在,返回nil
3.如果存在,使用hincrby-1,减1
4.减完后,counter>0值仍大于0,则返回0
5.减完后,counter<=0,删除key
6.用publish广播锁释放消息
redisson源码 释放锁

redisson源码 释放锁
订阅channel源码
订阅channel源码

watch dog自动延期

watch dog:当加锁成功后,同时开启守护线程,默认有效期是30秒,每隔10秒就会给锁续期到30秒
watchDog只有在未显示指定加锁时间时才会生效
lockWatchdogTimeout:可以设置超时时间
watchDog

分段锁

思想来源map/reduce,ConcurrentHashMap
分段锁

相关文章:

003 redis分布式锁 jedis分布式锁 Redisson分布式锁 分段锁

文章目录 Redis分布式锁原理1.使用set的命令时&#xff0c;同时设置过期时间2.使用lua脚本&#xff0c;将加锁的命令放在lua脚本中原子性的执行 Jedis分布式锁实现pom.xmlRedisCommandLock.javaRedisCommandLockTest.java 锁过期问题1乐观锁方式&#xff0c;增加版本号(增加版本…...

Jackson工具,java对象和json字符串之间的互相转换

一、maven依赖引入jackson <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.5</version></dependency>jackson-databind依赖见下&#xff1a; <depend…...

【设计模式】之装饰器模式

系列文章目录 【设计模式】之模板方法模式 【设计模式】之责任链模式 【设计模式】之策略模式 【设计模式】之工厂模式&#xff08;三种&#xff09; 前言 今天给大家介绍23种设计模式中的装饰器模式。&#x1f308; 一、什么是装饰器模式 装饰器模式&#xff08;Decora…...

leetcode_46.全排列

46. 全排列 题目描述&#xff1a;给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#…...

【牛客】[HNOI2003]激光炸弹

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 注意从&#xff08;1,1&#xff09;开始存即可&#xff0c;所以每次输入x,y之后&#xff0c;要x,y。 因为m的范围最大为…...

Docker与Harbor:构建企业级私有Docker镜像仓库

目录 引言 一、本地私有仓库 &#xff08;一&#xff09;基本概述 &#xff08;二&#xff09;搭建本地私有仓库 1.下载registry镜像 2.启动容器 3.上传本地镜像 4.客户端下载镜像 二、Harbor简介 &#xff08;一&#xff09;什么是 Harbor &#xff08;二&#xff…...

推荐几个傻瓜式短视频去水印在线网站

在数字化时代&#xff0c;短视频已成为信息传播的重要方式之一。随着TikTok、Instagram Reels、抖音等平台的流行&#xff0c;短视频的制作和分享成为了日常生活的一部分。然而&#xff0c;在分享或编辑这些短视频时&#xff0c;去除水印成为了一项不可或缺的需求。水印是视频原…...

大模型LLM之SFT微调总结

一. SFT微调是什么 在大模型的加持下现有的语义理解系统的效果有一个质的飞跃&#xff1b;相对于之前的有监督的Pre-Train模型&#xff1b;大模型在某些特定的任务中碾压式的超过传统nlp效果&#xff1b;由于常见的大模型参数量巨大&#xff1b;在实际工作中很难直接对大模型训…...

【RocketMQ问题总结-2】

RocketMQ 消息持久化 Broker通过底层的Netty服务器获取到一条消息后&#xff0c;会把这条消息的内容写入到一个CommitLog文件里去&#xff08;一个Broker进程就只有一个CommitLog文件&#xff0c;也就是说这个Broker上所有Topic的消息都会写入这个文件&#xff09;。 同时&…...

掌握Android Fragment开发之魂:Fragment的深度解析(上)

Fragment是Android开发中用于构建动态和灵活界面的基石。它不仅提升了应用的模块化程度&#xff0c;还增强了用户界面的动态性和交互性&#xff0c;允许开发者将应用界面划分为多个独立、可重用的部分&#xff0c;每个部分都可以独立于其他部分进行操作。本文将从以下几个方面深…...

深度解读DreamFusion:一站式AI解决方案

DreamFusion是一款备受瞩目的人工智能解决方案&#xff0c;它整合了多种AI技术&#xff0c;为用户提供了一站式的解决方案。本文将全面解读DreamFusion&#xff0c;探讨其特点、功能和应用场景&#xff0c;助您深入了解这一创新工具。 1. 特点概述 DreamFusion具备以下显著特…...

JVM-02

字节码文件是一种特殊的文件格式&#xff0c;它包含了将源代码转换为机器可执行代码所需的指令集。字节码文件通常是由编译器将源代码编译为字节码的中间表示形式。 在Java中&#xff0c;字节码文件的扩展名为.class&#xff0c;它存储了编译后的Java代码。这些字节码文件可以在…...

【一起深度学习——NIN】

NIN神经网络 原理图&#xff1a;代码实现&#xff1a;输出结果&#xff1a; 原理图&#xff1a; 代码实现&#xff1a; import torch from torch import nn from d2l import torch as d2ldef nin_block(in_channels, out_channels, kernel_size, strides, padding):return nn.…...

数字工厂管理系统如何助力企业数据采集与分析

随着科技的不断进步&#xff0c;数字化已成为企业发展的重要趋势。在制造业领域&#xff0c;数字工厂管理系统的应用日益广泛&#xff0c;它不仅提升了生产效率&#xff0c;更在数据采集与分析方面发挥着举足轻重的作用。本文旨在探讨数字工厂管理系统如何助力企业数据采集与分…...

uniap之微信公众号支付

近来用uniapp开发H5的时候&#xff0c;需要接入支付&#xff0c;原来都是基于后端框架来做的&#xff0c;所以可谓是一路坑中过&#xff0c;今天整理下大致流程分享给大家。 先封装util.js&#xff0c;便于后面调用 const isWechat function(){return String(navigator.userA…...

Django知识点总结

因为最近在搞一个Python项目&#xff0c;使用的Django框架。所以快速学习了一下这个web框架。并做一些总结。 Django官网的介绍&#xff1a;Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Built by experience…...

算法(C++

题目&#xff1a;螺旋矩阵&#xff08;59. 螺旋矩阵 II - 力扣&#xff08;LeetCode&#xff09;&#xff09; 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&am…...

Python专题:六、循环语句(1)

补充知识 代码的注释 #描述性文字 阅读代码的人更好的理解代码 while循环语句 x<100条件控制语句&#xff0c;Totalx,Total自增加x&#xff0c;x1&#xff0c;x自增加1&#xff0c;x<100此条件满足时&#xff0c;执行while循环&#xff0c;当x101时&#xff0c;x101条…...

力扣2105---给植物浇水II(Java、模拟、双指针)

题目描述&#xff1a; Alice 和 Bob 打算给花园里的 n 株植物浇水。植物排成一行&#xff0c;从左到右进行标记&#xff0c;编号从 0 到 n - 1 。其中&#xff0c;第 i 株植物的位置是 x i 。 每一株植物都需要浇特定量的水。Alice 和 Bob 每人有一个水罐&#xff0c;最初是…...

Windows设置Redis为开机自启动

前言 Redis作为当前最常用的当前缓存技术&#xff0c;基本上Web应用中都有使用。所以&#xff0c;每次我们在本地启动项目前&#xff0c;都必须将Redis服务端启动。但是&#xff0c;每次都要去启动Redis就很麻烦&#xff0c;有没有办法做到开机自动启动Redis呢&#xff1f;这当…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...