✨ 新增DistributeLock (#128)
* ✨ 新增DistributeLock Co-authored-by: huyuanzhi <hyz_sound@163.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import com.hccake.ballcat.common.redis.config.CachePropertiesHolder;
|
|||||||
import com.hccake.ballcat.common.redis.core.annotation.CacheDel;
|
import com.hccake.ballcat.common.redis.core.annotation.CacheDel;
|
||||||
import com.hccake.ballcat.common.redis.core.annotation.CachePut;
|
import com.hccake.ballcat.common.redis.core.annotation.CachePut;
|
||||||
import com.hccake.ballcat.common.redis.core.annotation.Cached;
|
import com.hccake.ballcat.common.redis.core.annotation.Cached;
|
||||||
|
import com.hccake.ballcat.common.redis.lock.DistributedLock;
|
||||||
import com.hccake.ballcat.common.redis.operation.CacheDelOps;
|
import com.hccake.ballcat.common.redis.operation.CacheDelOps;
|
||||||
import com.hccake.ballcat.common.redis.operation.CachedOps;
|
import com.hccake.ballcat.common.redis.operation.CachedOps;
|
||||||
import com.hccake.ballcat.common.redis.operation.CachePutOps;
|
import com.hccake.ballcat.common.redis.operation.CachePutOps;
|
||||||
@@ -155,29 +156,18 @@ public class CacheStringAspect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2.==========如果缓存为空 则需查询数据库并更新===============
|
// 2.==========如果缓存为空 则需查询数据库并更新===============
|
||||||
Object dbData = null;
|
cacheData = DistributedLock.<String>builder().action(ops.lockKey(), () -> {
|
||||||
// 尝试获取锁,只允许一个线程更新缓存
|
String cacheValue = cacheQuery.get();
|
||||||
String reqId = UUID.randomUUID().toString();
|
if (cacheValue == null) {
|
||||||
if (CacheLock.lock(ops.lockKey(), reqId)) {
|
|
||||||
// 有可能其他线程已经更新缓存,这里再次判断缓存是否为空
|
|
||||||
cacheData = cacheQuery.get();
|
|
||||||
if (cacheData == null) {
|
|
||||||
// 从数据库查询数据
|
// 从数据库查询数据
|
||||||
dbData = ops.joinPoint().proceed();
|
Object dbValue = ops.joinPoint().proceed();
|
||||||
// 如果数据库中没数据,填充一个String,防止缓存击穿
|
// 如果数据库中没数据,填充一个String,防止缓存击穿
|
||||||
cacheData = dbData == null ? CachePropertiesHolder.nullValue() : cacheSerializer.serialize(dbData);
|
cacheValue = dbValue == null ? CachePropertiesHolder.nullValue() : cacheSerializer.serialize(dbValue);
|
||||||
// 设置缓存
|
// 设置缓存
|
||||||
ops.cachePut().accept(cacheData);
|
ops.cachePut().accept(cacheValue);
|
||||||
}
|
}
|
||||||
// 解锁
|
return cacheValue;
|
||||||
CacheLock.releaseLock(ops.lockKey(), reqId);
|
}).fail(cacheQuery).lock();
|
||||||
// 返回数据
|
|
||||||
return dbData;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cacheData = cacheQuery.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自旋时间内未获取到锁,或者数据库中数据为空,返回null
|
// 自旋时间内未获取到锁,或者数据库中数据为空,返回null
|
||||||
if (cacheData == null || ops.nullValue(cacheData)) {
|
if (cacheData == null || ops.nullValue(cacheData)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hccake.ballcat.common.redis.lock;
|
||||||
|
|
||||||
|
import com.hccake.ballcat.common.redis.lock.function.ThrowingSupplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author huyuanzhi 锁住的方法
|
||||||
|
* @param <T> 返回类型
|
||||||
|
*/
|
||||||
|
public interface Action<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行方法
|
||||||
|
* @param supplier 执行方法
|
||||||
|
* @return 状态处理器
|
||||||
|
*/
|
||||||
|
StateHandler<T> action(String lockKey, ThrowingSupplier<? extends T> supplier);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.hccake.ballcat.common.redis.lock;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import com.hccake.ballcat.common.redis.core.CacheLock;
|
||||||
|
import com.hccake.ballcat.common.redis.lock.function.ThrowingSupplier;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author huyuanzhi
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2021/11/16 分布式锁操作类
|
||||||
|
*/
|
||||||
|
public final class DistributedLock<T> implements Action<T>, StateHandler<T> {
|
||||||
|
|
||||||
|
Object result;
|
||||||
|
|
||||||
|
String key;
|
||||||
|
|
||||||
|
ThrowingSupplier<? extends T> executeAction;
|
||||||
|
|
||||||
|
Function<? super T, ? extends T> successAction;
|
||||||
|
|
||||||
|
Supplier<? extends T> lockFailAction;
|
||||||
|
|
||||||
|
Consumer<? super Throwable> exceptionAction;
|
||||||
|
|
||||||
|
private DistributedLock() {
|
||||||
|
this.exceptionAction = this::throwException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Action<T> builder() {
|
||||||
|
return new DistributedLock<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateHandler<T> action(String lockKey, ThrowingSupplier<? extends T> action) {
|
||||||
|
Assert.isTrue(this.executeAction == null, "execute action has been already set");
|
||||||
|
Assert.notNull(action, "execute action cant be null");
|
||||||
|
Assert.notBlank(lockKey, "lock key cant be blank");
|
||||||
|
this.executeAction = action;
|
||||||
|
this.key = lockKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateHandler<T> success(Function<? super T, ? extends T> action) {
|
||||||
|
Assert.isTrue(this.successAction == null, "success action has been already set");
|
||||||
|
Assert.notNull(action, "success action cant be null");
|
||||||
|
this.successAction = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateHandler<T> fail(Supplier<? extends T> action) {
|
||||||
|
Assert.isTrue(this.lockFailAction == null, "lock fail action has been already set");
|
||||||
|
Assert.notNull(action, "lock fail action cant be null");
|
||||||
|
this.lockFailAction = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateHandler<T> exception(Consumer<? super Throwable> action) {
|
||||||
|
Assert.notNull(action, "exception action cant be null");
|
||||||
|
this.exceptionAction = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T lock() {
|
||||||
|
String requestId = UUID.randomUUID().toString();
|
||||||
|
if (Boolean.TRUE.equals(CacheLock.lock(this.key, requestId))) {
|
||||||
|
T ret = null;
|
||||||
|
boolean exResolved = false;
|
||||||
|
try {
|
||||||
|
ret = executeAction.get();
|
||||||
|
this.setValue(ret);
|
||||||
|
}
|
||||||
|
catch (Throwable e) {
|
||||||
|
handleException(e);
|
||||||
|
exResolved = true;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
CacheLock.releaseLock(this.key, requestId);
|
||||||
|
}
|
||||||
|
if (!exResolved && this.successAction != null) {
|
||||||
|
this.setValue(this.successAction.apply(ret));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (lockFailAction != null) {
|
||||||
|
this.setValue(lockFailAction.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T) this.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleException(Throwable e) {
|
||||||
|
this.exceptionAction.accept(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(Object result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <E extends Throwable> void throwException(Throwable t) throws E {
|
||||||
|
throw (E) t;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.hccake.ballcat.common.redis.lock;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author huyuanzhi 状态处理器
|
||||||
|
* @param <T> 返回类型
|
||||||
|
*/
|
||||||
|
public interface StateHandler<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取锁成功,业务方法执行成功回调
|
||||||
|
* @param action 回调方法引用
|
||||||
|
* @return 状态处理器
|
||||||
|
*/
|
||||||
|
StateHandler<T> success(Function<? super T, ? extends T> action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取锁失败回调
|
||||||
|
* @param action 回调方法引用
|
||||||
|
* @return 状态处理器
|
||||||
|
*/
|
||||||
|
StateHandler<T> fail(Supplier<? extends T> action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取锁成功,执行业务方法异常回调
|
||||||
|
* @param action 回调方法引用
|
||||||
|
* @return 状态处理器
|
||||||
|
*/
|
||||||
|
StateHandler<T> exception(Consumer<? super Throwable> action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终态,获取锁
|
||||||
|
*/
|
||||||
|
T lock();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.hccake.ballcat.common.redis.lock.function;
|
||||||
|
|
||||||
|
public interface ThrowingSupplier<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可抛异常的supplier
|
||||||
|
* @return T
|
||||||
|
* @throws Throwable 异常
|
||||||
|
*/
|
||||||
|
T get() throws Throwable;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.hccake.ballcat.common.redis.test;
|
||||||
|
|
||||||
|
import com.hccake.ballcat.common.redis.lock.DistributedLock;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author huyuanzhi
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2021/11/16
|
||||||
|
*/
|
||||||
|
@SpringJUnitConfig(RedisConfiguration.class)
|
||||||
|
class DistributeLockTest {
|
||||||
|
|
||||||
|
String lockKey = "ballcat";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess() {
|
||||||
|
String lock = DistributedLock.<String>builder().action(lockKey, () -> lockKey).success(ret -> ret + ret).lock();
|
||||||
|
System.out.println(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testException() {
|
||||||
|
String lock = DistributedLock.<String>builder().action(lockKey, this::get).success(ret -> ret + ret)
|
||||||
|
.exception(e -> System.out.println("发生异常了")).lock();
|
||||||
|
System.out.println(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get() throws IOException {
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.hccake.ballcat.common.redis.test;
|
||||||
|
|
||||||
|
import com.hccake.ballcat.common.redis.config.CacheProperties;
|
||||||
|
import com.hccake.ballcat.common.redis.config.CachePropertiesHolder;
|
||||||
|
import com.hccake.ballcat.common.redis.core.CacheLock;
|
||||||
|
import com.hccake.ballcat.common.redis.prefix.IRedisPrefixConverter;
|
||||||
|
import com.hccake.ballcat.common.redis.prefix.impl.DefaultRedisPrefixConverter;
|
||||||
|
import com.hccake.ballcat.common.redis.serialize.PrefixStringRedisSerializer;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author huyuanzhi
|
||||||
|
* @version 1.0
|
||||||
|
* @date 2021/11/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(CacheProperties.class)
|
||||||
|
public class RedisConfiguration {
|
||||||
|
|
||||||
|
private static final String host = "localhost";
|
||||||
|
private static final int port = 6379;
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CachePropertiesHolder cachePropertiesHolder(CacheProperties cacheProperties) {
|
||||||
|
CachePropertiesHolder cachePropertiesHolder = new CachePropertiesHolder();
|
||||||
|
cachePropertiesHolder.setCacheProperties(cacheProperties);
|
||||||
|
return cachePropertiesHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisConnectionFactory redisConnectionFactory(){
|
||||||
|
LettuceConnectionFactory factory = new LettuceConnectionFactory(host,port);
|
||||||
|
factory.afterPropertiesSet();
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnClass(IRedisPrefixConverter.class)
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public StringRedisTemplate stringRedisTemplate(IRedisPrefixConverter redisPrefixConverter,
|
||||||
|
RedisConnectionFactory redisConnectionFactory) {
|
||||||
|
StringRedisTemplate template = new StringRedisTemplate();
|
||||||
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
|
template.setKeySerializer(new PrefixStringRedisSerializer(redisPrefixConverter));
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CacheLock cacheLock(StringRedisTemplate stringRedisTemplate) {
|
||||||
|
CacheLock cacheLock = new CacheLock();
|
||||||
|
cacheLock.setStringRedisTemplate(stringRedisTemplate);
|
||||||
|
return cacheLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(IRedisPrefixConverter.class)
|
||||||
|
public IRedisPrefixConverter redisPrefixConverter() {
|
||||||
|
return new DefaultRedisPrefixConverter("ballcat");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user