SpringBoot+AOP+Redission实战分布式锁

news2024/10/7 14:31:33

文章目录

  • 前言
  • 一、Redission是什么?
  • 二、使用场景
  • 三、代码实战
    • 1.项目结构
    • 2.类图
    • 3.maven依赖
    • 4.yml
    • 5.config
    • 6.annotation
    • 7.aop
    • 8.model
    • 9.service
  • 四、单元测试
  • 总结


前言

在集群环境下非单体应用存在的问题:JVM锁只能控制本地资源的访问,无法控制多个JVM间的资源访问,所以需要借助第三方中间件来控制整体的资源访问,redis是一个可以实现分布式锁,保证AP的中间件,可以采用setnx命令进行实现,但是在实现细节上也有很多需要注意的点,比如说获取锁、释放锁时机、锁续命问题,而redission工具能够有效降低实现分布式锁的复杂度,看门狗机制有效解决锁续命问题。


一、Redission是什么?

Redisson是一个用于Java的Redis客户端,它提供了许多分布式对象和服务,使得在Java应用中使用Redis变得更加便捷。Redisson提供了对Redis的完整支持,并附带了一系列的功能,如分布式锁、分布式集合、分布式对象、分布式队列等。


二、使用场景

  1. 分布式锁:Redisson提供了强大的分布式锁机制,通过基于Redis的分布式锁,可以保证在分布式环境下的数据一致性。常见的使用场景包括分布式任务调度、防止重复操作、资源竞争控制等。
  2. 分布式集合:Redisson提供了一系列的分布式集合实现,如Set、List、Queue、Deque等。这些集合的特点是数据分布在多台机器上,并且支持并发访问,适用于需要在多个节点之间共享数据的场景。
  3. 分布式对象:Redisson支持将普通Java对象转换为可在Redis中存储和操作的分布式对象。这些对象可以跨JVM进行传输,并保持一致性。使用分布式对象,可以方便地实现分布式缓存、会话管理等功能。
  4. 分布式队列:Redisson提供了高级的分布式队列实现,支持公平锁和非公平锁的排队方式。分布式队列可以在多个线程和多个JVM之间进行数据传输,适用于消息队列、异步任务处理等场景。
  5. 分布式信号量、锁定和闭锁:Redisson提供了分布式信号量、可重入锁和闭锁等机制,用于实现更复杂的并发控制需求。这些工具能够有效地管理并发访问,确保在分布式环境下的数据操作的正确性。

除了以上提到的功能,Redisson还提供了许多其他的分布式应用场景所需的功能组件,如分布式BitSet、分布式地理位置、分布式发布/订阅等。


三、代码实战

通过aop切面编程,可以降低与业务代码的耦合度,便于拓展和维护

1.项目结构

在这里插入图片描述


2.类图

在这里插入图片描述


3.maven依赖

<dependencies>
   <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Redisson -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.20.0</version>
    </dependency>

    <!-- Spring AOP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <!-- Spring Expression Language (SpEL) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

4.yml

dserver:
  port: 8081

spring:
  redis:
    host: localhost
    port: 6379
    # 如果需要密码认证,请使用以下配置
    # password: your_password

5.config

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

/**
 * @author 28382
 */
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    // 如果有密码认证,请添加以下注解,并修改相应的配置:
    //@Value("${spring.redis.password}")
    //private String redisPassword;

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
        // 如果有密码认证,请添加以下配置:
        //config.useSingleServer().setPassword(redisPassword);
        return Redisson.create(config);
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author 28382
 */
@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

6.annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 28382
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {

    // 自定义业务keys
    String[] keys() default {};

    // 租赁时间 单位:毫秒
    long leaseTime() default 30000;

    // 等待时间 单位:毫秒
    long waitTime() default 3000;

}

7.aop

支持解析 SpEL

import com.mxf.code.annotation.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class DistributedLockAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(distributedLock)")
    public Object lockMethod(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        // 获取自定义业务keys
        String[] keys = distributedLock.keys();
        // 租赁时间
        long leaseTime = distributedLock.leaseTime();
        // 等待时间
        long waitTime = distributedLock.waitTime();

        // 创建参数名发现器
        ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

        // 获取方法参数名
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

        // 创建 SpEL 解析器
        ExpressionParser expressionParser = new SpelExpressionParser();

        // 创建 SpEL 上下文
        EvaluationContext evaluationContext = new StandardEvaluationContext();

        // 设置方法参数值到 SpEL 上下文中
        Object[] args = joinPoint.getArgs();
        if (parameterNames != null && args != null && parameterNames.length == args.length) {
            for (int i = 0; i < parameterNames.length; i++) {
                evaluationContext.setVariable(parameterNames[i], args[i]);
            }
        }

        // 构建完整的锁键名
        StringBuilder lockKeyBuilder = new StringBuilder();
        if (keys.length > 0) {
            for (String key : keys) {
                if (StringUtils.hasText(key)) {
                    try {
                        // 解析 SpEL 表达式获取属性值
                        Object value = expressionParser.parseExpression(key).getValue(evaluationContext);
                        lockKeyBuilder.append(value).append(":");
                    } catch (SpelEvaluationException ex) {
                        // 如果解析失败,则使用原始字符串作为属性值
                        LiteralExpression expression = new LiteralExpression(key);
                        lockKeyBuilder.append(expression.getValue()).append(":");
                    }
                }
            }
        }
        // 使用方法名作为最后一部分键名
        lockKeyBuilder.append(methodSignature.getName());
        String fullLockKey = lockKeyBuilder.toString();
        // 获取 Redisson 锁对象
        RLock lock = redissonClient.getLock(fullLockKey);
        // 尝试获取分布式锁
        // boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)
        boolean success = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);

        if (success) {
            try {
                // 执行被拦截的方法
                return joinPoint.proceed();
            } finally {
                // 释放锁
                lock.unlock();
            }
        } else {
            log.error("Failed to acquire distributed lock");
            // 获取锁超时,抛出异常
            throw new RuntimeException("Failed to acquire distributed lock");
        }
    }

}

8.model


import lombok.Data;

/**
 * @author 28382
 */
@Data
public class User {
    private Long id;
    private String name;
    private String address;

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

9.service

import com.mxf.code.annotation.DistributedLock;
import com.mxf.code.model.User;
import org.springframework.stereotype.Service;

/**
 * @author 28382
 */
@Service
public class UserService {
    int i = 0;

    @DistributedLock
    public void test01() {
        System.out.println("执行方法1 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);
        sleep();
    }

    @DistributedLock(keys = "myKey",leaseTime = 30L)
    public void test02() {
        System.out.println("执行方法2 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);
        sleep();
    }

    @DistributedLock(keys = "#user.id")
    public User test03(User user) {
        System.out.println("执行方法3 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);
        sleep();
        return user;
    }

    @DistributedLock(keys = {"#user.id", "#user.name"}, leaseTime = 5000, waitTime = 5000)
    public User test04(User user) {
        System.out.println("执行方法4 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);
        sleep();
        return user;
    }

    private void sleep() {
        // 模拟业务耗时
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}

四、单元测试

import com.mxf.code.model.User;
import com.mxf.code.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


@SpringBootTest(classes = SpringBootLockTest.class)
@SpringBootApplication
public class SpringBootLockTest {
    @Autowired
    UserService userService;

    private static final Random RANDOM = new Random();

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLockTest.class, args);
    }

    @Test
    public void test01() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Runnable task = () -> userService.test01();
        for (int i = 0; i < 100; i++) {
            executorService.submit(task);
        }
        Thread.sleep(10000);
    }

    @Test
    public void test02() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Runnable task = () -> userService.test02();
        for (int i = 0; i < 100; i++) {
            executorService.submit(task);
        }
        Thread.sleep(10000L);
    }

    @Test
    public void test03() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Runnable task = () -> userService.test03(new User(1L, "name"));
        for (int i = 0; i < 100; i++) {
            executorService.submit(task);
        }
        Thread.sleep(10000L);
    }

    @Test
    public void test04() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Runnable task = () -> userService.test04(new User(1L, "name"));
        for (int i = 0; i < 100; i++) {
            executorService.submit(task);
        }
        Thread.sleep(100000L);
    }
}

test01

在这里插入图片描述

test02

在这里插入图片描述

test03

在这里插入图片描述

test04

在这里插入图片描述

总结

可以在项目中单独建立一个Module,需要的子系统直接引入,在需要加分布式的业务代码方法上添加注解及配注解属性值即可,存在一个潜在的问题就是如果redis使用主从架构,在主节点和从节点同步加锁信息时主节点挂掉,这时选取一个暂未同步完整节点信息的从节点作为主节点时,存在一定锁失效的问题,这是可以考虑红锁或者zookeeper实现强一致性。

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

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

相关文章

AMEYA--大唐恩智浦DNB1168助力CTC和CTP新电池技术价值突破

“里程焦虑”是新能源汽车行业中绕不开的话题。从短期来看&#xff0c;电池材料难有突破性的进展&#xff0c;电池能量密度提升受限。那么&#xff0c;对动力电池进行结构优化、为电芯“腾出”更大的空间&#xff0c;就成为提高汽车续航能力的不二选择。 动力电池的瘦身之路 传…

Stable diffusion从入门到进阶教程

全网最全Stable Diffusion教程&#xff0c;耗时三个月制作。 基础教程 Stable Diffusion知识大纲05-26 10:57 Stable Diffusion安装教程06-09 15:58 抖音资料发放处昨天 14:46 比较好用的 AI绘画网站整理05-26 10:24 Stable Diffusion配置要求05-08 18:01 StableDiffusio…

15 springboot项目——thymeleaf语法与关闭模板引擎

15.1 thymeleaf语法 在html文件中&#xff0c;有些是需要使用本地的css样式&#xff0c;使用thymeleaf语法加载&#xff1a; 首先对head标签上面的html标签进行更改&#xff1a; <html lang"en" xmlns:th"http://www.thymeleaf.org"> 其次&#xff…

2024考研408-计算机网络 第三章-数据链路层学习笔记

文章目录 前言一、数据链路层的功能1.1、数据链路层的研究思想1.2、数据链路层基本概念1.3、数据链路层功能概述&#xff08;5个功能&#xff09; 二 、组帧2.1、封装成帧以及发送帧的过程&#xff08;包含名词解释&#xff09;2.2、实现透明传输及四种组帧方法2.2.1、什么是透…

shell 自动创建磁盘分区 PV 、VG、 LV

设计思路&#xff1a; 1、创建磁盘分区变量集合 devName("vdb" "vdc" "vdd" "vde") 2、创建for循环&#xff0c;磁盘分区数量 /dev/vdb0~3&#xff0c;用于在for循环中if判断磁盘分区是否已经存在 if 判断条件 [[ -b "目录名称…

LCD驱动深入分析

很多人都会说操纵lcd显示就是操纵framebuffer&#xff0c;表面上来看是这样的。实际上是frambuffer就是linux内核驱动申请的一片内存空间&#xff0c;然后lcd内有一片sram&#xff0c;cpu内部有个lcd控制器&#xff0c;它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram…

腾讯云 +WordPress 搭建个人网站

搭建个人网站 准备服务器配置宝塔面板开放端口权限 部署WordPress访问效果 准备服务器 腾讯云网址 购买腾讯云服务器有新人优惠&#xff0c;第一次购买会比较便宜&#xff08;赚的是你续费的钱&#xff09;&#xff0c;我这里买了轻量的三年是408&#xff0c;还是比较实惠的&a…

端口映射教程vs快解析内网穿透

随着社会信息化的发展&#xff0c;很多人都开始关注网络问题&#xff0c;掌握一些基础的网络知识是非常有必要的。其中&#xff0c;端口映射作为一项重要的技术&#xff0c;在网络通信中起到了至关重要的作用。 端口映射在现实生活中有着广泛的应用。如果你是一位游戏爱好者&a…

Linux--打印到显示器的内容输出重定向到文件里的代码

// ./myproc hello int main(int argc, char* argv[]) {if (argc ! 2){return 2;}int fd open("log.txt", O_WRONLY | O_CREAT | O_TRUNC);if (fd < 0){perror("open");return 1;}dup2(fd, 1);fprintf(stdout, "%s\n", argv[1]); } 运行结果…

人工智能数据获取与数据管理指南

成功部署AI实践典范 人工智能的部署离不开大规模高质量训练数据的注入&#xff0c;大数据的管理制度也随着人工智能的发展而日益完善&#xff0c;数据采集与治理更是企业做AI部署策略时的两大复杂因素。 有几项最佳实践可以作为构建和部署有效的AI解决方案的典范。建立长期全面…

C语言笔试题训练【第一天】

目录 第一题 第二题 第三题 第四题 第五题 大家好&#xff0c;我是纪宁。 从今天开始博主会日更一些经典的C语言笔试题&#xff0c;持续20天左右。题目类型为5道选择题加2道编程题&#xff0c;希望能和大家一起进步。 第一题 1.读程序&#xff0c;下面程序正确的输出是&…

GD32F103VE侵入事件

GD32F103VE的TAMPER引脚(PC13)&#xff0c;当PC13输入低电平时&#xff0c;会产生一个侵入检测事件。它会将所有“数据备份寄存器”内容清除。 这个功能有什么用&#xff1f; 一是防止被人开壳&#xff0c;抄袭。二是自毁功能。 直奔主题&#xff0c;多一句就是浪费时间。测试…

nsqd的架构及源码分析

文章目录 一 nsq的整体代码结构 二 回顾nsq的整体架构图 三 nsqd进程的作用 四 nsqd启动流程的源码分析 五 本篇博客总结 在博客 nsq整体架构及各个部件作用详解_YZF_Kevin的博客-CSDN博客 中我们讲了nsq的整体框架&#xff0c;各个部件的大致作用。如果没看过的&…

cloudstack远程调试

前置条件&#xff1a;服务器安装好cloudstack的management、agent; 1、managemeng、agent启动服务文件 packaging/systemd cloudstack-agent.default # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTIC…

openmp和avx配置

实际场景&#xff1a; 项目中数据拷贝慢&#xff08;使用的是memcpy&#xff09;&#xff0c;希望能加速拷贝&#xff0c;所以尝试了使用avx的流方式&#xff0c;和openmp方式处理 问题1&#xff1a; 调用avx是报错 error: inlining failed in call to always_inline ‘__m512…

亲测有效!帮你更方便更舒服使用ubuntu20.04!!!

今天要记录的是如何更舒服的使用ubuntu20.04&#xff0c;全部内容就在上面这张图里&#xff0c;包括三方面&#xff1a;1、ubuntu美化&#xff1b;2、ubuntu扩展&#xff1b;3、必备软件。 1、ubuntu美化 这部分内容可以直接参考&#xff1a;这位大佬&#xff0c;讲的很详细也…

gRPC三种Java客户端性能测试实践

本篇文章只做性能测试实践&#xff0c;不会测试各类状况下极限性能&#xff0c;所以硬件配置和软件参数就不单独分享了。 服务端 依旧采用了fun_grpc项目的SDK内容。服务端代码如下&#xff1a; package com.funtester.grpc;import com.funtester.frame.execute.ThreadPoolU…

Python实现GA遗传算法优化卷积神经网络分类模型(CNN分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世…

验证码安全志:AIGC+集成环境信息信息检测

目录 知己知彼&#xff0c;黑灰产破解验证码的过程 AIGC加持&#xff0c;防范黑灰产的破解 魔高一丈&#xff0c;黑灰产AIGC突破常规验证码 双重防护&#xff0c;保障验证码安全 黑灰产经常采用批量撞库方式登录用户账号&#xff0c;然后进行违法违规操作。 黑灰产将各种方…

RL — 强化学习算法概述

一、说明 在本系列中&#xff0c;我们检查了许多强化学习&#xff08;RL&#xff09;算法&#xff0c;例如&#xff0c;MoJoCo任务的策略梯度方法&#xff0c;Atari游戏的DQN和机器人控制的基于模型的RL。虽然许多算法都是针对特定领域引入的&#xff0c;但这种联系只能是遗留的…