Initial commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class RedissonClientFactory {
|
||||
|
||||
private static volatile Map<String, RedissonClient> redisMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static void putRedissonClient(String beanName, RedissonClient redissonClient) {
|
||||
redisMap.put(beanName, redissonClient);
|
||||
}
|
||||
|
||||
public static RedissonClient getRedissonClient(String beanName) {
|
||||
if (ObjectEmptyUtils.isEmpty(beanName) || !redisMap.containsKey(beanName)) {
|
||||
throw new IllegalStateException("(redisson)bean-对应的RedissonClient未注册:" + beanName);
|
||||
}
|
||||
return redisMap.get(beanName);
|
||||
}
|
||||
|
||||
public static Map<String, RedissonClient> getRedisMap() {
|
||||
return redisMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 锁
|
||||
**/
|
||||
@SuppressWarnings("all")
|
||||
public interface RedissonClusterLockerService {
|
||||
|
||||
default public <T, R> R wrapLock(T source, Function<T, String> fun,
|
||||
Function<T, String> keyGetter, Function<T, R> wrapFun) {
|
||||
return wrapLock(source, fun, keyGetter, wrapFun, 15L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
<T, R> R wrapLock(T source, Function<T, String> fun,
|
||||
Function<T, String> keyGetter, Function<T, R> wrapFun,
|
||||
Long leaseTime, TimeUnit leaseTimeUnit);
|
||||
|
||||
default public <T, R> R wrapLock(T source, Function<T, String> keyGetter, Function<T, R> wrapFun) {
|
||||
return wrapLock(source, (t) -> {
|
||||
String key = keyGetter.apply(source);
|
||||
return delivery(key);
|
||||
}, keyGetter, wrapFun);
|
||||
}
|
||||
|
||||
String delivery(String key);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 支持注解获取锁
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RedissonLockAction {
|
||||
|
||||
/**
|
||||
* RedissonConfig 中 remote 的 key 名字
|
||||
*/
|
||||
String redissonName();
|
||||
/**
|
||||
* 锁的资源,key。支持spring El表达式
|
||||
*/
|
||||
@AliasFor("key")
|
||||
String value() default "'default'";
|
||||
@AliasFor("value")
|
||||
String key() default "'default'";
|
||||
/**
|
||||
* 锁类型
|
||||
*/
|
||||
RedissonLockType lockType() default RedissonLockType.REENTRANT_LOCK;
|
||||
/**
|
||||
* 获取锁等待时间,默认3秒
|
||||
*/
|
||||
long waitTime() default 3000L;
|
||||
/**
|
||||
* 锁自动释放时间,默认10秒
|
||||
*/
|
||||
long leaseTime() default 10000L;
|
||||
/**
|
||||
* 时间单位
|
||||
*/
|
||||
TimeUnit unit() default TimeUnit.MILLISECONDS;
|
||||
/**
|
||||
* 锁失败是否Throw异常
|
||||
*/
|
||||
boolean throwException() default false;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
/**
|
||||
* 锁类型
|
||||
*/
|
||||
public enum RedissonLockType {
|
||||
/**
|
||||
* 读锁
|
||||
*/
|
||||
READ_LOCK,
|
||||
/**
|
||||
* 写锁
|
||||
*/
|
||||
WRITE_LOCK,
|
||||
/**
|
||||
* 可重入锁
|
||||
*/
|
||||
REENTRANT_LOCK,
|
||||
/**
|
||||
* 公平锁
|
||||
*/
|
||||
FAIR_LOCK;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.lingniu.framework.plugin.redisson.builder;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public abstract class RedissonAbstractClientBuilder {
|
||||
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
protected RedissonAbstractClientBuilder(RedissonProperties redissonProperties) {
|
||||
this.redissonProperties = redissonProperties;
|
||||
}
|
||||
|
||||
protected abstract void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts);
|
||||
|
||||
public Config build() {
|
||||
Config redissionConfig = new Config();
|
||||
Set<RedisHostAndPort> redisHostAndPorts = parseRedisHostAndPort(redissonProperties.getRedisAddresses());
|
||||
config(redissionConfig, redisHostAndPorts);
|
||||
return redissionConfig;
|
||||
}
|
||||
|
||||
public Set<RedisHostAndPort> parseRedisHostAndPort(String redisAddress) {
|
||||
Set<RedisHostAndPort> redisHostAndPorts = new HashSet<>();
|
||||
// 分离地址部分和密码部分
|
||||
String[] parts = redisAddress.split(";");
|
||||
String addressPart = parts[0];
|
||||
String password = parts.length > 1 ? parts[1] : null;
|
||||
// 解析host:port格式的地址
|
||||
String[] hosts = addressPart.split(",");
|
||||
for (String hostPort : hosts) {
|
||||
hostPort = hostPort.trim();
|
||||
if (hostPort.contains(":")) {
|
||||
int colonIndex = hostPort.indexOf(":");
|
||||
String host = hostPort.substring(0, colonIndex);
|
||||
int port = Integer.parseInt(hostPort.substring(colonIndex + 1));
|
||||
RedisHostAndPort redisHostAndPort = new RedisHostAndPort();
|
||||
redisHostAndPort.setHost(host);
|
||||
redisHostAndPort.setPort(port);
|
||||
if (password != null) {
|
||||
redisHostAndPort.setPkey(password);
|
||||
}
|
||||
redisHostAndPorts.add(redisHostAndPort);
|
||||
}
|
||||
}
|
||||
return redisHostAndPorts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lingniu.framework.plugin.redisson.builder;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import cn.lingniu.framework.plugin.util.ip.IpUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.config.ClusterServersConfig;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.ReadMode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @description: 集群类型
|
||||
**/
|
||||
public class RedissonClusterClientBuilder extends RedissonAbstractClientBuilder {
|
||||
|
||||
public RedissonClusterClientBuilder(RedissonProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts) {
|
||||
ClusterServersConfig clusterServersConfig = redssionConfig.useClusterServers();
|
||||
if (StringUtils.isEmpty(getRedissonProperties().getClientName())) {
|
||||
clusterServersConfig.setClientName(ApplicationNameContext.getApplicationName() + ":" + IpUtil.getIp());
|
||||
}
|
||||
clusterServersConfig.setMasterConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
|
||||
clusterServersConfig.setSlaveConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
|
||||
clusterServersConfig.setMasterConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
|
||||
clusterServersConfig.setSlaveConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
|
||||
clusterServersConfig.setIdleConnectionTimeout(getRedissonProperties().getIdleConnectionTimeout());
|
||||
clusterServersConfig.setConnectTimeout(getRedissonProperties().getConnectTimeout());
|
||||
clusterServersConfig.setTimeout(getRedissonProperties().getTimeout());
|
||||
clusterServersConfig.setRetryAttempts(getRedissonProperties().getRetryAttempts());
|
||||
RedisHostAndPort redisHostAndPort = redisHostAndPorts.stream().findAny().get();
|
||||
clusterServersConfig.setPassword(redisHostAndPort.getPkey());
|
||||
clusterServersConfig.setCheckSlotsCoverage(getRedissonProperties().getCheckSlotsCoverage());
|
||||
clusterServersConfig.setReadMode(ReadMode.valueOf(getRedissonProperties().getReadMode()));
|
||||
redisHostAndPorts.stream().forEach(h -> {
|
||||
clusterServersConfig.addNodeAddress(String.format("redis://%s/", h.getHost() + ":" + h.getPort()));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.lingniu.framework.plugin.redisson.builder;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import cn.lingniu.framework.plugin.util.ip.IpUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* @description: 单节点类型
|
||||
**/
|
||||
public class RedissonStandaloneClientBuilder extends RedissonAbstractClientBuilder {
|
||||
|
||||
|
||||
public RedissonStandaloneClientBuilder(RedissonProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts) {
|
||||
SingleServerConfig singleServerConfig = redssionConfig.useSingleServer();
|
||||
if (StringUtils.isEmpty(getRedissonProperties().getClientName())) {
|
||||
singleServerConfig.setClientName(ApplicationNameContext.getApplicationName() + ":" + IpUtil.getIp());
|
||||
}
|
||||
singleServerConfig.setDatabase(getRedissonProperties().getDatabase());
|
||||
singleServerConfig.setConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
|
||||
singleServerConfig.setConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
|
||||
singleServerConfig.setIdleConnectionTimeout(getRedissonProperties().getIdleConnectionTimeout());
|
||||
singleServerConfig.setConnectTimeout(getRedissonProperties().getConnectTimeout());
|
||||
singleServerConfig.setTimeout(getRedissonProperties().getTimeout());
|
||||
singleServerConfig.setRetryAttempts(getRedissonProperties().getRetryAttempts());
|
||||
RedisHostAndPort redisHostAndPort = redisHostAndPorts.stream().findAny().get();
|
||||
singleServerConfig.setPassword(redisHostAndPort.getPkey());
|
||||
singleServerConfig.setAddress(String.format("redis://%s/", redisHostAndPort.getHost() + ":" + redisHostAndPort.getPort()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RedisHostAndPort {
|
||||
|
||||
private String host;
|
||||
|
||||
private int port;
|
||||
|
||||
private String pkey;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 集群类型
|
||||
**/
|
||||
public enum RedisType {
|
||||
STANDALONE("standalone", "单节点类型"),
|
||||
CLUSTER("cluster", "集群类型"),
|
||||
SENTINEL("sentinel", "哨兵类型");
|
||||
|
||||
@Getter
|
||||
private String type;
|
||||
|
||||
@Getter
|
||||
private String memo;
|
||||
|
||||
RedisType(String type, String memo) {
|
||||
this.type = type;
|
||||
this.memo = memo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* redisson 配置
|
||||
*/
|
||||
@ConfigurationProperties(prefix = RedissonConfig.PREFIX)
|
||||
@Data
|
||||
public class RedissonConfig {
|
||||
|
||||
public static final String PREFIX = "framework.lingniu.redisson";
|
||||
|
||||
|
||||
private Map<String, RedissonProperties> remote = new HashMap<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
/**
|
||||
* 详细配置
|
||||
*/
|
||||
@Data
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* redis连接串信息 host:port,host:port, 多个用,分割,最后一个是;是密码,可以不配置
|
||||
*/
|
||||
private String redisAddresses;
|
||||
|
||||
@Pattern(regexp = "^(SLAVE|MASTER|MASTER_SLAVE)$", message = "readMode must be one of SLAVE, MASTER, MASTER_SLAVE")
|
||||
private String readMode = "MASTER";
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private RedisType storageType = RedisType.CLUSTER;
|
||||
private String clientName = "";
|
||||
|
||||
@Min(value = 0, message = "database must be greater than or equal to 0")
|
||||
@Max(value = 15, message = "database must be less than or equal to 15")
|
||||
private int database = 0;
|
||||
|
||||
private int connectionMinimumIdleSize = 12;
|
||||
|
||||
private int connectionPoolSize = 32;
|
||||
|
||||
private int idleConnectionTimeout = 10000;
|
||||
|
||||
private int connectTimeout = 2000;
|
||||
|
||||
private int timeout = 2000;
|
||||
|
||||
private int retryAttempts = 4;
|
||||
//cluster: Slots检查
|
||||
private Boolean checkSlotsCoverage = true;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClusterLockerService;
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 初始化-RedissonClusterLockerService
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableConfigurationProperties({RedissonConfig.class})
|
||||
public class RedissonAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RedissonClusterLockerService redissonClusterLockerService() {
|
||||
return new RedissonClusterLockerService() {
|
||||
@Override
|
||||
public <T, R> R wrapLock(T source, Function<T, String> fun, Function<T, String> keyGetter, Function<T, R> wrapFun, Long leaseTime, TimeUnit leaseTimeUnit) {
|
||||
RedissonClient client = Optional.ofNullable(RedissonClientFactory.getRedissonClient(fun.apply(source))).orElseThrow(() -> new IllegalStateException("Redisson client not found for: " + fun.apply(source)));
|
||||
RLock lock = client.getLock(keyGetter.apply(source));
|
||||
try {
|
||||
lock.lock(leaseTime, leaseTimeUnit);
|
||||
return wrapFun.apply(source);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delivery(String key) {
|
||||
return new ArrayList<>(RedissonClientFactory.getRedisMap().keySet()).toArray(new String[]{})[Math.abs(key.hashCode() % RedissonClientFactory.getRedisMap().size())];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedissonBeanRegisterPostProcessor redissonBeanRegister() {
|
||||
return new RedissonBeanRegisterPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
|
||||
import cn.lingniu.framework.plugin.redisson.builder.RedissonClusterClientBuilder;
|
||||
import cn.lingniu.framework.plugin.redisson.builder.RedissonStandaloneClientBuilder;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisType;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonConfig;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedissonBeanRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
RedissonConfig config = Binder.get(applicationContext.getEnvironment()).bind(RedissonConfig.PREFIX, RedissonConfig.class).orElse(new RedissonConfig());
|
||||
Map<String, RedissonProperties> remote = config.getRemote();
|
||||
Optional.ofNullable(remote).ifPresent(b -> b.forEach((k, v) -> {
|
||||
if (applicationContext.containsBean(k)) {
|
||||
throw new IllegalStateException("redisson的实例对象已注册,请查看配置:" + k);
|
||||
}
|
||||
registerContainer(registry, k, v);
|
||||
}));
|
||||
}
|
||||
|
||||
private void registerContainer(BeanDefinitionRegistry registry, String beanName, RedissonProperties redissonProperties) {
|
||||
Config redissonConfig = null;
|
||||
if (RedisType.STANDALONE.equals(redissonProperties.getStorageType())) {
|
||||
redissonConfig = new RedissonStandaloneClientBuilder(redissonProperties).build();
|
||||
} else if (RedisType.CLUSTER.equals(redissonProperties.getStorageType())) {
|
||||
redissonConfig = new RedissonClusterClientBuilder(redissonProperties).build();
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的Redisson存储类型:" + redissonProperties.getStorageType());
|
||||
}
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Redisson.class);
|
||||
builder.addConstructorArgValue(redissonConfig);
|
||||
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
|
||||
// 从ApplicationContext中获取已注册的Bean实例
|
||||
RedissonClient jedisCluster = applicationContext.getBean(beanName, RedissonClient.class);
|
||||
RedissonClientFactory.putRedissonClient(beanName, jedisCluster);
|
||||
if (!registry.containsBeanDefinition("redissonDistributedLockAspectConfiguration")) {
|
||||
registry.registerBeanDefinition("redissonDistributedLockAspectConfiguration",
|
||||
BeanDefinitionBuilder.rootBeanDefinition(RedissonDistributedLockAspectConfiguration.class).getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonLockAction;
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Aspect 实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class RedissonDistributedLockAspectConfiguration {
|
||||
|
||||
private ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
@Pointcut("@annotation(cn.lingniu.framework.plugin.redisson.RedissonLockAction)")
|
||||
public void lockPoint() {
|
||||
}
|
||||
|
||||
@Around("lockPoint()")
|
||||
public Object around(ProceedingJoinPoint pjp) throws Throwable {
|
||||
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
|
||||
RedissonLockAction redissonLockAction = method.getAnnotation(RedissonLockAction.class);
|
||||
Object[] args = pjp.getArgs();
|
||||
String key = redissonLockAction.value();
|
||||
key = parse(key, method, args);
|
||||
|
||||
RLock lock = getLock(key, redissonLockAction);
|
||||
if (!lock.tryLock(redissonLockAction.waitTime(), redissonLockAction.leaseTime(), redissonLockAction.unit())) {
|
||||
log.warn("RedissonLockAction-获取锁失败: [{}]", key);
|
||||
if (redissonLockAction.throwException())
|
||||
throw new RuntimeException(String.format("RedissonLockAction-获取锁失败:[%s]", key));
|
||||
return null;
|
||||
}
|
||||
log.info("RedissonLockAction-获取锁成功: [{}]", key);
|
||||
try {
|
||||
return pjp.proceed();
|
||||
} catch (Exception e) {
|
||||
log.error(String.format("RedissonLockAction-方法锁执行异常:[%s]", key), e);
|
||||
throw e;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
log.info("RedissonLockAction-释放锁成功: [{}]", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析spring EL表达式
|
||||
*/
|
||||
private String parse(String key, Method method, Object[] args) {
|
||||
String[] params = discoverer.getParameterNames(method);
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
context.setVariable(params[i], args[i]);
|
||||
}
|
||||
return parser.parseExpression(key).getValue(context, String.class);
|
||||
}
|
||||
|
||||
private RLock getLock(String key, RedissonLockAction redissonLockAction) {
|
||||
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient(redissonLockAction.redissonName());
|
||||
switch (redissonLockAction.lockType()) {
|
||||
case REENTRANT_LOCK:
|
||||
return redissonClient.getLock(key);
|
||||
case FAIR_LOCK:
|
||||
return redissonClient.getFairLock(key);
|
||||
case READ_LOCK:
|
||||
return redissonClient.getReadWriteLock(key).readLock();
|
||||
case WRITE_LOCK:
|
||||
return redissonClient.getReadWriteLock(key).writeLock();
|
||||
default:
|
||||
throw new RuntimeException("RedissonLockAction-不支持的锁类型:" + redissonLockAction.lockType().name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.lingniu.framework.plugin.redisson.init.RedissonAutoConfiguration
|
||||
@@ -0,0 +1,121 @@
|
||||
# 【重要】redisson详细资料-redis搭建---参考官网
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 基于 Redisson 封装的 Redis 集群操作和分布式锁组件,专门用于处理 Redis 集群环境下的分布式锁场景
|
||||
2. 核心能力
|
||||
* 高性能键值存储:基于RocksDB的高效读写能力,支持快速的数据存取操作。
|
||||
* 集群自适应感知:客户端能够感知集群变化并自动刷新配置
|
||||
* Redisson 操作:基于 Redisson 提供 Redis 集群操作能力
|
||||
* 分布式锁支持:支持多种类型的分布式锁:
|
||||
■ 可重入锁(REENTRANT_LOCK)
|
||||
■ 公平锁(FAIR_LOCK)
|
||||
■ 读锁(READ_LOCK)
|
||||
■ 写锁(WRITE_LOCK)
|
||||
* 注解式锁操作:通过 @LockAction 注解简化分布式锁的使用
|
||||
* 灵活配置:支持锁等待时间、自动释放时间、时间单位等参数配置
|
||||
* 异常处理:支持锁获取失败时的异常抛出或返回 null 两种处理方式
|
||||
* 支持多集群操作redis,也可扩展队列消费
|
||||
3. 适用场景:该组件特别适用于需要在 Redis 集群环境下使用分布式锁的企业级应用,通过注解方式简化了分布式锁的使用,提供了多种锁类型以满足不同业务场景的需求
|
||||
* 分布式系统:需要在分布式环境下保证数据一致性的场景
|
||||
* 高并发业务:需要防止并发操作导致数据不一致的业务场景
|
||||
* 集群环境:基于 Redis 集群部署的应用系统
|
||||
* 资源竞争控制:需要对共享资源进行并发访问控制的场景
|
||||
* 定时任务:分布式环境下需要确保任务单实例执行的定时任务
|
||||
* 库存扣减:电商系统中需要防止超卖的库存扣减场景
|
||||
* 幂等性保证:需要保证接口幂等性的业务操作。
|
||||
|
||||
## 如何配置--更多参数参考:RedissonConfig/RedissonProperties
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
lingniu:
|
||||
redisson:
|
||||
remote:
|
||||
r1:
|
||||
# 应用id需在cachecloud申请
|
||||
redisAddresses: xxxx:6387
|
||||
# 客户端名称,默认为应用名 + IP
|
||||
clientName: ""
|
||||
# 单节点 Redis 默认数据库,默认为 0
|
||||
database: 0
|
||||
# 连接池最小空闲连接数,默认为 12
|
||||
connectionMinimumIdleSize: 12
|
||||
# 连接池最大连接数,默认为 32
|
||||
connectionPoolSize: 32
|
||||
# 空闲连接超时时间(毫秒),默认为 10000
|
||||
idleConnectionTimeout: 10000
|
||||
# 连接超时时间(毫秒),默认为 2000
|
||||
connectTimeout: 2000
|
||||
# 操作超时时间(毫秒),默认为 2000
|
||||
timeout: 2000
|
||||
# 操作重试次数,默认为 4
|
||||
retryAttempts: 4
|
||||
# 集群模式下是否检查 Slots 覆盖,默认为 true
|
||||
checkSlotsCoverage: true
|
||||
# 读取模式,默认为 "SLAVE"
|
||||
# 可选值:
|
||||
# - SLAVE: 从节点读取
|
||||
# - MASTER: 主节点读取
|
||||
# - MASTER_SLAVE: 主从节点读取
|
||||
readMode: "SLAVE"
|
||||
# 存储类型,默认为 "CLUSTER"
|
||||
# 可选值:
|
||||
# - CLUSTER: 集群模式
|
||||
# - SINGLE: 单节点模式
|
||||
# - SENTINEL: 哨兵模式
|
||||
# - REPLICATED: 复制模式
|
||||
storageType: "CLUSTER"
|
||||
# 实例名称
|
||||
r2:
|
||||
# 应用id需在cachecloud申请
|
||||
redisAddresses: xxx:6387
|
||||
# 单节点 Redis 默认数据库,默认为 0
|
||||
database: 0
|
||||
|
||||
```
|
||||
|
||||
## 如何使用--正常操作redis
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private RedissonClient test;
|
||||
// 或者
|
||||
RedissonFactory.getRedissonClient("test").api
|
||||
```
|
||||
|
||||
## 如何使用--分布式锁
|
||||
|
||||
```java
|
||||
/**
|
||||
* 使用 #orderId 参数作为锁的key
|
||||
*/
|
||||
@PostMapping("/process/{orderId}")
|
||||
@LockAction(
|
||||
redissonName = "r1",
|
||||
key = "#orderId",
|
||||
lockType = LockType.REENTRANT_LOCK,
|
||||
waitTime = 3000L, leaseTime = 30000L, unit = TimeUnit.MILLISECONDS,
|
||||
throwEx = true
|
||||
)
|
||||
public ResponseEntity<String> processOrder(@PathVariable String orderId) {
|
||||
// 业务逻辑
|
||||
return ResponseEntity.ok("Order processed: " + orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用请求参数
|
||||
*/
|
||||
@GetMapping("/query")
|
||||
@LockAction(
|
||||
redissonName = "defaultRedisson",
|
||||
key = "'query:' + #userId + ':' + #status",
|
||||
throwEx = true
|
||||
)
|
||||
public ResponseEntity<List<Order>> queryOrders(
|
||||
@RequestParam String userId,
|
||||
@RequestParam String status) {
|
||||
// 业务逻辑
|
||||
return ResponseEntity.ok(new ArrayList<>());
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user