【完美】SpringBoot中使用注解来实现 Redis 分布式锁

本文详细介绍了如何使用Redis实现分布式锁,并结合AOP进行操作,防止并发问题。通过注解、切面拦截、定时任务实现锁的加、解锁及续时功能,确保业务操作的原子性和数据一致性。在测试中模拟耗时请求,验证了锁的有效性。
摘要由CSDN通过智能技术生成

一、业务背景

有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。


二、分析流程

使用 Redis 作为分布式锁,将锁的状态放到 Redis 统一维护,解决集群中单机 JVM 信息不互通的问题,规定操作顺序,保护用户的数据正确。

梳理设计流程

  1. 新建注解 @interface,在注解里设定入参标志

  2. 增加 AOP 切点,扫描特定注解

  3. 建立 @Aspect 切面任务,注册 bean 和拦截特定方法

  4. 特定方法参数 ProceedingJoinPoint,对方法 pjp.proceed() 前后进行拦截

  5. 切点前进行加锁,任务执行后进行删除 key

核心步骤:加锁、解锁和续时

加锁

使用了 RedisTemplate 的 opsForValue.setIfAbsent 方法,判断是否有 key,设定一个随机数 UUID.random().toString,生成一个随机数作为 value。

从 redis 中获取锁之后,对 key 设定 expire 失效时间,到期后自动释放锁。

按照这种设计,只有第一个成功设定 Key 的请求,才能进行后续的数据操作,后续其它请求由于无法获得🔐资源,将会失败结束。

超时问题

担心 pjp.proceed() 切点执行的方法太耗时,导致 Redis 中的 key 由于超时提前释放了。

例如,线程 A 先获取锁,proceed 方法耗时,超过了锁超时时间,到期释放了锁,这时另一个线程 B 成功获取 Redis 锁,两个线程同时对同一批数据进行操作,导致数据不准确。

解决方案:增加一个「续时」

任务不完成,锁不释放:

维护了一个定时线程池 ScheduledExecutorService,每隔 2s 去扫描加入队列中的 Task,判断是否失效时间是否快到了,公式为:【失效时间】<= 【当前时间】+【失效间隔(三分之一超时)】

/**
 * 线程池,每个 JVM 使用一个线程去维护 keyAliveTime,定时执行 runnable
 */
private static final ScheduledExecutorService SCHEDULER = 
new ScheduledThreadPoolExecutor(1, 
new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
static {
    SCHEDULER.scheduleAtFixedRate(() -> {
        // do something to extend time
    }, 0,  2, TimeUnit.SECONDS);
}

三、设计方案

经过上面的分析,同事小🐟设计出了这个方案:

前面已经说了整体流程,这里强调一下几个核心步骤:

  • 拦截注解 @RedisLock,获取必要的参数

  • 加锁操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot使用Redis分布式锁可以通过以下步骤实现: 1. 添加Redis依赖:在`pom.xml`文件添加Redis相关的依赖,例如`spring-boot-starter-data-redis`。 2. 配置Redis连接信息:在`application.properties`(或者`application.yml`)文件配置Redis的连接信息,包括主机名、端口号、密码等。 3. 创建Redis分布式锁的工具类:可以创建一个名为`RedisLockUtil`的工具类,其包含获取锁、释放锁等方法的实现。 ```java @Component public class RedisLockUtil { private static final long DEFAULT_LOCK_EXPIRE = 30000; // 默认锁的过期时间,30秒 @Autowired private StringRedisTemplate redisTemplate; /** * 获取锁 * @param lockKey 锁的键 * @param requestId 请求标识,用于区分不同的客户端 * @param expireTime 锁的过期时间 * @return true表示获取锁成功,false表示获取锁失败 */ public boolean tryLock(String lockKey, String requestId, long expireTime) { Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS); return result != null && result; } /** * 释放锁 * @param lockKey 锁的键 * @param requestId 请求标识,用于判断是否有权释放锁 */ public void releaseLock(String lockKey, String requestId) { String value = redisTemplate.opsForValue().get(lockKey); if (value != null && value.equals(requestId)) { redisTemplate.delete(lockKey); } } } ``` 4. 在需要加锁的地方使用Redis分布式锁: ```java @Autowired private RedisLockUtil redisLockUtil; public void doSomethingWithLock() { String lockKey = "lock:key"; String requestId = UUID.randomUUID().toString(); long expireTime = 5000; // 锁的过期时间为5秒 boolean locked = redisLockUtil.tryLock(lockKey, requestId, expireTime); if (locked) { try { // 获取到锁,执行业务逻辑 // ... } finally { // 释放锁 redisLockUtil.releaseLock(lockKey, requestId); } } else { // 获取锁失败,可能有其他线程正在处理 // ... } } ``` 以上是一个简单的使用Redis分布式锁的示例,你可以根据自己的实际需求进行调整和扩展。同时,还可以结合AOP、注解等方式来简化和统一锁的使用
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值