Spring Boot牵手Redisson:分布式锁实战秘籍

news2025/2/11 18:31:58

一、引言

在这里插入图片描述

在当今的分布式系统架构中,随着业务规模的不断扩大和系统复杂度的日益增加,如何确保多个服务节点之间的数据一致性和操作的原子性成为了一个至关重要的问题。在单机环境下,我们可以轻松地使用线程锁或进程锁来控制对共享资源的访问,但在分布式系统中,由于各个服务节点分布在不同的物理或逻辑位置,它们之间的内存并不共享,传统的锁机制无法直接应用。这时候,分布式锁应运而生。

分布式锁作为一种跨节点的同步机制,能够有效地控制多个进程或线程对共享资源的访问,确保在同一时刻只有一个客户端能够获取到锁并执行临界区代码,从而避免数据不一致和竞态条件等问题。它在许多场景中都发挥着关键作用,比如电商系统中的库存扣减、订单处理,分布式任务调度系统中的任务分配与执行,以及缓存数据的更新等。

在众多分布式锁的实现方案中,基于 Redis 的方案因其高性能、简单易用等特点而被广泛采用。而 Redisson 作为一个在 Redis 基础上实现的 Java 驻内存数据网格(In-Memory Data Grid),不仅提供了对 Redis 各种数据结构的便捷访问接口,还封装了一系列分布式系统常用的高级功能,其中就包括功能强大、易于使用的分布式锁实现。

Spring Boot 则是当前最流行的 Java 开发框架之一,它通过自动配置和约定大于配置的理念,极大地简化了 Spring 应用的开发过程,使得开发者能够快速搭建出高效、稳定的应用程序。

将 Redisson 与 Spring Boot 进行集成,能够充分发挥两者的优势,为我们提供一种简单、高效的分布式锁解决方案。在本文中,我们将深入探讨如何在 Spring Boot 项目中集成 Redisson 来实现分布式锁,并通过实际的代码示例和详细的解释,帮助大家理解其原理和使用方法,同时也会分享一些在实际应用中可能遇到的问题及解决方案 。

二、认识 Redisson 与分布式锁

2.1 Redisson 简介

Redisson 是一个基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid)和分布式锁服务。它不仅仅是对 Redis 的简单封装,更是提供了一系列丰富的分布式 Java 数据结构和服务,使得在 Java 应用中使用 Redis 变得更加便捷和强大。Redisson 支持多种 Redis 的部署模式,包括单节点、集群、哨兵和主从模式,这使得它能够适应各种不同规模和复杂度的分布式系统。

在 Redisson 中,你可以像使用本地 Java 对象一样使用各种分布式数据结构,如分布式集合(RSetRListRMap等)、分布式队列(RQueueRDeque等)、分布式锁(RLockRReadWriteLock等)以及分布式原子变量(RAtomicLongRAtomicDouble等)。这种高度的抽象和封装极大地简化了分布式系统的开发过程,让开发者可以专注于业务逻辑的实现,而无需过多关注底层的分布式细节。

例如,在使用 Redisson 的分布式锁时,开发者只需要通过简单的RLock lock = redisson.getLock("myLock"); lock.lock();就可以获取一个分布式锁,而无需手动编写复杂的 Redis 命令和逻辑来实现锁的获取、释放以及锁的过期处理等功能 。

2.2 分布式锁的作用

在分布式系统中,多个节点(如不同的服务器、进程或线程)可能会同时访问和操作共享资源,如数据库中的数据、缓存中的数据或者文件系统中的文件等。如果没有有效的同步机制,就可能会出现数据不一致、竞态条件(Race Condition)等问题。分布式锁的作用就是解决这些问题,它通过一种跨节点的同步机制,确保在同一时刻只有一个客户端能够获取到锁并执行临界区代码,从而避免多个客户端同时对共享资源进行并发访问和修改,保证数据的一致性和完整性。

以电商系统中的库存扣减为例,如果没有分布式锁,当多个用户同时下单购买同一件商品时,可能会出现多个订单同时扣减库存的情况,导致库存数量出现负数,从而引发超卖问题。而使用分布式锁后,只有获取到锁的订单处理线程能够执行库存扣减操作,其他线程需要等待锁的释放,这样就可以确保库存扣减操作的原子性和正确性,避免超卖现象的发生 。

2.3 常见分布式锁实现方式对比

在分布式系统中,除了基于 Redis + Redisson 实现分布式锁外,还有其他常见的实现方式,如基于 MySQL、ZooKeeper 等。下面我们来对比一下这几种实现方式的优缺点:

MySQL 实现分布式锁:利用 MySQL 的表锁或行锁机制,通过在数据库中创建一个锁表,使用唯一索引或FOR UPDATE语句来实现分布式锁。这种方式的优点是对于已经使用 MySQL 的系统来说,不需要引入额外的中间件,实现相对简单。然而,它的缺点也很明显,由于数据库的读写操作性能相对较低,在高并发场景下,会对数据库造成较大的压力,容易成为性能瓶颈。同时,数据库的可用性也会影响分布式锁的可靠性,如果数据库出现故障,整个分布式锁机制将无法正常工作 。

ZooKeeper 实现分布式锁:ZooKeeper 是一个分布式协调服务,它利用其节点的特性来实现分布式锁。客户端通过在 ZooKeeper 中创建临时顺序节点来竞争锁,并且可以通过监听节点的变化来实现锁的等待和通知机制。ZooKeeper 实现的分布式锁具有较高的可靠性和一致性,能够保证锁的公平性,即按照请求锁的顺序依次获取锁。但是,ZooKeeper 的性能相对 Redis 来说较低,因为它需要进行网络通信和节点的创建、删除等操作,这会带来一定的延迟。此外,ZooKeeper 的部署和维护相对复杂,需要搭建集群来保证高可用性 。

Redis 实现分布式锁:Redis 是一个高性能的内存数据库,它利用SET命令的NX(Not eXists)和PX(过期时间)选项来实现锁的原子获取,通过DEL命令来释放锁。Redis 实现分布式锁的优点是性能高,获取锁和释放锁的操作非常快,因为它是基于内存操作的。同时,Redis 支持锁的自动过期,这可以有效降低死锁的风险。然而,原生的 Redis 分布式锁实现不是真正意义上的公平锁,无法保证请求锁的顺序。在 Redis 集群模式下,由于数据的分布式存储和同步机制,没有内置的分布式锁支持,需要更为复杂的实现来保证锁的一致性 。

而 Redis + Redisson 的组合则充分发挥了 Redis 的高性能和 Redisson 的丰富功能与便捷性。Redisson 对 Redis 的分布式锁进行了封装和扩展,提供了更高级的锁功能,如可重入锁、公平锁、读写锁等,并且在 Redisson 的实现中,已经考虑了各种复杂的分布式场景和异常情况,使得分布式锁的使用更加安全和可靠。同时,Redisson 的 API 设计简洁易用,大大降低了开发者使用分布式锁的难度 。

三、Spring Boot 集成 Redisson 的步骤

3.1 创建 Spring Boot 项目

如果你是创建全新的 Spring Boot 项目,可以使用 Spring Initializer 来快速搭建项目骨架。打开你的 IDE(如 IntelliJ IDEA、Eclipse 等),在创建新项目时选择 Spring Initializer 选项。在向导中,填写项目的基本信息,如 Group、Artifact、Name 等,然后选择你需要的依赖,这里我们至少需要添加 Spring Web 依赖,方便后续进行测试。

如果你是在现有项目中集成 Redisson,确保项目已经是一个 Spring Boot 项目,并且已经配置好了基本的 Spring 依赖和项目结构 。

3.2 添加 Redisson 依赖

在项目的pom.xml文件中添加 Redisson 的依赖。如果你使用的是 Maven,添加以下依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.19.3</version>
</dependency>

在选择 Redisson 版本时,要注意其与 Spring Boot 以及 Redis 的版本兼容性。可以参考 Redisson 官方文档或者相关的版本兼容性对照表,以确保选择的版本能够稳定运行。例如,Spring Boot 2.5.x 版本建议搭配 Redisson 3.16.x 系列版本 。

3.3 配置 Redisson

application.propertiesapplication.yml文件中配置 Redis 的连接信息。如果使用application.properties,配置如下:

# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器端口
spring.redis.port=6379
# Redis密码(如果有)
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=3000

如果使用application.yml,配置如下:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 
    timeout: 3000

这些配置将被redisson-spring-boot-starter自动读取并用于创建 Redisson 客户端连接。如果 Redis 部署在集群环境或者使用了哨兵模式,还需要相应地调整配置 。

3.4 编写配置类(可选)

如果你使用的是redisson-spring-boot-starter,通常不需要额外编写配置类,因为 Starter 会自动进行配置。但如果有一些特殊的配置需求,比如自定义 Redisson 的线程池大小、编解码器等,或者你没有使用 Starter 方式集成 Redisson,就需要编写配置类来创建RedissonClient实例。

以下是一个配置类的示例:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
   

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
   
        Config config = new Config();
        // 使用单机模式连接Redis
        config.useSingleServer()
             .setAddress("redis://127.0.0.1:6379")
             .setPassword("");
        return Redisson.create(config);
    }
}

在这个配置类中,我们创建了一个RedissonClient实例,并将其注册为 Spring 的 Bean。destroyMethod = "shutdown"指定了在 Spring 容器关闭时,自动调用RedissonClientshutdown方法来释放资源 。

3.5 测试集成是否成功

编写一个简单的测试代码来验证 Redisson 是否集成成功。可以创建一个 Spring 的 Service 类,在其中注入RedissonClient,并进行一些简单的操作,如获取一个分布式锁或者操作一个分布式集合。

以下是一个测试获取分布式锁的示例:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RedissonTestService {
   

    @Autowired
    private RedissonClient redissonClient;

    public void testLock() {
   
        // 获取一个名为"myLock"的分布式锁
        RLock lock = redissonClient.getLock("myLock");
        try {
   
            // 尝试获取锁,这里可以设置等待时间和锁的过期时间
            boolean isLocked = lock.tryLock(10, 60, java.util.concurrent.TimeUnit.SECONDS);
            if (isLocked) {
   
                // 获取到锁,执行临界区代码
                System.out.println("成功获取到锁,执行临界区代码");
                // 模拟业务逻辑处理
                Thread.sleep(3000);
            } else {
   
                // 未获取到锁
                System.out.println("未能获取到锁");
            }
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        } finally {
   
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
   
                lock.unlock();
                System.out.println("锁已释放");
            }
        }
    }
}

然后可以在测试类中调用这个方法进行测试:

import org.junit.jupiter.api.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

制药行业 BI 可视化数据分析方案

一、行业背景 随着医药行业数字化转型的深入&#xff0c;企业积累了海量的数据&#xff0c;包括销售数据、生产数据、研发数据、市场数据等。如何利用这些数据&#xff0c;挖掘其价值&#xff0c;为企业决策提供支持&#xff0c;成为医药企业面临的重大挑战。在当今竞争激烈的…

[学习笔记] Kotlin Compose-Multiplatform

Compose-Multiplatform 原文&#xff1a;https://github.com/zimoyin/StudyNotes-master/blob/master/compose-multiplatform/compose.md Compose Multiplatform 是 JetBrains 为桌面平台&#xff08;macOS&#xff0c;Linux&#xff0c;Windows&#xff09;和Web编写Kotlin UI…

Golang 并发机制-7:sync.Once实战应用指南

Go的并发模型是其突出的特性之一&#xff0c;但强大的功能也带来了巨大的责任。sync.Once是由Go的sync包提供的同步原语。它的目的是确保一段代码只执行一次&#xff0c;而不管有多少协程试图执行它。这听起来可能很简单&#xff0c;但它改变了并发环境中管理一次性操作的规则。…

【AI实践】Cursor上手-跑通Hello World和时间管理功能

背景 学习目的&#xff1a;熟悉Cursor使用环境&#xff0c;跑通基本开发链路。 本人背景&#xff1a;安卓开发不熟悉&#xff0c;了解科技软硬件常识 实践 基础操作 1&#xff0c;下载安装安卓Android Studio 创建一个empty project 工程&#xff0c;名称为helloworld 2&am…

【多模态大模型】系列4:目标检测(ViLD、GLIP)

目录 1 ViLD2 GLIP 1 ViLD OPEN-VOCABULARY OBJECT DETECTION VIA VISION AND LANGUAGE KNOWLEDGE DISTILLATION 从标题就能看出来&#xff0c;作者是把CLIP模型当成一个Teacher&#xff0c;去蒸馏他自己的网络&#xff0c;从而能Zero Shot去做目标检测。 现在的目标检测数据…

计算机网络结课设计:通过思科Cisco进行中小型校园网搭建

上学期计算机网络课程的结课设计是使用思科模拟器搭建一个中小型校园网&#xff0c;当时花了几天时间查阅相关博客总算是做出来了&#xff0c;在验收后一直没管&#xff0c;在寒假想起来了简单分享一下&#xff0c;希望可以给有需求的小伙伴一些帮助 目录 一、设计要求 二、…

从零到一:基于Rook构建云原生Ceph存储的全面指南(下)

接上篇&#xff1a;《从零到一&#xff1a;基于Rook构建云原生Ceph存储的全面指南&#xff08;上&#xff09;》 链接: link 六.Rook部署云原生CephFS文件系统 6.1 部署cephfs storageclass cephfs文件系统与RBD服务类似&#xff0c;要想在kubernetes pod里使用cephfs&#…

AutoMQ 如何实现没有写性能劣化的极致冷读效率

前言 追赶读&#xff08;Catch-up Read&#xff0c;冷读&#xff09;是消息和流系统常见和重要的场景。 削峰填谷&#xff1a;对于消息来说&#xff0c;消息通常用作业务间的解耦和削峰填谷。削峰填谷要求消息队列能将上游发送的数据堆积住&#xff0c;让下游在容量范围内消费…

【Rabbitmq篇】高级特性----TTL,死信队列,延迟队列

目录 一.TTL ???1.设置消息的TTL 2.设置队列的TTL 3.俩者区别? 二.死信队列 定义&#xff1a; 消息成为死信的原因&#xff1a; 1.消息被拒绝&#xff08;basic.reject 或 basic.nack&#xff09; 2.消息过期&#xff08;TTL&#xff09; 3.队列达到最大长度? …

【Java】多线程和高并发编程(三):锁(中)深入ReentrantLock

文章目录 3、深入ReentrantLock3.1 ReentrantLock和synchronized的区别3.2 AQS概述3.3 加锁流程源码剖析3.3.1 加锁流程概述3.3.2 三种加锁源码分析3.3.2.1 lock方法3.3.2.2 tryLock方法3.3.2.3 lockInterruptibly方法 3.4 释放锁流程源码剖析3.4.1 释放锁流程概述3.4.2 释放锁…

电路笔记(元器件):AD 5263数字电位计(暂记)

AD5263 是四通道、15 V、256位数字电位计&#xff0c;可通过SPI/I2C配置具体电平值。 配置模式&#xff1a; W引脚作为电位器的抽头&#xff0c;可在A-B之间调整任意位置的电阻值。也可将W与A(或B)引脚短接&#xff0c;A-W间的电阻总是0欧姆&#xff0c;通过数字接口调整电位器…

webpack【初体验】使用 webpack 打包一个程序

打包前 共 3 个文件 dist\index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Webpack 示例&…

VMware安装CentOS 7(全网超详细图文保姆版教程)

文章目录 一、下载及安装 VMware1.1 VMware下载1.2 CentOS下载 二、搭建虚拟机环境2.1 创建新虚拟机2.2 选择自定义2.3 选择虚拟机硬件兼容性2.4 选择稍后安装操作系统2.5 选择Linux系统 版本选择 centos 7 64位2.6 设备你虚拟机的名字和保存位置&#xff08;保存位置建议在编辑…

mysql BUG 导致 show processlist 有大量的show slave stauts 处于init状态

一、详细报错信息&#xff1a; 1、执行show slave status\G 卡住 && stop slave也卡住 2、show processlist 发现 Waiting for commit lock NULL 锁 3、错误日志报错主备同步用户认证失败 二、报错原因&#xff08;分析过程&#xff09;&#xff1a; 1、排查备库日志…

机器学习在癌症分子亚型分类中的应用

学习笔记&#xff1a;机器学习在癌症分子亚型分类中的应用——Cancer Cell 研究解析 1. 文章基本信息 标题&#xff1a;Classification of non-TCGA cancer samples to TCGA molecular subtypes using machine learning发表期刊&#xff1a;Cancer Cell发表时间&#xff1a;20…

从MySQL优化到脑力健康:技术人与效率的双重提升

文章目录 零&#xff1a;前言一&#xff1a;MySQL性能优化的核心知识点1. 索引优化的最佳实践实战案例&#xff1a; 2. 高并发事务的处理机制实战案例&#xff1a; 3. 查询性能调优实战案例&#xff1a; 4. 缓存与连接池的优化实战案例&#xff1a; 二&#xff1a;技术工作者的…

Qt:项目文件解析

目录 QWidget基础项目文件解析 .pro文件解析 widget.h文件解析 widget.cpp文件解析 widget.ui文件解析 main.cpp文件解析 认识对象模型 窗口坐标系 QWidget基础项目文件解析 .pro文件解析 工程新建好之后&#xff0c;在工程目录列表中有⼀个后缀为 ".pro" …

react使用if判断

1、第一种 function Dade(req:any){console.log(req)if(req.data.id 1){return <span>66666</span>}return <span style{{color:"red"}}>8888</span>}2、使用 {win.map((req,index) > ( <> <Dade data{req}/>{req.id 1 ?…

conda 修复 libstdc++.so.6: version `GLIBCXX_3.4.30‘ not found 简便方法

ImportError: /data/home/hum/anaconda3/envs/ipc/bin/../lib/libstdc.so.6: version GLIBCXX_3.4.30 not found (required by /home/hum/anaconda3/envs/ipc/lib/python3.11/site-packages/paddle/base/libpaddle.so) 1. 检查版本 strings /data/home/hum/anaconda3/envs/ipc/…

python学opencv|读取图像(六十)先后使用cv2.erode()函数和cv2.dilate()函数实现图像处理

【1】引言 前序学习进程中&#xff0c;先后了解了使用cv2.erode()函数和cv2.dilate()函数实现图像腐蚀和膨胀处理的效果&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;五十八&#xff09;使用cv2.erode()函数实现图像腐蚀处理-CSDN博客 pytho…