This does not introduce springboot integration and redis configuration integration, only redisson distributed locks
Mainly rely on
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--分布式锁redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.0</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
redission configuration file
application-single-dev.yml
Stand-alone mode
singleServerConfig:
idleConnectionTimeout: 10000 #连接空闲超时(毫秒),默认10000
connectTimeout: 10000 #连接空闲超时(毫秒),默认10000
timeout: 3000 #命令等待超时(毫秒),默认3000
retryAttempts: 3 #命令失败重试次数
retryInterval: 1500 #命令重试发送时间间隔(毫秒),默认1500
password: null
subscriptionsPerConnection: 5 #单个连接最大订阅数量,默认5
clientName: null #客户端名称
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1 #发布和订阅连接的最小空闲连接数,默认1
subscriptionConnectionPoolSize: 50 #发布和订阅连接池大小,默认50
connectionMinimumIdleSize: 24 #最小空闲连接数,默认32
connectionPoolSize: 64 #连接池大小,默认64
database: 0
dnsMonitoringInterval: 5000 #DNS监测时间间隔(毫秒),默认5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.JsonJacksonCodec> {}
#"transportMode": "NIO"
Cluster mode:
clusterServersConfig:
idleConnectionTimeout: 10000
pingTimeout: 1000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
reconnectionTimeout: 3000
failedAttempts: 3
password: null
subscriptionsPerConnection: 5
clientName: null
loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
slaveSubscriptionConnectionMinimumIdleSize: 1
slaveSubscriptionConnectionPoolSize: 50
slaveConnectionMinimumIdleSize: 32
slaveConnectionPoolSize: 64
masterConnectionMinimumIdleSize: 32
masterConnectionPoolSize: 64
readMode: "SLAVE"
nodeAddresses:
- "redis://127.0.0.1:7004"
- "redis://127.0.0.1:7001"
- "redis://127.0.0.1:7000"
scanInterval: 1000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
#"transportMode":"NIO"
Inject the configuration file
The configuration file read here is read according to the environment
RedissionConfig
package com.dzhjj.dzhjjapi.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.redisson.config.Config;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
/**
* @Auther: heng
* @Date: 2020/12/3 13:56
* @Description: redission分布式锁
* @Version 1.0.0
*/
@Order(2)
@Slf4j
@Configuration
public class RedissionConfig {
//@Autowired
// private RedisProperties redisProperties;
@Autowired
private Environment env;
/**
* 单机模式
* @return
*//*
@Bean
public RedissonClient redissonClient() {
RedissonClient redissonClient;
Config config = new Config();
String url = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(url)
.setTimeout(3000)
.setDatabase(redisProperties.getDatabase())
.setConnectionPoolSize(64)
.setConnectionMinimumIdleSize(50);
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
try {
redissonClient = Redisson.create(config);
return redissonClient;
} catch (Exception e) {
log.error("RedissonClient init redis url:[{}], Exception:", url, e);
return null;
}
}*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = Config.fromYAML(new ClassPathResource("application-single-"+env.getActiveProfiles()[0]+".yml").getInputStream());
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
Test distributed lock code
@Autowired
private RedissonClient redissonClient;
public Object testRedisLoak(){
Boolean result=false;
final String lockKey= "RedissonLock";
RLock lock=redissonClient.getLock(lockKey);
try {
//TODO:第一个参数30s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为30s
//TODO:第二个参数10s=表示上锁之后,10s内操作完毕将自动释放锁
Boolean cacheRes=lock.tryLock(10,10, TimeUnit.SECONDS);
return cacheRes;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//TODO:释放锁
lock.unlock();
}
return result;
}
Start to integrate annotation mode
Definition annotation: RsionLock
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
/**
* Description:
*
* <p>
* 分布式锁的注解
* </p>
* 在同一个注解中成对使用即可,比如示例代码中,value和path就是互为别名。
* 但是要注意一点,@AliasFor标签有一些使用限制,但是这应该能想到的,比如要求互为别名的属性属性值类型,默认值,都是相同的,互为别名的注解必须成对出现,比如value属性添加了@AliasFor(“path”),
* 那么path属性就必须添加@AliasFor(“value”),另外还有一点,互为别名的属性必须定义默认值。
*
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RsionLock {
/**
* 支持spring El表达式
* 锁的资源,key。
*/
//@AliasFor("value")
String key() default "";
/**
* 支持spring El表达式
* 锁的资源,value。
* 如果不为空这回拼接key
*/
//@AliasFor("key")
String value() default "";
/**
* 持锁时间,单位:秒
* TODO:10s=表示上锁之后,10s内操作完毕将自动释放锁
*/
long lockTime() default 10;
/**
* 当获取失败时候动作
*/
// LockFailAction action() default LockFailAction.CONTINUE;
/*public enum LockFailAction{
*//** 放弃 *//*
GIVEUP,
*//** 继续 *//*
CONTINUE;
}*/
/**
* 等待时间,单位:秒
* TODO:10s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为10s
* @return
*/
int waitTime() default 10;
/**
* 是否自动延期机制
* 默认不自动延期
* @return
*/
boolean isWatchDog() default false;
/**
* 延期时间 单位:秒
* @return
*/
int watchDogTime() default 2;
}
Define the annotation aspect LockAspectConfiguration
import com.dzhjj.dzhjjapi.annotations.RsionLock;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
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;
import java.util.concurrent.TimeUnit;
/**
* @Auther: heng
* @Date: 2020/12/3 17:50
* @Description: LockAspectConfiguration
* @Version 1.0.0
*/
@Slf4j
@Aspect
@Configuration
public class LockAspectConfiguration {
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Autowired
private RedissonClient redissonClient;
/**
* 定义切入点
*/
@Pointcut("@annotation(com.dzhjj.dzhjjapi.annotations.RsionLock)")
private void lockPoint() {
}
/**
* 环绕通知
*
* @param pjp pjp
* @return 方法返回结果
* @throws Throwable throwable
*/
@Around("lockPoint()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
RsionLock lockAction = method.getAnnotation(RsionLock.class);
String logKey = getLogKey(lockAction, pjp, method);
RLock lock=redissonClient.getLock(logKey);
//TODO:第一个参数30s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为30s
//TODO:第二个参数10s=表示上锁之后,10s内操作完毕将自动释放锁
Boolean cacheRes=lock.tryLock(lockAction.waitTime(),lockAction.lockTime(), TimeUnit.SECONDS);
if (!cacheRes) {
log.debug("get lock failed : " + logKey);
return null;
}
//得到锁,执行方法,释放锁
log.debug("get lock success : " + logKey);
try {
return pjp.proceed();
} catch (Exception e) {
log.error("execute locked method occured an exception", e);
} finally {
lock.unlock();
log.debug("release lock : " + logKey + (cacheRes ? " success" : " failed"));
}
return null;
}
/**
* 获得分布式缓存的key
*
* @param lockAction 注解对象
* @param pjp pjp
* @param method method
* @return String
*/
private String getLogKey(RsionLock lockAction, ProceedingJoinPoint pjp, Method method) {
String key = lockAction.key();
String value = lockAction.value();
Object[] args = pjp.getArgs();
return parse(key, method, args) + "_" + parse(value, method, args);
}
/**
* 解析spring EL表达式
*
* @param key key
* @param method method
* @param args args
* @return parse result
*/
private String parse(String key, Method method, Object[] args) {
String[] params = discoverer.getParameterNames(method);
if (null == params || params.length == 0 || !key.contains("#")) {
return key;
}
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < params.length; i++) {
if (null != args[i]){
context.setVariable(params[i], args[i]);
}else {
context.setVariable(params[i],"");
}
}
return parser.parseExpression(key).getValue(context, String.class);
}
}
The test code should be tested at the service layer. I will test it at the controller layer.
@RsionLock(key = "TestNewController_readlock", value = "#key",lockTime = 6,waitTime = 6)
@RequestMapping("/readlock")
public Object readlock(String key){
return key;
}
Visit url
http://localhost:8010/dzhjjapi/testnew/readlock?key=test
ps: After accessing the test lock, you can increase the time a bit, comment out the release lock, and then look at the redis lock status and the URL access lock status. The main one is the time to wait for the lock and the other is the time to release the lock.
result