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

Java-函数式编程-实现分布式锁工具

该业务适用场景:

  • 分布式环境下的资源互斥访问

  • 防止重复执行(如定时任务)

  • 库存扣减等需要强一致性的操作

一. 背景

在开发过程中需要使用到分布式锁的时候(以redisson为例). 一般会通过初始化RedissonClient配置, 然后在需要的地方使用. 当使用的多的时候会发现, 代码中充斥着相似的代码结构, 例如

String lockKey = RedisBaseKeyEnum.APP_USER_REGISTRY_VERIFY_CODE_KEY_LOCK.buildKey(account);
RLock lock = redissonClient.getLock(lockKey);
try {if (lock != null && !lock.isLocked() && lock.tryLock(0, 60L, TimeUnit.SECONDS)) {// some biz}
} catch (InterruptedException e) {LOG.error("加锁失败,key:{}, account:{}", lockKey, account, e);Thread.currentThread().interrupt();throw new BizException(BizErrorCode.VERIFY_CODE_SENDER_ERROR);
} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}
}

简单的加锁过程类似于上面的代码. 所以为了提升代码的可复用性, 降低重复代码, 提高系统可维护性. 我们可以通过Java中的函数式编程, 将重复的代码块抽象出一个模板. 然后具体的业务通过作为方法入参 来实现.

二. 核心点

  1. 抽象分布式锁业务

  2. 使用@FunctionalInterface注解将需要注入的业务抽象出来

  3. 注入使用

三. 实现方式

  1. 定义函数式接口 @FunctionalInterface

============================= 加锁业务 有返回值 ===============================
package com.sj.utils.lock.func;@FunctionalInterface
public interface LockTask {void run() throws Exception; // 没有入参的方法
}
============================= 加锁业务没有返回值 ===============================
package com.sj.utils.lock.func;@FunctionalInterface
public interface LockTask {void run() throws Exception; // 没有入参的方法}
  1. 定义Redisson分布式锁类型枚举

package com.sj.utils.lock;public enum RLockType {REENTRANT,       // 可重入锁FAIR,            // 公平锁READ,            // 读锁WRITE,           // 写锁TRY,             // 尝试获取锁TRY_WITH_FAIL    // 尝试失败锁 tryLock中waitTime为0
}

然后

===========================RLockSimpleUtil========================
public RLock getLock(String lockKey, RLockType lockType) {switch (lockType) {case TRY:case REENTRANT:case TRY_WITH_FAIL:return redissonClient.getLock(lockKey);case FAIR:return redissonClient.getFairLock(lockKey);case READ:return redissonClient.getReadWriteLock(lockKey).readLock();case WRITE:return redissonClient.getReadWriteLock(lockKey).writeLock();default:throw new IllegalArgumentException("Unknown lock type: " + lockType);}
}
  1. 定义RLockSimpleUtil

package com.sj.utils.lock;import com.sj.utils.lock.func.LockException;
import com.sj.utils.lock.func.LockTask;
import com.sj.utils.lock.func.LockTaskWithResult;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
public class RLockSimpleUtil {private final Logger log = LoggerFactory.getLogger(RLockSimpleUtil.class);private final RedissonClient redissonClient;// 锁默认参数private final long DEFAULT_WAIT_TIME = 5;       // 秒private final long DEFAULT_LEASE_TIME = 30;     // 秒private final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;public RLockSimpleUtil(RedissonClient redissonClient) {this.redissonClient = redissonClient;}/*** 执行带返回值的任务,默认使用可重入锁** @param lockKey 锁键* @param task    任务* @param <T>     任务返回值类型* @return 任务返回值*/public <T> T executeWithResult(String lockKey, LockTaskWithResult<T> task) {return executeWithResult(lockKey, RLockType.REENTRANT, DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT, task);}/*** 执行带返回值的任务,支持指定锁类型和超时时间** @param lockKey   锁键* @param lockType  锁类型* @param waitTime  等待时间* @param leaseTime 租约时间* @param unit      时间单位* @param lockTask  任务* @param <T>       任务返回值类型* @return 任务返回值*/public <T> T executeWithResult(String lockKey,RLockType lockType,long waitTime,long leaseTime,TimeUnit unit,LockTaskWithResult<T> lockTask) {RLock lock = getLock(lockKey, lockType);boolean acquired = false;try {acquired = lock.tryLock(waitTime, leaseTime, unit);if (!acquired) {throw new LockException("Failed to acquire lock: " + lockKey);}return lockTask.runWithResult();} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new LockException("Lock acquisition interrupted", e);} catch (Exception e) {throw new RuntimeException(e);} finally {unlockQuietly(lock, acquired);}}/*** 执行无返回值的任务,默认使用可重入锁** @param lockKey  锁键* @param lockTask 任务*/public void execute(String lockKey, LockTask lockTask) {execute(lockKey, RLockType.REENTRANT, DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT, lockTask);}/*** 执行无返回值的任务,支持指定锁类型和超时时间** @param lockKey   锁键* @param lockType  锁类型* @param waitTime  等待时间* @param leaseTime 租约时间* @param timeUnit  时间单位* @param lockTask  任务*/public void execute(String lockKey, RLockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, LockTask lockTask) {RLock lock = getLock(lockKey, lockType);boolean acquired = false;try {acquired = lock.tryLock(waitTime, leaseTime, timeUnit);if (!acquired) {throw new LockException("Failed to acquire lock: " + lockKey);}lockTask.run();} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new LockException("Interrupted while trying to acquire lock: " + lockKey);} catch (Exception e) {throw new RuntimeException(e);} finally {unlockQuietly(lock, acquired);}}/*** 获取指定锁类型的锁实例** @param lockKey  锁键* @param lockType 锁类型* @return 锁实例*/public RLock getLock(String lockKey, RLockType lockType) {switch (lockType) {case TRY:case REENTRANT:case TRY_WITH_FAIL:return redissonClient.getLock(lockKey);case FAIR:return redissonClient.getFairLock(lockKey);case READ:return redissonClient.getReadWriteLock(lockKey).readLock();case WRITE:return redissonClient.getReadWriteLock(lockKey).writeLock();default:throw new IllegalArgumentException("Unknown lock type: " + lockType);}}/*** 安静地解锁锁实例** @param lock     锁实例* @param acquired 是否成功获取锁*/private void unlockQuietly(RLock lock, boolean acquired) {try {if (acquired && lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}} catch (Exception e) {if (lock != null && lock.isHeldByCurrentThread()) {log.warn("Error unlocking: {} ", e.getMessage(), e);lock.unlock();}}}}

四. 使用方式

  1. 在具体的业务中注入RLockSimpleUtil

  2. 根据具体业务, 直接调用util中的具体方法, 例如一个简单的加锁业务:

@Override
public ShortAddressResult acquire(ShortAddressTypeEnum type, Long householdId, Integer count) {String key = RedisBaseKeyEnum.DEVICE_SHORT_ADDRESS_LIST_CACHE_KEY_LOCK.buildKey(householdId.toString());return rLockSimpleUtil.executeWithResult(key,RLockType.FAIR,30,3,TimeUnit.SECONDS,// 下方执行具体的加锁业务() -> {String cacheKey = getCacheKey(type, householdId);return shortAddressPoolService.acquire(type, cacheKey, householdId, count);});
}

五. 补充

  1. Java内置的函数式接口:

  • Runnable - 无参数无返回值

  • Supplier - 无参数有返回值

  • Consumer - 有参数无返回值

  • Function<T,R> - 有参数有返回值

  • Predicate - 有参数返回布尔值

  1. 潜在问题

  2. Redis 单点依赖

    1. 依赖 Redis 可用性

    2. Redis 故障会影响所有锁操作

  3. 锁超时风险

    1. 固定租约时间可能不适合所有场景

    2. 长时间任务可能导致锁提前释放

  4. 性能考虑

    1. 每次锁操作都需要网络通信

    2. 高并发场景下可能成为瓶颈

  5. 异常处理可能过于宽泛

  6. 缺少监控指标

    1. 没有锁获取成功率统计

    2. 没有锁持有时间监控

http://www.gsyq.cn/news/35479.html

相关文章:

  • 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-31- 操作日历时间控件-上篇(详细教程) - 北京
  • 2025年智能照明系统定做厂家权威推荐榜单:智能照明控制/智能灯控十大品牌/智能家居照明源头厂家精选
  • 2025年评价高的WHB系列筛土机最新TOP品牌厂家排行
  • 25.10.06
  • 四个月,AI为主,人为辅,一款产品两个知识库!
  • 25.09.17
  • 政府机构跨网文件交换案例分享:构建跨网文件交换统一通道
  • 25.09.15
  • Java 类加载机制 面试题(一)
  • 2025年优秀的舟山注塑螺杆厂家最新推荐排行榜
  • 2025年专业的工业型无线测力称重变送器高评价厂家推荐榜
  • 2025年专业的液压式矫平机优质厂家推荐榜单
  • CentOS7进入单用户模式
  • 实例方法实际上也是类属性,这个说法对吗?——实例的命名空间和类的命名空间详解
  • 2025年热门的薄膜电动搬运车高评价厂家推荐榜
  • 2025年正规的净化材料净化板厂家最新推荐权威榜
  • 2025 年 10 月传感器厂家最新推荐,技术实力与市场口碑深度解析磁致伸缩位移/防爆位移/防水位移/隔爆位移/线性位移传感器厂家推荐
  • 2025年专业的多圈电位器最新TOP厂家排名
  • 2025年热门的全自动高压隔膜压滤机厂家选购指南与推荐
  • 2025年最好的破碎机高评价厂家推荐榜
  • 2025 年振动筛厂家最新推荐榜,技术实力与市场口碑深度解析:甄选高适配性优质厂家实验振动筛/防爆振动筛/精细振动筛/分级振动筛粉末振动筛公司推荐
  • 2025年评价高的马口铁罐用户好评厂家排行
  • 你好,软工实践!
  • 2025年评价高的弥散供氧设备厂家最新权威实力榜
  • 2025年热门的化粪池清淤机器人最新TOP厂家排名
  • 2025年比较好的斜面冲击台高评价厂家推荐榜
  • 2025年质量好的食品铁盒高评价厂家推荐榜
  • 2025年靠谱的主被动隔振解决方案厂家选购指南与推荐
  • 2025年10月医美术后修护产品推荐榜:传明酸精华对比评测
  • 2025年热门的双层恒温 振荡培养箱TOP实力厂家推荐榜