如果由于流量大而在短时间内几乎同时发出请求,或者由于服务器不稳定而需要很长时间来处理请求,并发问题可能会导致数据完整性问题。

示例问题情况

图片

让我们假设有一个逻辑可以检索产品的库存并将库存减少一个,如上所述。此时,两个请求大致同时到达,以检索和减少库存。这里出现的问题是其他请求在完成减库存任务之前也可以查询库存,所以虽然有一个库存,但是两个请求都处理了,导致库存为零的意外结果。

因此,为了稳定地访问和控制共享资源,需要使用锁来限制在特定代码块中只能处理一个请求。

1.进程中的锁控制

各种编程语言通常会提供线程安全设施,以防止多个线程访问一个进程。在 Java 中,您可以通过向方法添加 synchronized 关键字或在代码中添加 synchronized 块来控制并发性。

代码示例:
@Synchronizedfun decrease(id: Long, quantity: Long) {    val stock: Stock = stockRepository.findByIdOrNull(id)         ?: throw IllegalArgumentException("无法找到库存信息.")    stock.decrease(quantity);}
坏处:

它在应用程序服务器不是由一个组成的多服务器环境中不能很好地工作。由于上述方法只能控制一个进程内的并发,所以在配置多个进程时也会出现同样的并发问题。

2.DB中的锁控制

有一种方法可以直接控制数据库中的锁,而不是在应用程序级别。

2.1. 悲观锁

这是在请求资源时会频繁出现并发问题的悲观预期下设置锁的方法。当一个事务正在访问数据时,禁止其他事务进行读写。许多 RBDMS(MySQL、Oracle、PostgreSQL 等)SELECT ~ FOR UPDATE提供了一个功能,可以防止其他事务访问该行,直到更新完成并用 SQL 语句提交。

图片

代码示例:
interface StockRepository : JpaRepository<Stock, Long> {    @Lock(LockModeType.PESSIMISTIC_WRITE)    fun findStockById(id: Long): Stock?}

如上面的例子,如果你使用 JPA,@Lock(LockModeType.PESSIMISTIC_WRITE)你可以指定悲观锁。

  • LockModeType.PESSIMISTIC_WRITE: SELECT ~ FOR UPDATE query is executed (lock both read and write with exclusive lock)

  • LockModeType.PESSIMISTIC_READ: SELECT ~ FOR SHARE query is executed (write lock with shared lock)

坏处:

多表连接时存在死锁风险。
此外,由于频繁锁定会降低吞吐量,因此如果并发问题很少发生,则会降低性能。

2.2 乐观锁

乐观锁定是一种通过添加诸如版本之类的列来调整数据一致性的方法,而不是使用实际锁定。它乐观地判断并发问题不会频繁发生,所以所有的请求都在不加锁的情况下处理,只有当发现数据一致性问题时,才进行回滚以保证一致性。

图片

发生并发问题时,如果现有的版本值已经在一个事务中被更新,则在另一个事务中无法检索到相应的版本值,因此不会发生更新。如果没有更新,则确定有问题,进行回滚处理,保证数据的一致性。

坏处:

如果并发问题频繁发生,则可能会频繁回滚。

3.使用Redis控制锁

使用 Redis,即使在具有多个应用程序的多服务器环境中,也可以有效地使用锁。

3.1. 使用 SETNX 命令的自旋锁

Redis是一种在键不存在时使用SETNX“ SET  if  N ot e Xists ”命令设置值的方法。这允许您实现一个自旋锁,将特定键设置为锁,并在锁已被使用时定期请求获取锁。

图片

在上面的示例中SET stock-id "lock" NX EX 3,如果 stock-id 键不存在,则设置一个 3 秒后过期的“锁定”值。

应用会不断发出请求,直到获取到stock-id key,用到时删除key。

(SETNX 命令已弃用,因此建议使用 SET 命令和 NX 选项。)

代码示例:
@Componentclass RedisLockRepository(    private val redisTemplate: RedisTemplate<String, String>,) {    fun lock(key: Long): Boolean {        val isSuccess: Boolean? = redisTemplate.opsForValue()            .setIfAbsent(key.toString(), "lock", Duration.ofSeconds(3L))
        return isSuccess ?: false    }
    fun unlock(key: Long): Boolean {        return redisTemplate.delete(key.toString())    }}
@Serviceclass RedisLockStockService(    private val redisLockRepository: RedisLockRepository,) {    fun decrease(id: Long, quantity: Long) {        while (redisLockRepository.lock(id).not()) {            TimeUnit.MILLISECONDS.sleep(100L) // 100 milliseconds  sleep         }
        try {            ...                    } finally {            redisLockRepository.unlock(id)        }    }}

坏处:

实现很简单,但是它给 Redis 服务器增加了负载,因为它会不断尝试请求直到获得锁。

3.2. 使用Redisson的分布式锁

Redisson是一个帮助 Redis 高效处理分布式锁的开源软件。它提供了一个功能,可以通过使用 pub/sub 和 Lua 脚本来有效地处理分布式锁。

发布/订阅功能

Redisson 使用 Redis 的发布/订阅功能来等待订阅频道上的消息,直到获得锁。然后,当发生解锁并发布频道消息时,尝试获取锁。

图片

使用 Lua 脚本执行原子命令

Redis 可以使用 Lua 脚本原子地执行一组命令,Redisson 也利用它来原子地执行一组锁定和解锁命令。

图片


上面的例子是Redisson的解锁流程的一段代码。
1. 检索具有指定散列键和递减 1 的锁。
2. 如果计数器结果为0,则删除key。
3.使用publish命令发送频道的消息。

当使用上述函数解锁时,通过订阅从另一个线程接收消息,并且可以尝试获取锁。

@Serviceclass RedissonLockStockFacade(    private val stockService: StockService,    private val redissonClient: RedissonClient,) {    private val log: Logger = LoggerFactory.getLogger(this::class.java)
    fun decrease(id: Long, quantity: Long) {        val lock: RLock = redissonClient.getLock(id.toString())
        try {            val available: Boolean = lock.tryLock(5L, 1L, TimeUnit.SECONDS)            if (available.not()) {                log.error("lock ")                return            }            stockService.decrease(id, quantity)        } finally {            lock.unlock()        }    }}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/86626.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/86626.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/86626.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【2025CCF中国开源大会】OpenChain标准实践:AI时代开源软件供应链安全合规分论坛重磅来袭!

点击蓝字 关注我们 CCF Opensource Development Committee 在AI时代&#xff0c;软件供应链愈发复杂&#xff0c;从操作系统到开发框架&#xff0c;从数据库到人工智能工具&#xff0c;开源无处不在。AI 与开源生态深度融合&#xff0c;在为软件行业带来前所未有的创新效率的同…

[Java实战]springboot3使用JDK21虚拟线程(四十)

[Java实战]springboot3使用JDK21虚拟线程(四十) 告别线程池爆满、内存溢出的噩梦!JDK21 虚拟线程让高并发连接变得触手可及。本文将带你深入实战,见证虚拟线程如何以极低资源消耗轻松应对高并发压测。 一、虚拟线程 传统 Java 线程(平台线程)与 OS 线程 1:1 绑定,创建和…

SpringBoot 中使用 @Async 实现异步调用​

​ ​ SpringBoot 中使用 Async 实现异步调用 一、Async 注解的使用场合​二、Async 注解的创建与调试​三、Async 注解的注意事项​四、总结​ 在高并发、高性能要求的应用场景下&#xff0c;异步处理能够显著提升系统的响应速度和吞吐量。Spring Boot 提供的 Async 注解为开…

CMOS SENSOR HDR场景下MIPI 虚拟端口的使用案例

CMOS SENSOR HDR场景下MIPI 虚拟端口的使用案例 文章目录 CMOS SENSOR HDR场景下MIPI 虚拟端口的使用案例📷 **一、HDR模式下的虚拟通道核心作用**⚙️ **二、典型应用案例****1. 车载多目HDR系统****2. 工业检测多模态HDR****3. 手机多摄HDR合成**🔧 **三、实现关键技术点…

RJ45 以太网与 5G 的原理解析及区别

一、RJ45 以太网的原理 1. RJ45 接口与以太网的关系 RJ45 是一种标准化的网络接口&#xff0c;主要用于连接以太网设备&#xff08;如电脑、路由器&#xff09;&#xff0c;其物理形态为 8 针模块化接口&#xff0c;适配双绞线&#xff08;如 CAT5、CAT6 网线&#xff09;。以…

valkey之sdscatrepr 函数优化解析

一、函数功能概述 sds sdscatrepr(sds s, const char *p, size_t len)函数的核心功能是将字符串p追加到字符串s中。在追加过程中&#xff0c;它会对字符串p中的字符进行判断&#xff0c;使用isprint()函数识别不可打印字符&#xff0c;并对这些字符进行转义处理&#xff0c;确…

MyBatis 缓存机制详解

MyBatis 缓存机制详解 MyBatis 提供了强大的缓存机制来提高数据库访问性能&#xff0c;主要包括一级缓存和二级缓存两种。 一级缓存 (Local Cache) 特性&#xff1a; 默认开启&#xff0c;作用域为 SqlSession 级别同一个 SqlSession 中执行相同的 SQL 查询时&#xff0c;会…

设计模式精讲 Day 13:责任链模式(Chain of Responsibility Pattern)

【设计模式精讲 Day 13】责任链模式&#xff08;Chain of Responsibility Pattern&#xff09; 文章内容 在“设计模式精讲”系列的第13天&#xff0c;我们将深入讲解责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;。这是一种行为型设计模式&#xff0c;…

h-ui面板 hysteria2

搭建文档 项目地址&#xff1a;https://github.com/jonssonyan/h-ui/blob/main/README_ZH.md参考视频&#xff1a;https://www.youtube.com/watch?vNi3iaLOsH_A一键部署命令 # root权限 sudo -ibash <(curl -fsSL https://raw.githubusercontent.com/jonssonyan/h-ui/mai…

自动登录脚本神器-Mac电脑实现自动登录堡垒机并自动输入账号密码跳转不同机器环境

先讲下背景&#xff1a; 公司电脑需要先登录堡垒机&#xff0c;然后再从堡垒机跳转到具体生产机器&#xff0c;每次输入堡垒机都要通过Authenticator里的2FC的码做验证&#xff0c;然后再跳到堡垒机还要再输入一次账号密码&#xff0c;为了方便快速登录机器&#xff0c;可以制…

【C/C++】C++26新特性前瞻:全面解析未来编程

展望未来&#xff1a;C26 新特性全面解析 随着 C 标准每三年一次的迭代节奏&#xff0c;C26&#xff08;预计于 2026 年底正式发布&#xff09;正在逐步成型。相比 C20 的革命性更新和 C23 的“修补增强”&#xff0c;C26 继续推进现代 C 的理念——更安全、更高效、更模块化&…

ArXiv 2101 | Rethinking Interactive Image Segmentation Feature Space Annotation

Rethinking Interactive Image Segmentation Feature Space Annotation Author: lartpangLink: https://github.com/lartpang/blog/issues/10论文&#xff1a;https://arxiv.org/abs/2101.04378代码&#xff1a;https://github.com/LIDS-UNICAMP/rethinking-interactive-image…

架构经验总结

20250511-总结经验 一、SOA 1&#xff09;过程&#xff1a;需求分析、系统设计、系统实现、构件组装、部署运维、后开发阶段。 2&#xff09;特点&#xff1a;无状态、单一职责、明确定义接口、自包含、模块化、粗粒度、重用性、兼容性、互操作性、松耦合、策略声明。 3&…

debain切换 opensuse 我都安装了什么

绿色进度条后&#xff0c;黑屏&#xff08;只有一个下划线&#xff09;等待 使用 nomodeset 属性解决 进入系统无法连接 wifi&#xff0c;只能使用网线连接 wifi 这个我在安装中文字体后&#xff0c;注销登录&#xff0c;得到了解决&#xff0c;不确定是不是字体问题。&#x…

思科ISE/ISE-PIC安全警报:两处高危RCE漏洞(CVSS 10.0)可致未授权获取root权限

思科已发布更新&#xff0c;修复身份服务引擎&#xff08;Identity Services Engine&#xff0c;ISE&#xff09;及ISE被动身份连接器&#xff08;ISE-PIC&#xff09;中两处最高危安全漏洞&#xff0c;这些漏洞可能允许未经认证的攻击者以root用户身份执行任意命令。 漏洞详情…

智能助手(利用GPT搭建智能系统)

项目介绍 本项目旨在打造一个基于通义千问模型的智能助手&#xff0c;能够理解用户指令并自动生成可执行的 JavaScript 代码。该代码可直接调用预设接口&#xff0c;完成指定操作&#xff0c;并返回执行结果。通过大模型的理解与生成能力&#xff0c;实现从自然语言到接口调用…

【源码+文档+调试讲解】基于web的运动健康小程序的设计与实现y196

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

临床项目计划框架

一、项目概述 1.1 项目名称 项目名称:评估XX药物在YY患者中安全性和有效性的III期随机对照试验 1.2 项目背景与立项依据 1.2.1 研究背景 简述疾病负担、当前治疗现状、未满足的医疗需求,为项目开展提供背景支持。 1.2.2 科学依据 总结前期研究结果、理论基础、研究假设的形…

Hoare逻辑与分离逻辑:从程序验证到内存推理的演进

文章目录 引言一、Hoare逻辑基础&#xff1a;程序正确性的形式化验证&#x1f330; 例子&#xff1a;简单赋值语句的Hoare逻辑验证&#x1f330; 例子&#xff1a;条件语句的Hoare逻辑验证 二、分离逻辑&#xff1a;Hoare逻辑在内存管理中的扩展&#x1f50d; 分离逻辑的核心扩…

Tomcat Maven 插件

在 Maven 项目中&#xff0c;可以使用 Tomcat Maven 插件&#xff08;tomcat7-maven-plugin 或 tomcat-maven-plugin&#xff09;来直接部署 WAR 文件到 Tomcat 服务器&#xff0c;而无需手动复制 WAR 文件到 webapps 目录。以下是详细的使用方法&#xff1a; 1. 配置 Tomcat M…