分布式锁实现方案 - Lock4j 使用

一、Lock4j 分布式锁工具

你是不是在使用分布式锁的时候,还在自己用 AOP 封装框架?那么 Lock4j 你可以考虑一下。

Lock4j 是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。

立志打造一个简单但富有内涵的分布式锁组件。

并且支持redission,redisTemplate,zookeeper。可混用,支持扩展。

Giee地址:https://gitee.com/baomidou/lock4j

二、使用方式

这里我以 redisson 作为分布式锁的底层。

添加依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.5</version>
</dependency>

然后再配置中增加 redis 的配置:

spring:
  redis:
    timeout: 6000
    password:
    cluster:
      max-redirects:
      nodes:
        - 192.168.40.120:6379
        - 192.168.40.121:6379
        - 192.168.40.122:6379

声明 RedissonClient

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient getRedisson(RedisProperties redisProperties) {
        Config config = new Config();
        String[] nodes = redisProperties.getCluster().getNodes().stream().filter(StringUtils::isNotBlank).map(node -> "redis://" + node).collect(Collectors.toList()).toArray(new String[]{});
        ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(nodes);
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            clusterServersConfig.setPassword(redisProperties.getPassword());
        }
        clusterServersConfig.setConnectTimeout((int) (redisProperties.getTimeout().getSeconds() * 1000));
        clusterServersConfig.setScanInterval(2000);
        return Redisson.create(config);
    }
}

然后只需在需要分布式锁的地方加 @Lock4j 即可:

@RestController
@RequestMapping("/lock")
public class Lock4jController {

    //不指定,默认获取锁超时3秒,30秒锁过期
    @Lock4j
    @GetMapping("/test")
    public String test() {
        return "success";
    }

    @Lock4j(keys = {"#id", "#name"}, expire = 60000, acquireTimeout = 10000)
    @GetMapping("/test1")
    public String test1(Long id, String name) {
    	Thread.sleep(5000);
        return "success";
    }

}

如果同时两次访问 /test1,可以感觉出第二次没有获得锁的请求等待的时间更长,因为要等待锁的释放:

在这里插入图片描述

获取锁超时时间和锁过期时间,可以通过配置在配置文件中全局生效:

lock4j:
  acquire-timeout: 3000 #默认值3s,可不设置
  expire: 30000 #默认值30s,可不设置
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
  lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置

acquire-timeout 等待锁的时长,超过这个时间会默认抛出 com.baomidou.lock.exception.LockFailureException 异常。

在这里插入图片描述

也可以自定义异常捕获,需要实现 LockFailureStrategy 接口:

@Slf4j
@Component
public class MyLockFailureStrategy implements LockFailureStrategy {

    @Override
    public void onLockFailure(String key, Method method, Object[] arguments) {
        log.error("key: {} , method: {} ,arguments: {} ", key, method.getName(), Arrays.asList(arguments).toString());
    }
}

如果获取锁超时,则可以看到打印的日志:

在这里插入图片描述

锁的获取逻辑也可以自定义,如果是 Redisson 依赖下,可以继承 AbstractLockExecutor<RLock> 抽象类,例如:

@Slf4j
@Component
public class MyLockExecutor extends AbstractLockExecutor<RLock> {

    @Resource
    RedissonClient redissonClient;
    /**
     * 尝试获取锁
     */
    @Override
    public RLock acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
        log.info("key: {} 尝试获取锁", lockKey);
        try {
            RLock lockInstance = this.redissonClient.getLock(lockKey);
            boolean locked = lockInstance.tryLock(acquireTimeout, expire, TimeUnit.MILLISECONDS);
            return (RLock)this.obtainLockInstance(locked, lockInstance);
        } catch (InterruptedException var9) {
            return null;
        }
    }

    /**
     * 释放锁
     */
    @Override
    public boolean releaseLock(String key, String value, RLock lockInstance) {
        log.info("key: {} 释放锁", key);
        if (lockInstance.isHeldByCurrentThread()) {
            try {
                return (Boolean)lockInstance.forceUnlockAsync().get();
            } catch (InterruptedException | ExecutionException var5) {
                return false;
            }
        } else {
            return false;
        }
    }
}

然后在使用时指定执行器:

@Lock4j(keys = {"#id", "#name"}, expire = 60000, acquireTimeout = 1000, executor = MyLockExecutor.class)
@GetMapping("/test2")
public String test2(Long id, String name) throws InterruptedException {
    Thread.sleep(5000);
    return "success";
}

请求 test2 接口可以看到打印的日志:

在这里插入图片描述

Key 的生成也可以自定义,只需继承 DefaultLockKeyBuilder 抽象类,例如:

@Slf4j
@Component
public class MyLockKeyBuilder extends DefaultLockKeyBuilder {

    public MyLockKeyBuilder(BeanFactory beanFactory) {
        super(beanFactory);
    }

    @Override
    public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
        String key = super.buildKey(invocation, definitionKeys);
        log.info("生成的key:{} ", key);
        return key;
    }
}

运行后可以观察日志:

在这里插入图片描述

上面都是通过注解的方式,同样也可以手动控制锁的获取和释放,只需要引入 LockTemplate ,例如:

@RestController
@RequestMapping("/lock")
public class Lock4j2Controller {
    @Resource
    private LockTemplate lockTemplate;

    @GetMapping("/test3")
    public String test1(Long id, String name) {
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock(id + name, 30000L, 5000L, RedissonLockExecutor.class);
        try {
            Thread.sleep(5000);
            return "success";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
    }
}

三、通过锁实现限流

在注解中 autoRelease 控制着是否自动在方法执行结束后释放锁,如果为 false 则是在 expire 时间到的时候移除锁,实际是通过 Redis 的过期机制,通过这个机制可以限制某个 key 的访问频次,例如:

@Lock4j(keys = {"#id", "#name"}, expire = 3000, acquireTimeout = 10000, autoRelease = false)
@GetMapping("/test4")
public String test4(Long id, String name) throws InterruptedException {
    return "success";
}

当同一个 idname 第一次访问的时候速度会很快:

在这里插入图片描述

如果频繁访问则会被限流:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java web系统的常见安全问题

一、背景 java开发的系统在发布到互联网后都需要进行安全扫描&#xff0c;本文主要总结开发web系统需要注意的与系统安全相关的问题。因为在做需求开发时&#xff0c;很少产品会将系统安全的因素考虑在内&#xff0c;总觉得实现个需求很简单&#xff0c;就是一些页面&#xff0…

Linux 文件权限

背景 因为在做一个任务的时候&#xff0c;调接口要到某个路径下的文件下记录log&#xff0c;但是调接口总是报一个错误&#xff1a; SEVERE: Local file cretaion error! I/O exception! File: /xxx/xxx/xxx/xx.log发现是一个创建文件的错误&#xff0c;根据提示&#xff0c;…

PC端视频网站系统源码 系统自带采集功能 附带完整的搭建教程

近年来&#xff0c;视频内容已成为人们日常生活中不可或缺的一部分。下面罗峰给大家分享一款PC端视频网站系统源码&#xff0c;该系统具有强大的采集功能&#xff0c;可轻松实现视频内容的自动更新和丰富。并附带完整的搭建教程&#xff0c;帮助您快速构建属于自己的视频网站。…

做校园外卖平台需要多少人?高校点外卖难题能否快速解决?

众所周知&#xff0c;大学生喜欢订外卖。到饭点前提前下单&#xff0c;下课后不用挤食堂&#xff0c;这种“懒人经济”在校园商圈非常流行&#xff0c;学生对外卖和跑腿等服务的依赖越来越深&#xff0c;外卖需求也在不断增加。但毕竟是在学校&#xff0c;环境因素会影响外卖的…

Java(使用注解的方式)连接数据库增删改查-MyBatis

准备工作&#xff1a; 1.创建一个springboot项目&#xff0c;并添加四个依赖 分别是&#xff0c;MyBatis的启动依赖和安装依赖&#xff0c;SQL的依赖&#xff0c;测试依赖&#xff0c;如下&#xff1a; 2.然后创建一张至少两条数据的表 &#xff08;表可以用各种图形化工具创…

【开源】基于Vue+SpringBoot的免税店商城管理系统

文末获取源码&#xff0c;项目编号&#xff1a; S 069 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S069。} 文末获取源码&#xff0c;项目编号&#xff1a;S069。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.2 研究方法 三、系统…

UE4/UE5 修改/还原场景所有Actor的材质

使用蓝图方法&#xff1a; 1.修改场景所有Actor 材质&#xff1a; Wirframe&#xff1a;一个材质类 MatList&#xff1a;获取到的所有模型的全部材质 的列表 TempAllClass&#xff1a;场景中所有获取的 Actor 的列表 功能方法如下&#xff1a; 蓝图代码可复制在&#xff1a…

基于SpringBoot+Vue会员制医疗预约服务管理信息系统(Java毕业设计)

点击咨询源码 大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的…

TensorBoard使用和问题解决

一、什么是TensorBoard? TensorBoard 是一组用于数据可视化的工具&#xff0c;它包含在流行的开源机器学习库 Tensorflow 中。TensorBoard 的主要功能包括&#xff1a; 可视化模型的网络架构跟踪模型指标&#xff0c;如损失和准确性等检查机器学习工作流程中权重、偏差和其他…

ABAP - Function ALV 02 简单开发一个Function ALV

了解Function ALV&#xff1a; https://blog.csdn.net/HeathlX/article/details/134879766?spm1001.2014.3001.5501程序开发步骤&#xff1a;① TCODE:SE38创建程序 ② 编写程序 DATA gt_spfli TYPE TABLE OF spfli.** Layout 变量定义 (固定使用 直接粘贴复制即可) DATA gs…

我的acer电脑U盘装系统前BIOS设置及装系统过程中的操作

1、开机长按F2进入BIOS设置 2、使能F12 3、调整boot顺序&#xff0c;使USB启动的优先级最高 4、按F10保存退出 5、插入U盘开机&#xff0c;boot选择界面无需操作&#xff0c;等待几秒&#xff0c;默认进入U盘系统 由于既使能了F12&#xff0c;又将U盘的优先级进调整到了最高&…

springboot集成knife4j详细教程

使用原生的swagger作为接口文档&#xff0c;功能不够强大&#xff0c;并且默认的ui比较简陋&#xff0c;不符合大众审美。所以实际开发中推荐使用knife4j对swagger进行增强。knife4j的地址&#xff1a;https://gitee.com/xiaoym/knife4j 基本使用 想要使用knife4j非常简单&…

Android获取Wifi网关

公司有这样一个应用场景&#xff1a;有一台球机设备&#xff0c;是Android系统的&#xff0c;它不像手机&#xff0c;它没有触摸屏幕&#xff0c;所以我们对球机的操作很不方便&#xff0c;于是我们搞这样一个设置&#xff1a;点击球机电源键5次分享出一个热点&#xff0c;然后…

UnoCSS 原子化开发初体验

UnoCSS 是一个即时的原子化 CSS 引擎&#xff0c;旨在灵活和可扩展。核心是不拘一格的&#xff0c;所有的 CSS 工具类都是通过预设提供的。再也不用为了取一个 classname 类名而烦恼了。 一、UnoCSS 特点 完全可定制&#xff1a;无核心工具&#xff0c;所有功能都通过预设提供…

【Fastadmin】根据Fieldlist键值组件做一个等级配置的完整示例

目录 1.效果展示&#xff1a; ​编辑 2.建表&#xff1a; 3.html页面 4.controller控制器 5.js 6.model 1.效果展示&#xff1a; 2.建表&#xff1a; 表名&#xff1a;fa_xxfb_config /*Navicat Premium Data TransferSource Server : rootSource Server Type …

WPS Office JS宏实现批量处理Word中的标题和正文的样式

该篇讲解下word文档中的标题和正文批量修改样式&#xff0c;如下图&#xff1a; 前面一篇已讲解了WPS Office宏编辑器操作方法&#xff0c;这里不细讲了&#xff0c;如有不清楚可以查看该篇&#xff1a;https://blog.csdn.net/jiciqiang/article/details/134653657?spm1001.20…

制作蓝牙小车(一)

制作控制蓝牙小车app 想制作一个蓝牙小车&#xff0c;通过手机app程序操控小车运行&#xff0c;制作分2个部分&#xff08;app制作&#xff0c;蓝牙小车硬件以及程序制作&#xff09;&#xff0c;先完成第一个部分app制作&#xff0c;本次app是通过androidstudio软件来制作安卓…

私域爆款案例拆解-元气森林

一、背景调研 二、引流策略 三、私域运营策略

第十一章 React 封装自定义组件

一、专栏介绍 &#x1f30d;&#x1f30d; 欢迎加入本专栏&#xff01;本专栏将引领您快速上手React&#xff0c;让我们一起放弃放弃的念头&#xff0c;开始学习之旅吧&#xff01;我们将从搭建React项目开始&#xff0c;逐步深入讲解最核心的hooks&#xff0c;以及React路由、…

客户案例:EDLP在央国企邮件数据合规中的价值与优势

客户背景 某机械制造企业&#xff0c;作为动力设备领域的领军企业&#xff0c;专门从事动力设备的研发、制造与销售。凭借丰富的经验与卓越的技术实力&#xff0c;该企业致力于深度研究动力设备的核心技术&#xff0c;为客户提供高效且可靠的解决方案。 客户需求 作为企业健康…