一、引言
在现代分布式系统中,如何保证数据一致性和避免并发冲突是一个重要课题。分布式锁作为解决这一问题的关键技术,在微服务架构中发挥着重要作用。本文将基于实际项目中的枚举实现,深入探讨各种分布式锁的特点和应用场景。
二、分布式锁概述
分布式锁是一种协调机制,用于在分布式系统中的多个节点间协调对共享资源的访问。相比传统的单机锁,分布式锁需要考虑网络延迟、分区容错等因素,实现起来更加复杂。
三、 项目中的分布式锁实现
在我们分析的项目中,通过枚举实现了六种不同的分布式锁类型,每种锁都有其特定的应用场景。
1. 非公平锁 (UNFAIR_LOCK)
java
UNFAIR_LOCK {
@Override
public boolean lock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getLock(lock.getKey());
try {
return rLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getLock(lock.getKey());
if (rLock.isHeldByCurrentThread()) {
rLock.unlockAsync();
}
}
}
特点:
- 允许线程在尝试获取锁时直接竞争
- 可能导致某些线程长时间等待
- 适用于对锁获取顺序没有严格要求的场景
优势: 性能较好,吞吐量高
2. 公平锁 (FAIR_LOCK)
java
FAIR_LOCK {
@Override
public boolean lock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getFairLock(lock.getKey());
try {
return rLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getFairLock(lock.getKey());
if (rLock.isHeldByCurrentThread()) {
rLock.unlockAsync();
}
}
}
特点:
- 按照请求顺序分配锁,先请求的线程优先获得锁
- 保证了锁获取的公平性
- 可能影响系统吞吐量
适用场景: 对锁获取顺序有严格要求的业务场景
3. 多锁 (MULTI_LOCK)
java
MULTI_LOCK {
@Override
public boolean lock(LockEntity lock) {
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
RedissonMultiLock multiLock = new RedissonMultiLock(locks);
try {
return multiLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
RedissonMultiLock multiLock = new RedissonMultiLock(locks);
multiLock.unlock();
}
}
特点:
- 将多个锁绑定为一个锁
- 只有当所有锁都获取成功时才算获取成功
- 适用于需要同时获取多个资源锁的场景
4. 红锁 (RED_LOCK)
java
RED_LOCK {
@Override
public boolean lock(LockEntity lock) {
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
RedissonRedLock redLock = new RedissonRedLock(locks);
try {
return redLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
RedissonRedLock redLock = new RedissonRedLock(locks);
redLock.unlock();
}
}
特点:
- 基于RedLock算法实现
- 通过多个独立的Redis节点保证高可用性
- 即使部分Redis节点故障,仍能保证锁的正确性
适用场景: 对高可用性要求极高的分布式环境
5. 读锁 (READ_LOCK)
java
READ_LOCK {
@Override
public boolean lock(LockEntity lock) {
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
try {
return rLock.readLock().tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
if (rLock.readLock().isHeldByCurrentThread()) {
rLock.readLock().unlockAsync();
}
}
}
6. 写锁 (WRITE_LOCK)
java
WRITE_LOCK {
@Override
public boolean lock(LockEntity lock) {
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
try {
return rLock.writeLock().tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
if (rLock.writeLock().isHeldByCurrentThread()) {
rLock.writeLock().unlockAsync();
}
}
}
读写锁特点:
- 读锁: 允许多个线程同时读取共享资源,适用于读多写少的场景
- 写锁: 独占访问共享资源,确保写入操作的原子性和一致性
四、实际应用案例
在项目中的类中,我们可以看到写锁的实际应用,现附上代码可直接使用:
pom.xml
xml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>3.52.0</version>
</dependency>
LockEntity.java
java
import lombok.Getter;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁实体类,用于封装分布式锁的相关配置参数
*/
@Getter
public class LockEntity {
// 锁的键值
private String key;
// 获取锁的等待时间,默认60秒
private long waitTime = 60L;
// 锁的租约时间,默认-1表示永不过期
private long leaseTime = -1L;
// 时间单位,默认为秒
private TimeUnit timeUnit;
// 锁键值列表,用于批量操作
private List<String> keyList;
/**
* 构造函数,初始化锁实体
* @param key 锁的键值
*/
public LockEntity(String key) {
this.timeUnit = TimeUnit.SECONDS;
this.key = key;
}
/**
* 解析分布式锁注解,将注解中的配置设置到当前对象
* @param anno 分布式锁注解
* @return 返回当前对象实例,支持链式调用
*/
public LockEntity parseLockAnno(DistributeLock anno) {
return this.setKey(anno.key())
.setWaitTime(anno.waitTime())
.setLeaseTime(anno.leaseTime())
.setTimeUnit(anno.timeUnit());
}
public LockEntity setKey(final String key) {
this.key = key;
return this;
}
public LockEntity setWaitTime(final long waitTime) {
this.waitTime = waitTime;
return this;
}
public LockEntity setLeaseTime(final long leaseTime) {
this.leaseTime = leaseTime;
return this;
}
public LockEntity setTimeUnit(final TimeUnit timeUnit) {
this.timeUnit = timeUnit;
return this;
}
public LockEntity setKeyList(final List<String> keyList) {
this.keyList = keyList;
return this;
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof LockEntity)) {
return false;
} else {
LockEntity other = (LockEntity)o;
if (!other.canEqual(this)) {
return false;
} else if (this.getWaitTime() != other.getWaitTime()) {
return false;
} else if (this.getLeaseTime() != other.getLeaseTime()) {
return false;
} else {
label52: {
Object this$key = this.getKey();
Object other$key = other.getKey();
if (this$key == null) {
if (other$key == null) {
break label52;
}
} else if (this$key.equals(other$key)) {
break label52;
}
return false;
}
Object this$timeUnit = this.getTimeUnit();
Object other$timeUnit = other.getTimeUnit();
if (this$timeUnit == null) {
if (other$timeUnit != null) {
return false;
}
} else if (!this$timeUnit.equals(other$timeUnit)) {
return false;
}
Object this$keyList = this.getKeyList();
Object other$keyList = other.getKeyList();
if (this$keyList == null) {
if (other$keyList != null) {
return false;
}
} else if (!this$keyList.equals(other$keyList)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof LockEntity;
}
@Override
public int hashCode() {
int result = 1;
long $waitTime = this.getWaitTime();
result = result * 59 + Long.hashCode($waitTime);
long $leaseTime = this.getLeaseTime();
result = result * 59 + Long.hashCode($leaseTime);
Object $key = this.getKey();
result = result * 59 + ($key == null ? 43 : $key.hashCode());
Object $timeUnit = this.getTimeUnit();
result = result * 59 + ($timeUnit == null ? 43 : $timeUnit.hashCode());
Object $keyList = this.getKeyList();
result = result * 59 + ($keyList == null ? 43 : $keyList.hashCode());
return result;
}
@Override
public String toString() {
return "LockEntity(key=" + this.getKey() +
", waitTime=" + this.getWaitTime() +
", leaseTime=" + this.getLeaseTime() +
", timeUnit=" + this.getTimeUnit() +
", keyList=" + this.getKeyList() + ")";
}
public LockEntity() {
this.timeUnit = TimeUnit.SECONDS;
}
}
DistributeLock.java
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁注解
*
* 用于在方法级别添加分布式锁功能,支持多种锁类型和键生成策略
* 通过AOP方式实现,在方法执行前获取锁,方法执行完成后释放锁
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributeLock {
/**
* 锁键
*/
String key() default "";
/**
* 键类型
*
* 指定锁键的生成策略
* @see KeyType
*/
KeyType keyType() default KeyType.STRING;
/**
* 锁类型
*
* 指定分布式锁的实现类型
* @see LockType
*/
LockType lockType() default LockType.FAIR_LOCK;
/**
* 等待时间
*
* 尝试获取锁的最大等待时间
* 默认60秒
*/
long waitTime() default 60L;
/**
* 锁的持有时间
*
* 锁的持有时间,-1表示永不过期
* 默认-1(永不过期)
*/
long leaseTime() default -1L;
/**
* 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
RedisUtils.java
java
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.redisson.api.BatchResult;
import org.redisson.api.ObjectListener;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBatch;
import org.redisson.api.RBucket;
import org.redisson.api.RBucketAsync;
import org.redisson.api.RKeys;
import org.redisson.api.RList;
import org.redisson.api.RListAsync;
import org.redisson.api.RMap;
import org.redisson.api.RMapAsync;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RSet;
import org.redisson.api.RTopic;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
/**
* Redis 工具类,封装了 Redisson 客户端的常用操作
*/
@Slf4j
public class RedisUtils {
private static final RedissonClient CLIENT;
static {
// 初始化 Redisson 客户端,添加空值检查
CLIENT = SpringUtil.getBean(RedissonClient.class);
if (CLIENT == null) {
throw new IllegalStateException("RedissonClient bean not found in Spring context");
}
}
/**
* 限流器操作
*
* @param key 限流器键
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔时间(秒)
* @return 可用许可数,-1表示获取失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, (long) rate, (long) rateInterval, RateIntervalUnit.SECONDS);
return rateLimiter.tryAcquire() ? rateLimiter.availablePermits() : -1L;
}
/**
* 获取 Redisson 客户端实例
*
* @return Redisson 客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/**
* 发布消息到指定频道
*
* @param channelKey 频道键
* @param msg 消息对象
* @param <T> 消息类型
*/
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
if (channelKey == null || consumer == null) {
throw new IllegalArgumentException("ChannelKey and consumer cannot be null");
}
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
consumer.accept(msg);
}
/**
* 发布消息到指定频道
*
* @param channelKey 频道键
* @param msg 消息对象
* @param <T> 消息类型
*/
public static <T> void publish(String channelKey, T msg) {
if (channelKey == null) {
throw new IllegalArgumentException("ChannelKey cannot be null");
}
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
}
/**
* 订阅指定频道的消息
*
* @param channelKey 频道键
* @param clazz 消息类型
* @param consumer 消息消费处理器
* @param <T> 消息类型
*/
public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
if (channelKey == null || clazz == null || consumer == null) {
throw new IllegalArgumentException("ChannelKey, clazz and consumer cannot be null");
}
RTopic topic = CLIENT.getTopic(channelKey);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
}
/**
* 根据模式匹配获取所有键
*
* @param pattern 键模式
* @return 匹配的键集合
*/
public static Collection<String> keys(final String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
throw new IllegalArgumentException("Pattern cannot be null or empty");
}
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
return stream.collect(Collectors.toList());
}
/**
* 根据模式删除匹配的键
*
* @param pattern 键模式
*/
public static void deleteKeys(final String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
throw new IllegalArgumentException("Pattern cannot be null or empty");
}
CLIENT.getKeys().deleteByPattern(pattern);
}
/**
* 判断键是否存在
*
* @param key 键
* @return 是否存在
*/
public static Boolean hasKey(String key) {
if (key == null) {
return false;
}
return CLIENT.getBucket(key).isExists();
}
/**
* 获取键的剩余过期时间
*
* @param key 键
* @return 剩余过期时间(毫秒)
*/
public static long getExpireTime(String key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
return CLIENT.getBucket(key).remainTimeToLive();
}
/**
* 设置键的过期时间
*
* @param key 键
* @param timeout 过期时间(秒)
* @return 设置是否成功
*/
public static boolean expire(final String key, final long timeout) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
return expire(key, Duration.ofSeconds(timeout));
}
/**
* 设置键的过期时间
*
* @param key 键
* @param duration 过期时间
* @return 设置是否成功
*/
public static boolean expire(final String key, final Duration duration) {
if (key == null || duration == null) {
throw new IllegalArgumentException("Key and duration cannot be null");
}
RBucket<Object> rBucket = CLIENT.getBucket(key);
return rBucket.expire(duration);
}
/**
* 设置缓存对象
*
* @param key 键
* @param value 值
* @param <T> 值类型
*/
public static <T> void setCacheObject(final String key, final T value) {
setCacheObject(key, value, false);
}
/**
* 设置缓存对象,支持保留TTL
*
* @param key 键
* @param value 值
* @param isSaveTtl 是否保留TTL
* @param <T> 值类型
*/
public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RBucket<T> bucket = CLIENT.getBucket(key);
if (isSaveTtl) {
try {
bucket.setAndKeepTTL(value);
} catch (Exception e) {
log.warn("Failed to keep TTL for key '{}', falling back to regular set: {}", key, e.getMessage());
long timeToLive = bucket.remainTimeToLive();
setCacheObject(key, value, Duration.ofMillis(timeToLive));
}
} else {
bucket.set(value);
}
}
/**
* 设置缓存对象并设置过期时间
*
* @param key 键
* @param value 值
* @param duration 过期时间
* @param <T> 值类型
*/
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
if (key == null || duration == null) {
throw new IllegalArgumentException("Key and duration cannot be null");
}
RBatch batch = CLIENT.createBatch();
RBucketAsync<T> bucket = batch.getBucket(key);
bucket.setAsync(value);
bucket.expireAsync(duration);
batch.execute();
}
/**
* 如果键不存在则设置值
*
* @param key 键
* @param value 值
* @param duration 过期时间
* @param <T> 值类型
* @return 是否设置成功
*/
public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RBucket<T> bucket = CLIENT.getBucket(key);
return bucket.setIfAbsent(value, duration);
}
/**
* 如果键存在则设置值
*
* @param key 键
* @param value 值
* @param duration 过期时间
* @param <T> 值类型
* @return 是否设置成功
*/
public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RBucket<T> bucket = CLIENT.getBucket(key);
return bucket.setIfExists(value, duration);
}
/**
* 为键添加监听器
*
* @param key 键
* @param listener 监听器
* @param <T> 值类型
*/
public static <T> void addObjectListener(final String key, final ObjectListener listener) {
if (key == null || listener == null) {
throw new IllegalArgumentException("Key and listener cannot be null");
}
RBucket<T> result = CLIENT.getBucket(key);
result.addListener(listener);
}
/**
* 获取缓存对象
*
* @param key 键
* @param <T> 值类型
* @return 缓存值
*/
public static <T> T getCacheObject(final String key) {
if (key == null) {
return null;
}
RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.get();
}
/**
* 删除缓存对象
*
* @param key 键
* @return 是否删除成功
*/
public static boolean deleteObject(final String key) {
if (key == null) {
return false;
}
return CLIENT.getBucket(key).delete();
}
/**
* 批量删除缓存对象
*
* @param keys 键集合
*/
public static void deleteObject(final Collection<String> keys) {
if (CollUtil.isEmpty(keys)) {
return;
}
RBatch batch = CLIENT.createBatch();
for (String key : keys) {
if (key != null) {
batch.getBucket(key).deleteAsync();
}
}
batch.execute();
}
/**
* 判断缓存对象是否存在
*
* @param key 键
* @return 是否存在
*/
public static boolean isExistsObject(final String key) {
if (key == null) {
return false;
}
return CLIENT.getBucket(key).isExists();
}
/**
* 覆盖缓存列表
*
* @param key 键
* @param dataList 数据列表
* @param <T> 列表元素类型
* @return 批量执行结果
*/
public static <T> BatchResult setCacheListOverride(final String key, final List<T> dataList) {
return setCacheListOverride(key, dataList, null);
}
/**
* 覆盖缓存列表并设置过期时间
*
* @param key 键
* @param dataList 数据列表
* @param duration 过期时间
* @param <T> 列表元素类型
* @return 批量执行结果
*/
public static <T> BatchResult setCacheListOverride(final String key, final List<T> dataList, Duration duration) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RBatch batch = CLIENT.createBatch();
batch.getBucket(key).deleteAsync();
if (dataList != null && !dataList.isEmpty()) {
batch.getList(key).addAllAsync(dataList);
}
if (duration != null) {
batch.getList(key).expireAsync(duration);
}
return batch.execute();
}
/**
* 设置列表指定索引位置的值
*
* @param key 键
* @param index 索引
* @param data 数据
* @param <T> 元素类型
* @return 旧值
*/
public static <T> T setCacheListIndex(final String key, final int index, final T data) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
return rList.set(index, data);
}
/**
* 向缓存列表添加元素
*
* @param key 键
* @param dataList 数据列表
* @param <T> 列表元素类型
* @return 是否添加成功
*/
public static <T> boolean addCacheList(final String key, final List<T> dataList) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
return rList.addAll(dataList);
}
/**
* 向缓存列表添加元素并设置过期时间
*
* @param key 键
* @param dataList 数据列表
* @param duration 过期时间
* @param <T> 列表元素类型
* @return 批量执行结果
*/
public static <T> BatchResult addCacheList(final String key, final List<T> dataList, Duration duration) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RBatch batch = CLIENT.createBatch();
RListAsync<T> list = batch.getList(key);
list.addAllAsync(dataList);
if (duration != null) {
list.expireAsync(duration);
}
return batch.execute();
}
/**
* 向缓存列表头部添加元素
*
* @param key 键
* @param data 数据
* @param <T> 元素类型
*/
public static <T> void addCacheListFirst(final String key, final List<T> data) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
rList.addAll(0, data);
}
/**
* 向缓存列表添加元素
*
* @param key 键
* @param data 数据
* @param <T> 元素类型
* @return 是否添加成功
*/
public static <T> boolean addCacheList(final String key, final T data) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
return rList.add(data);
}
/**
* 向缓存列表头部添加元素
*
* @param key 键
* @param data 数据
* @param <T> 元素类型
*/
public static <T> void addCacheListFirst(final String key, final T data) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
rList.add(0, data);
}
/**
* 为列表添加监听器
*
* @param key 键
* @param listener 监听器
* @param <T> 列表元素类型
*/
public static <T> void addListListener(final String key, final ObjectListener listener) {
if (key == null || listener == null) {
throw new IllegalArgumentException("Key and listener cannot be null");
}
RList<T> rList = CLIENT.getList(key);
rList.addListener(listener);
}
/**
* 获取缓存列表
*
* @param key 键
* @param <T> 列表元素类型
* @return 列表数据
*/
public static <T> List<T> getCacheList(final String key) {
if (key == null) {
return Collections.emptyList();
}
RList<T> rList = CLIENT.getList(key);
return rList.readAll();
}
/**
* 获取缓存列表范围数据
*
* @param key 键
* @param from 起始索引
* @param to 结束索引
* @param <T> 列表元素类型
* @return 列表数据
*/
public static <T> List<T> getCacheListRange(final String key, int from, int to) {
if (key == null) {
return Collections.emptyList();
}
RList<T> rList = CLIENT.getList(key);
return rList.range(from, to);
}
/**
* 获取缓存列表指定索引位置的元素
*
* @param key 键
* @param index 索引
* @param <T> 元素类型
* @return 元素值
*/
public static <T> T getCacheListIndex(final String key, final int index) {
if (key == null) {
return null;
}
RList<T> rList = CLIENT.getList(key);
return rList.get(index);
}
/**
* 移除缓存列表指定索引位置的元素
*
* @param key 键
* @param index 索引
*/
public static <T> void removeCacheListIndex(final String key, final int index) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
rList.fastRemove(index);
}
/**
* 移除缓存列表中指定元素
*
* @param key 键
* @param element 元素
* @param count 移除数量
* @param <T> 元素类型
* @return 是否移除成功
*/
public static <T> boolean removeCacheListCount(final String key, final T element, final int count) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RList<T> rList = CLIENT.getList(key);
return rList.remove(element, count);
}
/**
* 向缓存集合添加元素
*
* @param key 键
* @param dataSet 数据集
* @param <T> 元素类型
* @return 是否添加成功
*/
public static <T> boolean addCacheSet(final String key, final Set<T> dataSet) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RSet<T> rSet = CLIENT.getSet(key);
return rSet.addAll(dataSet);
}
/**
* 向缓存集合添加元素
*
* @param key 键
* @param data 数据
* @param <T> 元素类型
* @return 是否添加成功
*/
public static <T> boolean addCacheSet(final String key, final T data) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RSet<T> rSet = CLIENT.getSet(key);
return rSet.add(data);
}
/**
* 为集合添加监听器
*
* @param key 键
* @param listener 监听器
* @param <T> 集合元素类型
*/
public static <T> void addSetListener(final String key, final ObjectListener listener) {
if (key == null || listener == null) {
throw new IllegalArgumentException("Key and listener cannot be null");
}
RSet<T> rSet = CLIENT.getSet(key);
rSet.addListener(listener);
}
/**
* 获取缓存集合
*
* @param key 键
* @param <T> 集合元素类型
* @return 集合数据
*/
public static <T> Set<T> getCacheSet(final String key) {
if (key == null) {
return Collections.emptySet();
}
RSet<T> rSet = CLIENT.getSet(key);
return rSet.readAll();
}
/**
* 向缓存Map添加数据
*
* @param key 键
* @param dataMap 数据映射
* @param <T> 值类型
*/
public static <T> void addCacheMap(final String key, final Map<String, T> dataMap) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
if (!CollUtil.isEmpty(dataMap)) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.putAll(dataMap);
}
}
/**
* 覆盖缓存Map
*
* @param key 键
* @param dataMap 数据映射
* @param <T> 值类型
* @return 批量执行结果
*/
public static <T> BatchResult setCacheMapOverride(final String key, final Map<String, T> dataMap) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
if (CollUtil.isEmpty(dataMap)) {
return null;
}
RBatch batch = CLIENT.createBatch();
RMapAsync<String, T> map = batch.getMap(key);
map.clearAsync();
map.putAllAsync(dataMap);
return batch.execute();
}
/**
* 为Map添加监听器
*
* @param key 键
* @param listener 监听器
* @param <T> Map值类型
*/
public static <T> void addMapListener(final String key, final ObjectListener listener) {
if (key == null || listener == null) {
throw new IllegalArgumentException("Key and listener cannot be null");
}
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.addListener(listener);
}
/**
* 获取缓存Map
*
* @param key 键
* @param <T> 值类型
* @return Map数据
*/
public static <T> Map<String, T> getCacheMap(final String key) {
if (key == null) {
return Collections.emptyMap();
}
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.getAll(rMap.keySet());
}
/**
* 获取缓存Map的键集合
*
* @param key 键
* @param <T> 值类型
* @return 键集合
*/
public static <T> Set<String> getCacheMapKeySet(final String key) {
if (key == null) {
return Collections.emptySet();
}
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.keySet();
}
/**
* 设置缓存Map的值
*
* @param key 键
* @param hKey 哈希键
* @param value 值
* @param <T> 值类型
*/
public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
if (key == null || hKey == null) {
throw new IllegalArgumentException("Key and hKey cannot be null");
}
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.put(hKey, value);
}
/**
* 获取缓存Map的值
*
* @param key 键
* @param hKey 哈希键
* @param <T> 值类型
* @return 值
*/
public static <T> T getCacheMapValue(final String key, final String hKey) {
if (key == null || hKey == null) {
return null;
}
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.get(hKey);
}
/**
* 删除缓存Map的值
*
* @param key 键
* @param hKey 哈希键
* @param <T> 值类型
* @return 删除的值
*/
public static <T> T delCacheMapValue(final String key, final String hKey) {
if (key == null || hKey == null) {
return null;
}
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.remove(hKey);
}
/**
* 批量删除缓存Map的值
*
* @param key 键
* @param hKeys 哈希键集合
* @param <T> 值类型
*/
public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
if (key == null || CollUtil.isEmpty(hKeys)) {
return;
}
RBatch batch = CLIENT.createBatch();
RMapAsync<String, T> rMap = batch.getMap(key);
for (String hKey : hKeys) {
if (hKey != null) {
rMap.removeAsync(hKey);
}
}
batch.execute();
}
/**
* 批量获取缓存Map的值
*
* @param key 键
* @param hKeys 哈希键集合
* @param <K> 键类型
* @param <V> 值类型
* @return 值映射
*/
public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
if (key == null || CollUtil.isEmpty(hKeys)) {
return Collections.emptyMap();
}
RMap<K, V> rMap = CLIENT.getMap(key);
return rMap.getAll(hKeys);
}
/**
* 设置原子值
*
* @param key 键
* @param value 值
*/
public static void setAtomicValue(String key, long value) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RAtomicLong atomic = CLIENT.getAtomicLong(key);
atomic.set(value);
}
/**
* 获取原子值
*
* @param key 键
* @return 原子值
*/
public static long getAtomicValue(String key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.get();
}
/**
* 原子值自增
*
* @param key 键
* @return 自增后的值
*/
public static long incrAtomicValue(String key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.incrementAndGet();
}
/**
* 原子值自减
*
* @param key 键
* @return 自减后的值
*/
public static long decrAtomicValue(String key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.decrementAndGet();
}
private RedisUtils() {
// 工具类私有构造函数
}
}
SpelUtils.java
java
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Spring Expression Language (SpEL) 工具类
* 提供解析和执行 SpEL 表达式的功能,通常用于 AOP 切面中获取方法参数值
*/
public class SpelUtils {
private static final ExpressionParser expressionParser = new SpelExpressionParser();
private static final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
/**
* 解析 SpEL 表达式并返回字符串结果
*
* @param joinPoint 切点连接点,包含方法执行的相关信息
* @param value SpEL 表达式字符串
* @return 解析后的字符串结果
*/
public static String parse(JoinPoint joinPoint, String value) {
EvaluationContext context = getContext(joinPoint.getArgs(), getMethod(joinPoint));
return (String)getValue(context, value, String.class);
}
/**
* 获取表达式的值
*
* @param context SpEL 上下文环境
* @param value SpEL 表达式字符串
* @param clazz 返回值类型
* @param <T> 泛型类型
* @return 表达式计算结果
*/
private static <T> T getValue(EvaluationContext context, String value, Class<T> clazz) {
Expression expression = expressionParser.parseExpression(value);
return expression.getValue(context, clazz);
}
/**
* 创建 SpEL 上下文环境,并将方法参数设置为上下文中的变量
*
* @param arguments 方法参数数组
* @param signatureMethod 方法签名对象
* @return 包含方法参数作为变量的上下文环境
*/
private static EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
String[] parameterNames = parameterNameDiscoverer.getParameterNames(signatureMethod);
EvaluationContext context = new StandardEvaluationContext();
if (parameterNames == null) {
return context;
} else {
for(int i = 0; i < arguments.length; ++i) {
// 将参数名称与对应值设置到上下文中,便于 SpEL 表达式引用
context.setVariable(parameterNames[i], arguments[i]);
}
return context;
}
}
/**
* 获取切点对应的方法对象
* 处理接口代理的情况,确保获取到实际实现类的方法
*
* @param point 切点连接点
* @return 方法对象
*/
public static Method getMethod(JoinPoint point) {
try {
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
// 如果方法来自接口,则获取目标类的实际实现方法
return !method.getDeclaringClass().isInterface() ? method : point.getTarget().getClass().getDeclaredMethod(point.getSignature().getName(), method.getParameterTypes());
} catch (NoSuchMethodException var3) {
NoSuchMethodException e = var3;
throw new RuntimeException(e);
}
}
private SpelUtils() {
}
}
LockType.java
java
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
/**
* 分布式锁类型枚举
* 该枚举定义了多种分布式锁实现类型,包括公平锁、非公平锁、多锁、红锁和读写锁
* 每种锁类型都实现了统一的加锁和解锁接口,便于在不同场景下灵活选择
*
*/
public enum LockType {
/**
* 非公平锁
* 非公平锁允许线程在尝试获取锁时直接竞争,可能导致某些线程长时间等待
* 适用于对锁获取顺序没有严格要求的场景
*/
UNFAIR_LOCK {
@Override
public boolean lock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getLock(lock.getKey());
try {
// 尝试获取锁,设置等待时间和持有时间
return rLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getLock(lock.getKey());
// 确保当前线程持有锁才执行解锁
if (rLock.isHeldByCurrentThread()) {
rLock.unlockAsync(); // 异步解锁,提高性能
}
}
},
/**
* 公平锁
*
* 公平锁按照请求顺序分配锁,先请求的线程优先获得锁
* 保证了锁获取的公平性,但可能影响吞吐量
*/
FAIR_LOCK {
@Override
public boolean lock(LockEntity lock) {
// 获取公平锁实例
RLock rLock = RedisUtils.getClient().getFairLock(lock.getKey());
try {
// 尝试获取公平锁
return rLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock rLock = RedisUtils.getClient().getFairLock(lock.getKey());
if (rLock.isHeldByCurrentThread()) {
rLock.unlockAsync();
}
}
},
/**
* 多锁(MultiLock)
*
* MultiLock 将多个锁绑定为一个锁,只有当所有锁都获取成功时才算获取成功
* 适用于需要同时获取多个资源锁的场景
*/
MULTI_LOCK {
@Override
public boolean lock(LockEntity lock) {
// 将锁键列表转换为RLock数组
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
// 创建MultiLock实例
RedissonMultiLock multiLock = new RedissonMultiLock(locks);
try {
// 尝试获取所有锁
return multiLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
RedissonMultiLock multiLock = new RedissonMultiLock(locks);
// 释放所有锁
multiLock.unlock();
}
},
/**
* 红锁(RedLock)
*
* RedLock 是一种高可用的分布式锁算法,通过多个独立的Redis节点实现
* 即使部分Redis节点故障,仍能保证锁的正确性
*/
RED_LOCK {
@Override
public boolean lock(LockEntity lock) {
// 获取多个锁实例,用于实现RedLock算法
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
// 创建RedLock实例
RedissonRedLock redLock = new RedissonRedLock(locks);
try {
// 尝试获取红锁
return redLock.tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RLock[] locks = lock.getKeyList().stream()
.map(key -> RedisUtils.getClient().getLock(key))
.toArray(RLock[]::new);
RedissonRedLock redLock = new RedissonRedLock(locks);
// 释放红锁
redLock.unlock();
}
},
/**
* 读锁
*
* 读写锁中的读锁,允许多个线程同时读取共享资源
* 适用于读多写少的场景,提高并发性能
*/
READ_LOCK {
@Override
public boolean lock(LockEntity lock) {
// 获取读写锁实例的读锁部分
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
try {
// 尝试获取读锁
return rLock.readLock().tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
// 检查当前线程是否持有读锁
if (rLock.readLock().isHeldByCurrentThread()) {
rLock.readLock().unlockAsync();
}
}
},
/**
* 写锁
*
* 读写锁中的写锁,独占访问共享资源
* 确保写入操作的原子性和一致性
*/
WRITE_LOCK {
@Override
public boolean lock(LockEntity lock) {
// 获取读写锁实例的写锁部分
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
try {
// 尝试获取写锁
return rLock.writeLock().tryLock(lock.getWaitTime(), lock.getLeaseTime(), lock.getTimeUnit());
} catch (Exception e) {
return false;
}
}
@Override
public void unlock(LockEntity lock) {
RReadWriteLock rLock = RedisUtils.getClient().getReadWriteLock(lock.getKey());
// 检查当前线程是否持有写锁
if (rLock.writeLock().isHeldByCurrentThread()) {
rLock.writeLock().unlockAsync();
}
}
};
/**
* 尝试获取锁
*
* @return 获取锁是否成功
*/
public abstract boolean lock(LockEntity lock);
/**
* 释放锁
*
* @param lock 锁实体
*/
public abstract void unlock(LockEntity lock);
private LockType() {
}
}
KeyType.java
java
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 锁键类型枚举
* 定义了分布式锁键的生成策略,包括字符串模板和SpEL表达式两种方式
*
*/
public enum KeyType {
/**
* 字符串键类型
*
* 支持直接使用配置的键值,或者根据方法签名和参数自动生成键
* 当配置的键为空时,会自动拼接方法名和参数作为锁键
*/
STRING {
@Override
public LockEntity resolve(ProceedingJoinPoint joinPoint, DistributeLock lock) {
// 解析注解属性并创建锁实体
LockEntity lockEntity = (new LockEntity()).parseLockAnno(lock);
String key = lock.key();
// 如果没有显式指定键,则根据方法签名和参数自动生成
if (StrUtil.isBlank(key)) {
String methodName = joinPoint.getSignature().toString();
String argsStr = StrUtil.join(",", joinPoint.getArgs());
key = StrUtil.concat(true, new CharSequence[]{methodName, "-", argsStr});
}
return lockEntity.setKey(key);
}
},
/**
* SpEL表达式键类型
*
* 使用Spring Expression Language解析动态键值
* 允许在注解中使用复杂的表达式来动态生成锁键
*/
SPEL {
@Override
public LockEntity resolve(ProceedingJoinPoint joinPoint, DistributeLock lock) {
// 解析注解属性并创建锁实体
LockEntity lockEntity = (new LockEntity()).parseLockAnno(lock);
// 使用SpEL表达式解析器计算实际键值
String key = SpelUtils.parse(joinPoint, lock.key());
return lockEntity.setKey(key);
}
};
/**
* 解析锁键
*
* 根据不同的键类型策略,从切点和注解信息中解析出具体的锁键
*
* @param joinPoint AOP切点,包含目标方法的执行上下文
* @param lock 分布式锁注解,包含锁的配置信息
* @return 包含解析后锁键的锁实体
*/
public abstract LockEntity resolve(ProceedingJoinPoint joinPoint, DistributeLock lock);
/**
* 私有构造函数,防止外部实例化
*/
private KeyType() {
}
}
BaseSchedule.java
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
* 定时任务基类
* 提供带分布式锁的定时任务执行框架,确保同一时间只有一个线程执行指定的任务
*/
public abstract class BaseSchedule {
private static final Logger log = LoggerFactory.getLogger(BaseSchedule.class);
public BaseSchedule() {
}
/**
* 执行带分布式锁的定时任务
* 使用写锁确保任务的互斥执行,防止多实例或多线程环境下重复执行
* 锁的租约时间为5秒,单位为秒
*
* @param taskName 任务名称,用于日志记录和识别
* @param cacheKey 缓存键,用于分布式锁的标识,会自动添加"schedule:"前缀
* @param func 要执行的任务逻辑,实现Runnable接口
*/
protected void execute(String taskName, String cacheKey, Runnable func) {
LockType lock = LockType.WRITE_LOCK;
cacheKey = "schedule:" + cacheKey;
LockEntity lockEntity = (new LockEntity()).setKey(cacheKey).setWaitTime(0L).setLeaseTime(5L).setTimeUnit(TimeUnit.SECONDS);
String threadName = Thread.currentThread().getName();
boolean success = lock.lock(lockEntity);
if (!success) {
log.debug("定时任务: {}, 取消执行. {}", taskName, threadName);
} else {
try {
log.debug("定时任务: {}, 开始执行. {}", taskName, threadName);
func.run();
} catch (Exception var12) {
log.error("定时任务: {}, 执行出错. {}", taskName, threadName, var12);
} finally {
lock.unlock(lockEntity);
log.debug("定时任务: {}, 执行结束. {}", taskName, threadName);
}
}
}
}
CronScheduleTask.java
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class CronScheduleTask extends BaseSchedule {
/**
* 每分钟执行一次
*/
@Scheduled(cron = "0 * * * * ?")
public void executeSampleTask() {
execute("示例定时任务", "sample-task", () -> {
log.info("执行示例定时任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
log.info("示例定时任务执行完成");
});
}
/**
* 每天凌晨2点执行的任务
*/
@Scheduled(cron = "0 0 2 * * ?")
public void executeDailyTask() {
execute("每日定时任务", "daily-task", () -> {
// 具体业务逻辑
log.info("执行每日定时任务");
// 实现具体业务
});
}
/**
* 每小时执行的任务
*/
@Scheduled(cron = "0 0 * * * ?")
public void executeHourlyTask() {
execute("每小时任务", "hourly-task", () -> {
log.info("执行每小时任务");
});
}
}
五、总结
-
统一抽象 :通过
[LockType]枚举提供统一的分布式锁接口,封装不同锁类型的实现细节 -
多样化实现:支持非公平锁、公平锁、多锁、红锁和读写锁等多种锁类型,满足不同业务场景需求
-
安全性保障 :集成
[Redisson]的成熟分布式锁算法,确保锁的安全性和可靠性 -
易用性设计 :通过
[LockEntity]统一参数管理,简化锁的使用复杂度