SpringBoot + Lock4j实现高性能分布式锁

news2024/10/7 14:30:37

1. 简介

  在分布式业务开发中,很多场景都需要添加分布式锁。在具体实践过程中,研发人员都需要自行实现,导致实现方式不统一,代码风格迥异,难以维护。
  在Mybatis-Plus生态中,Lock4j提供了支持redissionredisTemplatezookeeper的分布式锁组件,简单易用,功能强大,扩展性强。
  Gitee地址:https://gitee.com/baomidou/lock4j

2. 简单示例

  以redisTemplate作为分布式锁底层实现编写示例代码。其他两种方案可参考官网文档实现。

  • 创建项目
  • 修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.c3stones</groupId>
    <artifactId>spring-boot-lock4j-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.7</version>
    </parent>

    <dependencies>
        <!--若使用redisTemplate作为分布式锁底层-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 配置redis信息
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    
# Lock4j配置
#lock4j:
#  acquire-timeout: 3000 #等待时长。默认值3s,可不设置
#  expire: 30000 #过期时间,防止死锁。默认值30s,可不设置
#  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
#  lock-key-prefix: lock4j #锁key前缀。默认值lock4j,可不设置
  • 统一响应类
      发生异常时,由全局异常处理类将异常信息格式化为统一响应类输出。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 响应工具类
 *
 * @param <T>
 * @author CL
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private Boolean code;

    private String msg;

    private T data;

    public static <T> R<T> ok() {
        return restResult(null, Boolean.TRUE, null);
    }

    public static <T> R<T> ok(T data) {
        return restResult(data, Boolean.TRUE, null);
    }

    public static <T> R<T> ok(T data, String msg) {
        return restResult(data, Boolean.TRUE, msg);
    }

    public static <T> R<T> failed() {
        return restResult(null, Boolean.FALSE, null);
    }

    public static <T> R<T> failed(String msg) {
        return restResult(null, Boolean.FALSE, msg);
    }

    public static <T> R<T> failed(T data) {
        return restResult(data, Boolean.FALSE, null);
    }

    public static <T> R<T> failed(T data, String msg) {
        return restResult(data, Boolean.FALSE, msg);
    }

    private static <T> R<T> restResult(T data, Boolean code, String msg) {
        R<T> apiResult = new R<>();
        apiResult.setCode(code);
        apiResult.setData(data);
        apiResult.setMsg(msg);
        return apiResult;
    }

}
  • 全局异常处理类
/**
 * 全局异常处理
 *
 * @author CL
 */
@Log4j2
@RestControllerAdvice
public class WebExceptionAdvice {

    /**
     * 异常处理
     *
     * @param ex 异常
     * @return {@link R}
     */
    @ExceptionHandler(value = Exception.class)
    public R<?> errorHandler(Exception ex) {
        log.error(ex);
        return R.failed(ex.getMessage());
    }

}
  • 自定义分布式锁失败策略(可选)
import com.baomidou.lock.LockFailureStrategy;
import com.baomidou.lock.exception.LockFailureException;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 自定义获取锁失败策略
 *
 * @author CL
 */
@Component
public class MyLockFailureStrategy implements LockFailureStrategy {

    @Override
    public void onLockFailure(String key, Method method, Object[] arguments) {
        throw new LockFailureException("处理中,请稍后");
    }

}
  • 五种测试方法
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.lock.executor.RedisTemplateLockExecutor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 测试Service
 *
 * @author CL
 */
@Service
@RequiredArgsConstructor
public class TestService {

    /**
     * 测试无锁
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    public Long test(long processTime) {
        try {
            Thread.sleep(processTime * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return processTime;
    }

    /**
     * 测试默认分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    @Lock4j
    public Long test1(long processTime) {
        return test(processTime);
    }

    /**
     * 测试自定义配置分布式锁
     * <p>
     * keys: 锁名称,全局唯一<br/>
     * expire: 过期时间,防止死锁<br/>
     * acquireTimeout: 等待时间,获取不到则执行失败策略<br/>
     * </p>
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    @Lock4j(keys = {"#processTime"}, expire = 60000, acquireTimeout = 1000)
    public Long test2(long processTime) {
        return test(processTime);
    }

    private final LockTemplate lockTemplate;

    /**
     * 测试手动获取分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    public Long test3(long processTime) {
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock("custom:" + processTime, 30000L, 2000L, RedisTemplateLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("处理中,请稍后");
        }

        // 获取锁成功
        try {
            test(processTime);
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }

        return processTime;
    }

    /**
     * 测试5秒内只能访问1次
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    @Lock4j(keys = {"#processTime"}, acquireTimeout = 0, expire = 5000, autoRelease = false)
    public Long test4(long processTime) {
        return processTime;
    }

}
  • 测试接口
import com.c3stones.common.R;
import com.c3stones.service.TestService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试Controller
 *
 * @author CL
 */
@RestController
@RequestMapping()
@RequiredArgsConstructor
public class TestController {

    private final TestService testService;

    /**
     * 测试无锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test")
    public R<Long> test(long processTime) {
        return R.ok(testService.test(processTime));
    }

    /**
     * 测试默认分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test1")
    public R<Long> test1(long processTime) {
        return R.ok(testService.test1(processTime));
    }

    /**
     * 测试自定义配置分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test2")
    public R<Long> test2(long processTime) {
        return R.ok(testService.test2(processTime));
    }

    /**
     * 测试手动获取分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test3")
    public R<Long> test3(long processTime) {
        return R.ok(testService.test3(processTime));
    }

    /**
     * 测试5秒内只能访问1次
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test4")
    public R<Long> test4(long processTime) {
        return R.ok(testService.test4(processTime));
    }

}
  • 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author CL
 */
@SpringBootApplication
public class Application {

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

}

3. 测试

  测试方案:手动快速发送两次相同的请求,假设业务处理时长为5秒,查看响应结果是否满足分布式锁。

  • 测试无锁
      相同条件:第一次请求和第二次请求都不会加锁,等待业务处理完成返回结果。
  • 测试默认分布式锁
      相同条件:第一次请求会加锁,等待业务处理完成返回结果并释放锁;第二次请求默认等待3秒获取锁,若获取锁失败则执行失败策略抛出异常。
  • 测试自定义配置分布式锁
      相同条件:第一次请求会加锁,等待业务处理完成返回结果并释放锁;第二次请求自定义等待1秒获取锁,若获取锁失败则执行失败策略抛出异常。
  • 测试手动获取分布式锁
      相同条件:第一次请求会加锁,等待业务处理完成返回结果并释放锁;第二次请求自定义等待2秒获取锁,若获取锁失败则执行失败策略抛出异常。
  • 测试5秒内只能访问1次
      相同条件:第一次请求会加锁,等待业务处理完成返回结果但不释放锁;第二次请求直接获取锁,若获取锁失败则执行失败策略抛出异常,若获取成功执行业务并加锁。

4. 项目地址

  spring-boot-lock4j-demo

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

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

相关文章

华为OD机试 - 最大排列(C++) | 附带编码思路 【2023】

刷算法题之前必看 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12199283.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 华为OD机试题…

python--matplotlib(4)

前言 Matplotlib画图工具的官网地址是 http://matplotlib.org/ Python环境下实现Matlab制图功能的第三方库&#xff0c;需要numpy库的支持&#xff0c;支持用户方便设计出二维、三维数据的图形显示&#xff0c;制作的图形达到出版级的标准。 其他matplotlib文章 python--matpl…

Spring Cloud Gateway Nacos 实现动态路由

微服务都是互相独立的&#xff0c;假如我们的网关和其他服务都在线上已经运行了好久&#xff0c;这个时候增加了一个微服务&#xff0c;这个时候要通过网关访问的话需要通过修改配置文件来增加路由规则&#xff0c;并且需要重启项目&#xff0c;所以我们需要实现动态路由 方式一…

34. 应用监控【监控端点配置】

当一个 Spring Boot 项目运行时&#xff0c;开发者需要对 Spring Boot 项目进行实时监控来获取项目的运行情况&#xff0c;在项目出错时能够实现自动报警等。 Spring Boot 提供了actuator 来帮助开发者获取应用程序的实时运行数据。开发者可以选择使用 HTTP 端点或JMX来管理和监…

九龙证券|银行资本管理办法迎“大修” 信用风险权重法调整优化

1年期AAA中债商业银行同业存单到期收益率 日前迎来“大修”的商业银行本钱办理方法&#xff0c;在债券商场激起“涟漪”——债券商场一改此前平静态势&#xff0c;连续两日跌落。 2月21日&#xff0c;10年期国债收益率较上星期五上行2.9个基点&#xff0c;至2.919%&#xff1b…

记录charles手机端配置https的成功过程

1.百度 https://www.likecs.com/show-204025787.html https://blog.csdn.net/enthan809882/article/details/117572094?spm1001.2101.3001.6650.6&utm_mediumdistribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaiduRate-6-117572094-blog-122959902.pc_rele…

余承东:问界就是华为生态汽车,不涉及20万以下车型

今天界面新闻发布了一篇余承东的专访文章&#xff0c;谈到了外界对华为造车的质疑&#xff0c;以及回应了与赛力斯的合作&#xff0c;后续HI模式与智选车模式如何推进的话题。摘录重点如下&#xff1a;1.首先&#xff0c;继续「不造车」“华为没有必要自己下场造车。”在他看来…

Spring MVC 源码 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

HandlerMapping 组件HandlerMapping 组件&#xff0c;请求的处理器匹配器&#xff0c;负责为请求找到合适的 HandlerExecutionChain 处理器执行链&#xff0c;包含处理器&#xff08;handler&#xff09;和拦截器们&#xff08;interceptors&#xff09;handler 处理器是 Objec…

【GO】k8s 管理系统项目[前端部分–Header]

【GO】k8s 管理系统项目[前端部分–Header] 1. 实现功能 面包屑展开关闭按钮用户信息(退出按钮) 2. 代码部分 src/layout/Layout.vue 在之前预留header位置补上 <!-- header --><el-header class"header"><el-row :gutter"20"><e…

python基于vue健身房课程预约平台

可定制框架:ssm/Springboot/vue/python/PHP/小程序/安卓均可开发 目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发3 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2.5 django框架 5 3 系统分析 6 3.1…

Sqoop利用Sql将mysql表_导入数据到Hive---大数据之Apache Sqoop工作笔记004

然后来看一下把数据导入到hive中去 可以去官网去看看文档,有什么参数 这里用的是hive-import对吧,然后 这里hive-overwrite是覆盖 这个hive-table staff_hive 是创建 一个hive的表是 staff_hive 然后我们先去启动一下hive 启动以后,然后我们去查一下,shot tables 可以看到 里…

Ubuntu 22.04.2 LTS安装Apollo8.0

本人硬件环境&#xff1a; CPU&#xff1a;Intel Core i7 6700 显卡&#xff08;GPU&#xff09;&#xff1a;NVIDIA GTX 3080 10G 内存&#xff1a;SAMSUNG DDR4 32GB 硬盘&#xff1a;双SSD系统盘 2T,双系统&#xff08;windows,ubuntu&#xff09; 一、安装Ubuntu 22.04…

【极海APM32替代笔记】HAL库中的SPI传输(可利用中断或DMA进行连续传输)

【极海APM32替代笔记】HAL库中的SPI传输&#xff08;可利用中断或DMA进行连续传输&#xff09; SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高…

Ssh协议绑定Git服务器

1、起因在公司开发一般使用gitlab部署公司git代码管理&#xff0c;个人的代码存储在公司gitlab上就显得不合时宜了&#xff0c;所以找了gitee上来存储代码。2、经过搜索了下github.com,gitee.com 其他当然有阿里云的云效等&#xff0c;个人使用优先国内git服务器&#xff0c;理…

【Netty系列・扫盲篇】Netty从入门到学废

文章目录1. 概述1.1 Netty 的地位1.2 Netty 的优势2. Hello World2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理&#x1f4a1; 提示3. 组件3.1 EventLoop&#x1f4a1; 优雅关闭演示 NioEventLoop 处理 io 事件&#x1f4a1; handler 执行中如何换人&#xff1f;演示 NioEventLo…

C# 业务单据号生成器(定义规则、获取编号、流水号)

系列文章 C#底层库–数据库访问帮助类&#xff08;MySQL版&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/126886379 C#底层库–JSON帮助类_详细&#xff08;序列化、反序列化、list、datatable&#xff09; 本文链接&#xff1a;htt…

GEE学习笔记 六十:GEE中生成GIF动画

生成GIF动画这个是GEE新增加的功能之一&#xff0c;这一篇文章我会简单介绍一下如何使用GEE来制作GIF动画。 相关API如下&#xff1a; 参数含义&#xff1a; params&#xff1a;设置GIF动画显示参数&#xff0c;详细的参数可以参考ee.data.getMapId() callback&#xff1a;回调…

基于RK3399地面测试台多参数记录仪测试平台软件设计

随着高科技技术在现代化战争中日益重要作用&#xff0c;飞行装备的研制亦从单元体制发展 到多元体制。航空装置系统在设计过程中&#xff0c;需要大量测试工作&#xff0c;尤其是需要把系统研制 和飞行试验中各部分工作状态参数实时记录&#xff0c;用以分析、改进设计。记录仪…

uniApp使用uni.chooseAddress()获取微信收货地址

获取微信收货地址 使用uniapp或者原生微信小程序获取微信的收货地址 1、需要在开发平台申请权限 在【开发】-【开发管理】-【接口设置】-【获取用户收货地址】–申请该权限&#xff0c;审核通过后方可使用。 2、在源码上添加配置 2.1 在uniapp上开发配置 打开manifest.js…

ECharts在vue中使用 与 图表自适应

目录 使用思路&#xff1a; Echarts在vue中使用 引入 ECharts 绘制图表实例&#xff08;复杂&#xff09; 实例效果&#xff1a; 官方入门实例&#xff08;简单&#xff09; 官方入门实例效果 ​编辑 图表自适应 ECharts 的功能十分强大,可以生成多种形式的图表,配置…