当前位置: 首页 > news >正文

java每日精进 5.27【分布式锁】

项目利用 Redis 实现分布式锁,提供两种使用方式:

编程式锁(通过 Redisson 显式加锁/解锁)和

声明式锁(通过 @Lock4j 注解自动管理锁)

以下以支付通知模块(PayNotify)为例,详细解析两种方式的实现过程。

1. 编程式锁(基于 Redisson)

编程式锁通过 Redisson 框架提供灵活的分布式锁操作,适合需要精细控制锁逻辑的场景。 PayNotify 模块使用编程式锁确保支付通知任务的并发安全。

实现步骤
步骤 1:引入 Redisson 依赖

在模块的 pom.xml 中添加 Redisson 依赖:

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.25.1</version> <!-- 建议使用最新版本 -->
</dependency>
  • 解释
    • redisson-spring-boot-starter 提供 Redisson 客户端,支持多种分布式锁类型(如普通锁、红锁、读写锁)。
    • 已在 Redis 缓存模块配置好 Redisson 和 Spring Data Redis,无需额外配置。
步骤 2:定义 Redis 锁键

在 RedisKeyConstants 接口中,定义支付通知任务的分布式锁键:

/*** System Redis Key 枚举类*/
public interface RedisKeyConstants {/*** 指定部门的所有子部门编号数组的缓存* <p>* KEY 格式:dept_children_ids:{id}* VALUE 数据类型:String 子部门编号集合*/String DEPT_CHILDREN_ID_LIST = "dept_children_ids";/*** 角色的缓存* <p>* KEY 格式:role:{id}* VALUE 数据类型:String 角色信息*/String ROLE = "role";/*** 用户拥有的角色编号的缓存* <p>* KEY 格式:user_role_ids:{userId}* VALUE 数据类型:String 角色编号集合*/String USER_ROLE_ID_LIST = "user_role_ids";/*** 拥有指定菜单的角色编号的缓存* <p>* KEY 格式:menu_role_ids:{menuId}* VALUE 数据类型:String 角色编号集合*/String MENU_ROLE_ID_LIST = "menu_role_ids";/*** 拥有权限对应的菜单编号数组的缓存* <p>* KEY 格式:permission_menu_ids:{permission}* VALUE 数据类型:String 菜单编号数组*/String PERMISSION_MENU_ID_LIST = "permission_menu_ids";/*** OAuth2 客户端的缓存* <p>* KEY 格式:oauth_client:{id}* VALUE 数据类型:String 客户端信息*/String OAUTH_CLIENT = "oauth_client";/*** 访问令牌的缓存* <p>* KEY 格式:oauth2_access_token:{token}* VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO}* <p>* 由于动态过期时间,使用 RedisTemplate 操作*/String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";/*** 站内信模版的缓存* <p>* KEY 格式:notify_template:{code}* VALUE 数据格式:String 模版信息*/String NOTIFY_TEMPLATE = "notify_template";/*** 邮件账号的缓存* <p>* KEY 格式:mail_account:{id}* VALUE 数据格式:String 账号信息*/String MAIL_ACCOUNT = "mail_account";/*** 邮件模版的缓存* <p>* KEY 格式:mail_template:{code}* VALUE 数据格式:String 模版信息*/String MAIL_TEMPLATE = "mail_template";/*** 短信模版的缓存* <p>* KEY 格式:sms_template:{id}* VALUE 数据格式:String 模版信息*/String SMS_TEMPLATE = "sms_template";/*** 小程序订阅模版的缓存** KEY 格式:wxa_subscribe_template:{userType}* VALUE 数据格式 String, 模版信息*/String WXA_SUBSCRIBE_TEMPLATE = "wxa_subscribe_template";}
  • 解释
    • 键格式为 pay_notify:lock:{id},如 pay_notify:lock:123,确保每个任务有唯一锁。
    • 使用模板字符串,便于动态生成。
步骤 3:实现锁操作类

创建 PayNotifyLockRedisDAO 类,使用 Redisson 实现加锁和解锁:

@Repository
public class PayNotifyLockRedisDAO {@Resourceprivate RedissonClient redissonClient;public void lock(long id, long timeoutMillis, Runnable action) {String lockKey = formatKey(id);RLock lock = redissonClient.getLock(lockKey);try {// 加锁lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);// 执行逻辑action.run();} finally {// 解锁lock.unlock();}}private static String formatKey(Long id) {return String.format(RedisKeyConstants.PAY_NOTIFY_LOCK, id);}
}

解释

  • 依赖注入:RedissonClient 由 Redisson Starter 提供。
  • 方法 lock
    1. 生成键:根据任务 ID 格式化锁键。
    2. 获取锁:通过 redissonClient.getLock(key) 获取锁对象,lock.lock(timeoutMillis, TimeUnit.MILLISECONDS) 设置加锁超时。
    3. 执行逻辑:在 try 块中执行传入的 Runnable 逻辑。
    4. 释放锁:在 finally 块中调用 lock.unlock(),确保锁释放。
  • 安全性:try-finally 结构保证锁一定释放,防止死锁。
步骤 4:应用锁

在 PayNotifyServiceImpl 中使用 PayNotifyLockRedisDAO 加锁:

@Service
public class PayNotifyServiceImpl implements PayNotifyService {public static final long NOTIFY_TIMEOUT_MILLIS = 120 * 1000; // 120秒@Resourceprivate PayNotifyLockRedisDAO payNotifyLockRedisDAO;@Resourceprivate PayNotifyTaskMapper payNotifyTaskMapper;@Overridepublic void executeNotifySync(PayNotifyTaskDO task) {payNotifyLockRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {// 校验任务是否已过期PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());if (DateUtils.afterNow(dbTask.getNextNotifyTime())) {log.info("[executeNotify][任务({}) 忽略,未到通知时间]", dbTask.getId());return;}// 执行通知逻辑executeNotify(dbTask);});}private void executeNotify(PayNotifyTaskDO task) {// 模拟通知逻辑log.info("[executeNotify][执行任务 {}]", task.getId());}
}
  • 解释
    • 注入:注入 PayNotifyLockRedisDAO 用于锁操作。
    • 加锁:调用 lock 方法,传入任务 ID、超时时间(120秒)和业务逻辑(Runnable)。
    • 校验:加锁后再次查询任务状态,防止并发重复执行。
    • 执行:调用 executeNotify 完成通知。
    • 优势:编程式锁显式控制锁的范围和释放时机,适合复杂逻辑。
步骤 5:为什么选择 Redisson?
  • 多类型锁:支持普通锁、红锁、读写锁等,满足不同场景。
  • 高可靠性:内置看门狗机制,自动延长锁超时时间。
  • 易用性:API 直观,集成 Spring 简单。

2. 声明式锁(基于 Lock4j)

声明式锁通过 Lock4j 的 @Lock4j 注解提供简洁的分布式锁支持,适合快速开发。Yudao 默认未启用 Lock4j,需手动引入。

实现步骤
步骤 1:引入 Lock4j 依赖

在 pom.xml 中添加 Lock4j 依赖:

<dependency><groupId>com.baomidou</groupId><artifactId>lock4j-redisson-spring-boot-starter</artifactId><version>2.2.4</version>
</dependency>
  • 解释
    • lock4j-redisson-spring-boot-starter 整合 Lock4j 和 Redisson,提供 Redis 分布式锁。
    • 默认 optional=true,需移除以启用。
步骤 2:配置 Lock4j

在 application-local.yml 中配置 Lock4j 参数:

lock4j:
  acquire-timeout: 3000 # 获取锁超时时间(毫秒)
  expire: 30000 # 锁过期时间(毫秒)

  • 解释
    • acquire-timeout:尝试获取锁的最长时间,超时后抛出异常。
    • expire:锁的自动过期时间,防止死锁。
    • 默认值适合大多数场景,可根据业务调整。
步骤 3:应用 @Lock4j 注解

在服务方法上添加 @Lock4j 注解:

@Service
public class DemoService {@Lock4jpublic void simple() {// 模拟业务逻辑log.info("[simple][执行简单逻辑]");}@Lock4j(keys = {"#user.id", "#user.name"}, expire = 60000, acquireTimeout = 1000)public User customMethod(User user) {log.info("[customMethod][处理用户 {}]", user);return user;}
}@Data
class User {private Long id;private String name;
}
  • 解释
    • 简单锁:simple 方法使用默认配置,锁键基于方法签名。
    • 自定义锁:customMethod 使用 SpEL 表达式(#user.id, #user.name)生成锁键,设置 60秒过期和 1秒获取超时。
    • 机制:Lock4j 自动在方法执行前加锁,执行后解锁,基于 Redis 实现。
步骤 4:Lock4j 的优势
  • 简洁:注解式开发,减少样板代码。
  • 灵活:支持 SpEL 表达式自定义锁键。
  • 多后端:支持 Redis、ZooKeeper 等,Redisson 仅为一种实现。

http://www.xdnf.cn/news/683983.html

相关文章:

  • 经典排序算法合集(下)
  • 【调试】【原理理解】ldm 和 diffusers 库的区别
  • 自动驾驶中的博弈式交互规划:从理论到实践
  • droidcam ivcam 电脑访问不到地址解决办法 把网线从猫插到路由上
  • 1. 编程语言进化史与JavaScript
  • 数据结构期末模拟试卷
  • app获取相册权限是否意味着所有相片都可随时读取?
  • 智能防护实战:从攻击成本看企业安全降本增效
  • Jpa 删除之@Version注解的实体类无法删除的问题
  • 远程办公如何实现零监控?深度拆解“吱吱”不会被监控的通讯办公软件
  • 在RK3588上实现YOLOv8n高效推理:从模型优化到GPU加速后处理全解析
  • 电机控制杂谈(26)——电机驱动系统的编码器的测速噪声
  • RK3568DAYU开发板-驱动平台驱动案例--PWM
  • 【Linux】(1)—进程概念-①冯诺依曼体系结构
  • 想查看或修改 MinIO 桶的匿名访问权限(public/private/custom)
  • java基础学习(十八)
  • 大模型微调(面经总结)
  • 代码风格指南
  • 聚焦北京央美备考画室:探寻实力之巅
  • 码蹄集——圆周率II、三个非负整数
  • PCB设计自检表
  • 基于心理健康与数字行为数据的多维度分析
  • JAVA运算符详解
  • Oracle向PG转移建议以及注意点
  • 57页 @《人工智能生命体 新启点》中國龍 原创连载
  • IvorySQL 核心技术解读:双 Parser 架构如何定义数据库兼容性?
  • python训练营打卡第36天
  • 竞赛小算法总结(二):gcdlcm,拓展欧几里得线性同余,逆元(含代码详解)
  • AE的ai图层导到Ai
  • spring4第2课-ioc控制反转-依赖注入,是为了解决耦合问题