适合 Spring Boot 3.0x的Redis 分布式锁

Spring Boot 中的 Redis 分布式锁

在分布式系统中,多个进程同时访问共享资源时,很容易出现并发问题。为了避免这些问题,我们可以使用分布式锁来保证共享资源的独占性。Redis 是一款非常流行的分布式缓存,它也提供了分布式锁的功能。在 Spring Boot 中,我们可以很容易地使用 Redis 分布式锁来管理并发访问。

本文将介绍 Redis 分布式锁的概念和原理,并说明如何在 Spring Boot 中使用它们。

Redis 分布式锁的概念和原理

Redis 分布式锁是一种基于 Redis 的分布式锁解决方案。它的原理是利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性。

spring boot 项目引入

bash 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
    <version>2.2.7</version>
</dependency>

在需要加分布式锁的方法上,添加注解@Lock4j

  1. @Lock4j 注解的功能
    获取锁超时(acquireTimeout):指定在获取锁时的等待时间,默认情况下是 3 秒。如果在这段时间内无法获取到锁,可能会抛出异常或进行相应的处理。

锁过期时间(expire):指定锁的过期时间,默认是 30 秒。如果在这段时间内锁没有被手动释放,它会自动失效。这个机制通常用于防止死锁。

SpEL 表达式支持:@Lock4j 支持使用 SpEL 表达式来动态生成锁的键(key),例如通过方法参数生成唯一的锁标识。

bash 复制代码
@Service
public class DemoServiceImpl {

    /**
    默认获取锁超时3秒,30秒锁过期
    这个方法使用了 @Lock4j 注解,并且没有显式地配置任何参数。
	默认行为:获取锁时的超时时间为 3 秒,锁的过期时间为 30 秒。
	适用场景:在简单的场景下,simple() 方法会被锁住,直到锁被释放或超时。在锁的持有期间,其他线程无法进入该方法。
	**/
    @Lock4j
    public void simple() {
        //需要执行的方法
    }

    /**
    完全配置,支持spel,这个方法使用了 @Lock4j 注解,并且自定义了多个参数。
	配置详解:
	keys = {"#user.id", "#user.name"}:使用 SpEL 表达式动态生成锁的键。这里锁的键由 user 对象的 id 和 name 属性组成,确保了锁的唯一性。
	expire = 60000:指定锁的过期时间为 60 秒(即1分钟)。
	acquireTimeout = 1000:指定获取锁的超时时间为 1 秒。
	适用场景:当方法涉及到特定用户操作时,使用 customMethod() 来确保同一用户(根据 id 和 name 唯一确定)不会被多个线程同时操作。只有在获取到锁的情况下,才会执行方法逻辑,锁在1秒内未获取到,则会抛出异常或执行相应处理。
	**/
    @Lock4j(keys = {"#user.id", "#user.name"}, expire = 60000, acquireTimeout = 1000)
    public User customMethod(User user) {
        return user;
    }
}

设置配置文件的

bash 复制代码
lock4j:
  acquire-timeout: 3000 # 默认获取锁超时时间为3秒
  expire: 30000 # 默认锁的过期时间为30秒
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor # 使用RedisTemplate作为默认的锁执行器
  lock-key-prefix: lock4j # 锁key的前缀,默认为lock4j

配置项详解

acquire-timeout: 3000

含义:指定全局默认的获取锁的超时时间,单位为毫秒。默认设置为3秒(3000毫秒)。如果在这个时间内未能获取到锁,将触发相应的处理逻辑(比如抛出异常)。

使用场景:适用于全局大多数锁的场景,可以减少在每个注解中重复配置的需要。

expire: 30000

含义:指定全局默认的锁过期时间,单位为毫秒。默认设置为30秒(30000毫秒)。在这个时间内,如果锁没有被释放,它将自动失效。

使用场景:适用于防止死锁的全局场景,确保锁在一定时间内自动释放,避免持有锁的线程因故障而导致锁无法释放。

primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor

含义:指定默认使用的分布式锁执行器。这个配置定义了使用哪种方式来实现锁的逻辑,常见的选项包括 Redisson, RedisTemplate, 和 Zookeeper。

使用场景:这里指定使用 RedisTemplateLockExecutor 来实现分布式锁逻辑,适用于大多数基于Redis的分布式系统。

lock-key-prefix: lock4j

含义:为锁的键(key)指定一个全局前缀。这个前缀会被添加到所有的锁键之前,以确保锁键的唯一性。

使用场景:适用于多个应用共享一个Redis实例时,通过设置前缀来防止不同应用之间的锁键冲突。

用法

bash 复制代码
    @Lock4j(executor = RedissonLockExecutor.class)
    public Boolean test() {
        return "true";
    }

自定义锁key生成器,默认的锁key生成器为 com.baomidou.lock.DefaultLockKeyBuilder

bash 复制代码
@Component
public class DynamicLockKeyBuilder extends DefaultLockKeyBuilder {

    @Override
	public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
		String key = super.buildKey(invocation, definitionKeys);
        //需要执行的方法
		return key;
	}
}

自定义锁获取失败策略,默认的锁获取失败策略为 com.baomidou.lock.DefaultLockFailureStrategy

bash 复制代码
@Component
public class MyLockFailureStrategy implements LockFailureStrategy {

    @Override
    public void onLockFailure(String key, long acquireTimeout, int acquireCount) {
    	//需要执行的方法或者业务代码
    }
}

手动上锁或手动解锁

bash 复制代码
@Service
@AllArgsConstructor
public class SysPaymentInfoServiceImpl extends ServiceImpl<SysPaymentInfoMapper, SysPaymentInfo> implements SysPaymentInfoService {

    private final LockTemplate lockTemplate;

    public void programmaticLock(String userId) {
        // 执行查询业务操作 不上锁
        // 业务区域
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock(userId, 30000L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试!");
        }
        // 获取锁成功,处理相关业务
        try {
            Console.log("执行简单方法 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        //结束
    }

指定时间内不释放锁(限流)

bash 复制代码
	// 用户在5秒内只能访问1次
    @Lock4j(keys = {"#user.id"}, acquireTimeout = 0, expire = 5000, autoRelease = false)
    public Boolean test(User user) {
        return "true";
    }
  1. @Lock4j(keys = {"#user.id"})
    含义:使用 SpEL 表达式 #user.id 生成锁的键。锁的键与传入的 user.id 关联,确保锁的唯一性。这样可以保证每个用户(根据其 id)都会有一个唯一的锁。
  2. acquireTimeout = 0
    含义:获取锁的超时时间设置为 0,表示立即尝试获取锁,不等待。如果无法获取锁,则直接放弃操作。
    使用场景:当你希望方法调用立即失败而不等待时,这个配置很有用。适用于需要快速响应的场景。
  3. expire = 5000
    含义:锁的过期时间设置为 5000 毫秒(5 秒)。锁在 5 秒后自动失效,释放锁资源。
    使用场景:这个配置用于控制用户只能在 5 秒内访问一次该方法,5 秒后锁自动失效,用户可以再次访问。
  4. autoRelease = false
    含义:设置 autoRelease = false 表示锁不会在方法执行完毕后自动释放。通常,默认情况下,锁在方法执行完毕后会自动释放,但在这里显式关闭了这个功能。
    使用场景:这种配置可能用于场景中需要手动控制锁的释放,而不是依赖方法执行结束后自动释放的情况。例如,你可能希望锁在一定时间后自然过期,而不是方法执行完毕就立即释放。
相关推荐
JH30735 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_12498707539 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_9 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_818732069 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
此生只爱蛋9 小时前
【Redis】主从复制
数据库·redis
汤姆yu12 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶12 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip13 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
惊讶的猫14 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
JavaGuide14 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot