Springboot高并发乐观锁
Spring Boot分布式锁的主要缺点包括但不限于以下几点:
-
性能开销:使用分布式锁通常涉及到网络通信,这会引入额外的延迟和性能开销。例如,当使用Redis或Zookeeper实现分布式锁时,每次获取或释放锁都需要与这些服务进行交互。
-
单点故障风险:如果依赖于某个特定的服务(如Redis)来管理锁,那么该服务可能会成为单点故障。如果这个服务不可用,所有依赖它的锁机制都会失效,可能导致系统不稳定或者数据不一致的问题。
-
死锁风险:在某些情况下,如果没有正确处理异常情况或者客户端突然崩溃,可能会导致死锁现象。例如,如果一个持有锁的进程未能正确释放锁,则其他等待该锁的进程将永远处于等待状态。
-
复杂性增加:引入分布式锁增加了系统的复杂性。开发人员需要理解如何正确地使用锁,并且要考虑到各种边界条件,比如超时、重试逻辑等。此外,还需要考虑不同类型的锁(如公平锁、非公平锁)以及它们对应用行为的影响。
-
资源竞争:在高并发场景下,多个实例尝试同时获取同一把锁会导致大量的资源竞争,从而影响整体性能。特别是对于一些频繁读写的热点数据来说,这种竞争可能会成为一个瓶颈。
-
实现差异:不同的分布式锁实现之间存在差异,这意味着迁移到另一种解决方案可能需要更改代码甚至重新设计架构。而且,不是所有的实现都提供了相同的特性和保障。
-
租约管理和心跳检测:一些分布式锁实现依赖于租约(Lease)和心跳来确保锁的有效性。这要求客户端定期向锁服务发送心跳信号以保持其持有的锁。如果网络分区发生或客户端出现故障,可能会导致锁提前被释放,进而引发数据一致性问题。
-
不适合长时间持有锁:由于网络延迟和其他因素,长时间持有分布式锁不是一个好的实践,因为它可能会阻塞其他请求过久,尤其是在高并发环境中。
Redis与Lua
使用Redis与Lua脚本结合的方式虽然有很多优点,比如减少网络开销、提供原子性操作以及可复用等特性,但也存在一些缺点:
-
脚本大小和执行时间限制:
- Lua脚本的大小受到一定的限制,过大的脚本可能无法成功加载到Redis中。
- Redis对Lua脚本的执行时间也有一定限制,以防止单个脚本占用过多资源或导致服务器阻塞。如果脚本执行时间过长,可能会触发客户端配置的时间限制,进而中断脚本执行。
-
编写复杂度:
- 编写Lua脚本需要一定的编程经验,对于不熟悉Lua语言或者编程概念的开发者来说,可能存在较高的学习曲线。
- 如果Lua脚本逻辑复杂,调试和维护也会变得更加困难。
-
阻塞风险:
- 在Redis中,Lua脚本是按照顺序串行执行的,并且在执行期间会阻止其他命令的处理。因此,长时间运行的脚本可能会造成Redis服务器的阻塞,影响系统的响应速度和其他客户端的操作。
- 不应该在Lua脚本中使用阻塞命令(如BLPOP, BRPOP等),因为这会导致Redis服务器在执行脚本时被阻塞,无法处理其他请求。
-
错误处理机制有限:
- 如果Lua脚本在执行过程中出现错误,Redis不会回滚已经执行的部分,这可能导致数据处于不一致状态。
- 错误发生后,通常只能通过日志来追踪问题所在,缺乏更高级别的错误恢复机制。
-
内存消耗:
- Lua脚本一旦执行就会被缓存起来供后续调用使用,这可以提高性能但同时也增加了内存使用量。如果脚本数量庞大或每个脚本占用较多内存,可能会给Redis带来额外的压力。
-
版本兼容性:
- 随着Redis版本的更新,Lua解释器的版本也可能发生变化,这可能会导致旧版本脚本在新版本Redis上不能正常工作的问题。
-
安全性考虑:
- 使用Lua脚本时需要注意安全性,避免恶意用户利用脚本执行攻击。例如,应避免直接将用户输入作为脚本的一部分执行,以防代码注入风险。
package com.cokerlk.redisclientside;import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.*;@RestController
public class LuaController {@Resourceprivate StringRedisTemplate stringRedisTemplate;private static final String LUA_SCRIPT = """if tonumber(redis.call('exists', KEYS[1])) == 0 thenredis.call('set', KEYS[1],'10')endif tonumber(redis.call('exists', KEYS[2])) == 0 thenredis.call('sadd', KEYS[2],'-1')endif tonumber(redis.call('get', KEYS[1])) > 0 and tonumber(redis.call('sismember', KEYS[2] , ARGV[1])) == 0 then redis.call('incrby', KEYS[1],'-1') redis.call('sadd',KEYS[2],ARGV[1])return 1else return 0 end""";@GetMapping("/sk")public Map<String,Object> secKill(String pid){Map<String,Object> resp = new HashMap<>();String uid = String.valueOf(new Random().nextInt(100000000));List<String> keys = new ArrayList<>();keys.add("P" + pid); //P1010 String类型 用于保存产品库存量keys.add("U" + pid);//U1010 SET类型 用于保存秒杀确权的UIDDefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_SCRIPT,Long.class);Long result = stringRedisTemplate.execute(redisScript, keys,uid);resp.put("uid", uid);resp.put("result", result);return resp;}
}
Spring Retry + Redis Watch实现乐观锁
Spring Retry 和 Redis 的 WATCH
命令可以结合使用来实现乐观锁,尤其是在处理分布式环境下的并发控制时。这种组合可以有效地减少锁的开销,并提供一种非阻塞的方式来处理并发更新。
实现步骤
-
使用 WATCH 监视键: 在开始事务之前,使用
WATCH
命令监视一个或多个键。这告诉Redis在这些键上设置一个“观察点”,如果这些键在事务执行过程中被其他客户端修改,则当前事务将失败。 -
发起 MULTI 开始事务: 当所有需要监视的键都已确定后,使用
MULTI
命令开启一个事务。从这一刻起,所有后续命令都会被收集起来,直到EXEC
被调用。 -
尝试执行命令: 在事务中执行所需的命令(例如
GET
、SET
等),最后通过EXEC
提交事务。如果自WATCH
以来没有键被修改,那么事务将成功提交;否则,EXEC
将返回null
表示事务失败。 -
使用 Spring Retry 进行重试: 如果由于其他客户端修改了受监视的键而导致事务失败,可以通过 Spring Retry 来自动重试整个过程。这样,应用程序可以在不增加复杂性的情况下处理并发冲突。
-
定义重试逻辑: 需要为 Spring Retry 配置适当的重试策略,包括最大重试次数、等待间隔等参数。同时,应该考虑何时停止重试,比如当达到最大重试次数或者超过某个时间限制时。
添加依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.5</version></dependency>--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></dependency><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
业务逻辑
package com.cokerlk.redisclientside;import jakarta.annotation.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Objects;@Service
public class SampleService {@Resourceprivate RedisTemplate<String,Object> redisTemplate;@Retryable(retryFor = IllegalStateException.class, maxAttempts = 2)@Transactionalpublic String saWatch(){System.out.println("executing sa()");List<Object> execute = redisTemplate.execute(new SessionCallback<>() {public List<Object> execute(RedisOperations operations) throws DataAccessException {redisTemplate.watch("sa001");redisTemplate.multi();redisTemplate.opsForValue().set("pri001", -100);try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}redisTemplate.opsForValue().set("sa001", 100);return redisTemplate.exec();}});if(Objects.isNull(execute)){System.out.println("发现并发冲突:" + execute);throw new IllegalStateException("Retry");}else{System.out.println("exec执行成功:" + execute);}return "success";}
}
-
redisTemplate.execute(SessionCallback)
:- 使用
SessionCallback
来定义一个Redis会话,其中包含了一系列命令,这些命令将在一个单独的事务中执行。
- 使用
-
redisTemplate.watch("sa001")
:- 开始监视键
"sa001"
,确保在接下来的事务期间如果该键被其他客户端修改,则当前事务将失败。
- 开始监视键
-
redisTemplate.multi()
:- 启动一个Redis事务,之后的所有命令都会被收集起来,直到调用
exec()
。
- 启动一个Redis事务,之后的所有命令都会被收集起来,直到调用
控制器
package com.cokerlk.redisclientside;import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class SampleController {@Resourceprivate RedisTemplate<String,Object> redisTemplate;@Resourceprivate SampleService sampleService;@GetMapping("/test")public String testWatch(){sampleService.saWatch();return "success";}@GetMapping("/setSA")public String setSA(){redisTemplate.opsForValue().set("sa001",300);return "success";}}
Application
package com.cokerlk.redisclientside;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;@SpringBootApplication
@EnableRetry
public class RedisClientSideApplication {public static void main(String[] args) {SpringApplication.run(RedisClientSideApplication.class, args);}}
测试
###
GET http://localhost:8080/test###
GET http://localhost:8080/setSAexecuting sa()
exec执行成功:[true, true]
executing sa()
exec执行成功:[]
相关文章:

Springboot高并发乐观锁
Spring Boot分布式锁的主要缺点包括但不限于以下几点: 性能开销:使用分布式锁通常涉及到网络通信,这会引入额外的延迟和性能开销。例如,当使用Redis或Zookeeper实现分布式锁时,每次获取或释放锁都需要与这些服务进行交…...

【WPS安装】WPS编译错误总结:WPS编译失败+仅编译成功ungrib等
WPS编译错误总结:WPS编译失败仅编译成功ungrib等 WPS编译过程问题1:WPS编译失败错误1:gfortran: error: unrecognized command-line option ‘-convert’; did you mean ‘-fconvert’?解决方案 问题2:WPS编译三个exe文件只出现u…...

pytorch MoE(专家混合网络)的简单实现。
专家混合(Mixture of Experts, MoE)是一种深度学习模型架构,通常用于处理大规模数据和复杂任务。它通过将输入分配给多个专家网络(即子模型),然后根据门控网络(gating network)的输出…...

虚拟机VMware的安装问题ip错误,虚拟网卡
要么没有虚拟网卡、有网卡远程连不上等 一般出现在win11 家庭版 1、是否IP错误 ip addr 2、 重置虚拟网卡 3、查看是否有虚拟网卡 4、如果以上检查都解决不了问题 如果你之前有vmware 后来卸载了,又重新安装,一般都会有问题 卸载重装vmware: 第一…...

Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门hello world输出【入门一】
开发环境搭建:Linux-Ubuntu下搭建ESP32的开发环境的步骤,使用乐鑫最新稳定版的esp-idf-CSDN博客 一、安装好开发环境后,在esp目录下再创建一个esp32的目录【用于编程测试demo】 二、进入esp32目录,打开终端【拷贝esp-idf的hello工…...

重温设计模式--命令模式
文章目录 命令模式的详细介绍C 代码示例C代码示例2 命令模式的详细介绍 定义与概念 命令模式属于行为型设计模式,它旨在将一个请求封装成一个对象,从而让你可以用不同的请求对客户端进行参数化,将请求的发送者和接收者解耦,并且能…...

电力通信规约-104实战
电力通信规约-104实战 概述 104规约在广泛应用于电力系统远动过程中,主要用来进行数据传输和转发,本文将结合实际开发实例来讲解104规约的真实使用情况。 实例讲解 因为个人技术栈是Java,所以本篇将采用Java实例来进行讲解。首先我们搭建一…...

什么是事务
在数据库管理系统中,事务(Transaction)是执行一系列操作的最小工作单元,这些操作要么全部成功,要么全部失败。为了确保数据的一致性和完整性,事务被设计为具备四大特性,即原子性(Ato…...

数据结构:双向循坏链表
目录 1.1双向循环链表的结构 2.双向链表功能的实现 2.1初始化链表 2.2销毁链表 2.3创建结点 2.4打印链表 2.5链表查找 2.6链表在pos的前面进行插入 2.7链表删除pos位置的节点 2.8链表的头插,头删 ,尾插,尾删 1.1双向循环链表的结构 …...

3.1、SDH的5种标准容器
1、定义与作用 在 SDH(同步数字体系)中,标准容器(C)是一种用来装载各种速率的 PDH(准同步数字系列)信号的信息结构。它的主要作用是进行速率适配,使不同速率的 PDH 信号能够在 SDH 的…...

Jenkins介绍
Jenkins 是一款流行的开源自动化服务器,在软件开发和持续集成 / 持续交付(CI/CD)流程中发挥着关键作用。 一、主要功能 1.持续集成(CI) (1).自动构建:Jenkins 可以配置为监听代码仓…...

5G学习笔记之Non-Public Network
目录 0. NPN系列 1. 概述 2. SNPN 2.1 SNPN概述 2.2 SNPN架构 2.3 SNPN部署 2.3.1 完全独立 2.3.2 共享PLMN基站 2.3.3 共享PLMN基站和PLMN频谱 3. PNI-NPN 3.1 PNI-NPN概述 3.2 PNI-NPN部署 3.2.1 UPF独立 3.2.2 完全共享 0. NPN系列 1. NPN概述 2. NPN R18 3. 【SNPN系列】S…...

网页生成鸿蒙App
如何网页生成鸿蒙App 纯鸿蒙发布后,鸿蒙App需求上升。如何快速生成鸿蒙App。变色龙云(http://www.appbsl.cn)推出了鸿蒙App打包服务。可以在线自动打包鸿蒙App。 第一步 创建应用 输入网站网址,上传图标。 第二步 生成鸿蒙证书 打开华为开发者管理中…...

JavaWeb通过Web查询数据库内容:(pfour_webquerymysql)
JavaWeb通过Web查询数据库内容: 数据库: 自行建库建表,主键 id 后端: 新建项目模块选择模块,添加依赖创建配置文件: db.propertiesJava类: query查询 前端: Web添加创建query.html…...

将java项目部署到linux
命令解析 Dockerfile: Dockerfile 是一个文本文件,包含了所有必要的指令来组装(build)一个 Docker 镜像。 docker build: 根据 Dockerfile 或标准指令来构建一个新的镜像。 docker save: 将本地镜像保存为一个 tar 文件。 docker load: 从…...

moviepy将图片序列制作成视频并加载字幕 - python 实现
DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 需要更多数据资源和技术解决方案,知识星球: “DataBall - X 数据球(free)” -------------------------------------------------------------…...

ROS1入门教程5:简单行为处理
一、新建项目 # 创建工作空间 mkdir -p demo5/src && cd demo5# 初始化工作空间 catkin_make# 创建功能包 cd src catkin_create_pkg demo roscpp actionlib_msgs message_generation tf 二、创建行为 # 创建行为目录 mkdir action && cd action# 创建行为文…...

Vue:实现输入框不能输负数功能
1、使用v-model指令 <input type"number" v-model"value" min"0" input"checkInput"> checkInput() {this.value Math.max(0, parseInt(this.value)); } 2、使用计算属性 <template><div><input type"…...

管理系统、微信小程序类源码文档-哔哩哔哩教程同步
文章目录 前言通用表基于JavaSpringBootVue前后端分离手机销售商城系统设计实现:基于JavaSpringBootVueuniapp实现大学生校园兼职微信小程序更新中。。。评论区打出你的题目 🌈你好呀!我是 山顶风景独好 🎈欢迎踏入我的博客世界,能…...

AOP切点表达式之方法表达式execution
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...

clickhouse-题库
1、clickhouse介绍以及架构 clickhouse一个分布式列式存储数据库,主要用于在线分析查询 2、列式存储和行式存储有什么区别? 行式存储: 1)、数据是按行存储的 2)、没有建立索引的查询消耗很大的IO 3)、建…...

在 Sanic 应用中使用内存缓存管理 IP 黑名单
[外链图片转存中…(img-Pm0K9mzd-1734859380698)] 在现代 web 应用中,保护 API 接口免受恶意请求的攻击至关重要。IP 黑名单是一种常见的安全措施,可以有效阻止某些 IP 地址的访问。本文将介绍如何在 Python 的 Sanic 框架中实现 IP 黑名单功能…...

可翻折的CPCI导冷板卡插拔机构
本技术涉及一种cpci板卡模块拔插,尤其涉及一种可翻折的cpci导冷板卡插拔机构。 背景技术: 1、cpci(compactpci)导冷板卡是一种基于compactpci计算机总线标准的卡式电子模块,它在标准的cpci架构之上增加了导热板来提高散热能力,常…...

面试题整理9----谈谈对k8s的理解2
面试题整理9----谈谈对k8s的理解2 1. Service 资源1.1 ServiceClusterIPNodePortLoadBalancerIngressExternalName 1.2 Endpoints1.3 Ingress1.4 EndpointSlice1.5 IngressClass 2. 配置和存储资源2.1 ConfigMap2.2 Secret2.3 PersistentVolume2.4 PersistentVolumeClaim2.5 St…...

12个城市人文扫街、旅拍、人像风光摄影后期Lightroom调色预设
12个城市人文扫街、旅拍、人像风光摄影后期Lightroom调色预设 12 个专为城市场景设计的专业 Adobe Lightroom 预设。只需单击一下,即可通过一致、专业的基础简化您的编辑流程。 Lightroom & Lightroom Classic,桌面和移动兼容包括 12 张由 pat_kay…...

无人设备遥控器之数传功率篇
一、数传功率的基本概念 数传功率是指遥控器发射端在传输数据时所使用的功率。这个功率值直接影响了数据传输的距离和信号强度。一般来说,数传功率越大,遥控器与无人设备之间的通信距离就越远,信号强度也相应增强。 二、数传功率的调节与选择…...

灭屏情况下,飞行模式+静音模式+插耳,播放音乐,电流异常
1. 功耗现象 灭屏情况下,飞行模式静音模式插耳,播放音乐,电流异常 1.1测试数据 飞行模式静音模式插耳机 原生音乐播放器 DriverOnly 32.5mA User版本 45mA 1.2 电流波形现象 上述看怀疑 CPU 未进入 Deep idle 导致? 2. …...

面向微服务的Spring Cloud Gateway的集成解决方案:用户登录认证与访问控制
🎯导读:本文档详细描述了一个基于Spring Cloud Gateway的微服务网关及Admin服务的实现。网关通过定义路由规则,利用负载均衡将请求转发至不同的后端服务,并集成了Token验证过滤器以确保API的安全访问,同时支持白名单路…...

Jmeter负载测试如何找到最大并发用户数?
在性能测试中,当我们接到项目任务时,很多时候我们是不知道待测接口能支持多少并发用户数的。此时,需要我们先做负载测试,通过逐步加压,来找到最大并发用户数。那么当我们找到一个区间,怎么找到具体的值呢&a…...

Spark-Streaming集成Kafka
Spark Streaming集成Kafka是生产上最多的方式,其中集成Kafka 0.10是较为简单的,即:Kafka分区和Spark分区之间是1:1的对应关系,以及对偏移量和元数据的访问。与高版本的Kafka Consumer API 集成时做了一些调整,下面我们…...