关键字:RedisLock 锁 Spring Boot MVC 分布式锁 基于注解
先上效果,rediskey 为hello+第一个从参数的userid 最大等待时间为5000毫秒,占用锁之后的自动释放时间为5秒,如果5秒内方法体执行完成,AOP代码会手动释放锁,但是对于写业务的人来说是透明的。
@Override
@AddLock(key = "'hello'+#p0.userId", maxWait = 5000, timeout =5)
public void sayHello(FrontUserVO user){
try {
log.info("拿到了锁,准备睡眠");
Thread.sleep(6);
log.info("你好" + user.getUserId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
AOP代码,这里是核心,主要是先拼接rediskey,然后是是否获取到锁和释放锁的判断,也没啥。
/**
* redis分布式锁实现
* @ProjectName: framework_v2_idea2
* @Package: com.ylm.lock.aop
* @ClassName: AddLockAspect
* @Author: JackWang
* @CreateDate: 2019/1/8 0008 14:19
* @UpdateUser: JackWang
* @UpdateDate: 2019/1/8 0008 14:19
* @Version: 1.0
*/
@Aspect
@Component
public class AddLockAspect {
/**
* 日志记录
*/
private static final Logger LOG = Logger.getLogger(AddLockAspect.class);
@Resource
private RedisLockService redisLockService;
@Pointcut("@annotation(com.ylm.lock.annotations.AddLock)")
public void addLockAnnotationPointcut() {
}
@Around(value = "addLockAnnotationPointcut()")
public Object addKeyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
//定义返回值
Object proceed = null;
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
Object target = joinPoint.getTarget();
Object[] arguments = joinPoint.getArgs();
AddLock annotation = AnnotationUtils.findAnnotation(targetMethod, AddLock.class);
String spel=null;
if(annotation != null){
spel = annotation.key();
}
//前置方法 开始
String redisKey = "fhslock:" + SpelUtil.parse(target,spel, targetMethod, arguments);
LOG.infoMsg("添加redisKey={}",redisKey);
boolean isLock = false;
long excuteTime = 0;
try {
//true代表成功了,false代表加锁失败
isLock = redisLockService.addRedisLock(redisKey,annotation.maxWait(),annotation.timeout());
// 目标方法执行
if(isLock){
excuteTime = new Date().getTime();
proceed = joinPoint.proceed();
}
} catch (Exception exception) {
//如果我自己加锁成功,出了异常则将锁释放掉
if(isLock) {
redisLockService.delRedisLock(redisKey);
}
throw exception;
} finally {
//加锁失败抛出异常
if (!isLock) {
throw new BusinessException(redisKey + "add LockTimeout,key:" + redisKey);
}
else
{
//如果当前时间 减去 service方法开始执行时间大于timeout的话,可能redis已经自动释放锁了,所以这边不在主动释放锁
if((new Date().getTime() - excuteTime) < annotation.timeout()*1000)
{
redisLockService.delRedisLock(redisKey);
}
return proceed;
}
}
}
}
注解代码
package com.ylm.lock.annotations;
import java.lang.annotation.*;
/**
* by jackwang
* redis分布式锁注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AddLock {
/**
* 锁的key
* @return 锁的key
*/
String key();
/**
* 最大等待时间 毫秒
* @return 最大等待时间毫秒
*/
long maxWait();
/**
* 锁最大占用时间秒
* @return 锁最大占用时间秒
*/
int timeout();
}
服务类
/**
*
* 内存数据库Redis的辅助类,负责对内存数据库的所有操作
*
* @author wangpengfei
* @version [版本号, 2016年7月22日]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
@Service("redisLockServiceImpl")
@DataSource("default")
public class RedisLockServiceImpl implements RedisLockService
{
/**
* 日志记录
*/
private static final Logger LOG = Logger.getLogger(RedisLockServiceImpl.class);
@Autowired
private RedisTemplate redisTemplate;
/**
* redisLock工具类
*/
private RedisLock redisLock;
@Override
public boolean addRedisLock(String lockMark)
{
try
{
// 加锁失败或者异常后,返回false
redisLock = new RedisLock(lockMark, redisTemplate);
LOG.info("加锁:" + lockMark);
return redisLock.lock(5000, 1 * 60);
}
catch (Exception e)
{
LOG.error("加锁失败,key:" + lockMark + ",异常:", e);
// 异常之后发邮件通知管理员
return false;
}
}
@Override
public boolean checkLockExit(String lockMark, long timeout)
{
try
{
// 加锁失败或者异常后,返回false
redisLock = new RedisLock(lockMark, redisTemplate);
LOG.warn("判断锁是否存在:" + lockMark);
return redisLock.checkLockExit(timeout);
}
catch (Exception e)
{
LOG.error("加锁失败,key:" + lockMark + ",异常:", e);
// 异常之后发邮件通知管理员
return false;
}
}
@Override
public boolean addRedisLock(String lockMark, long timeout, int expire)
{
try
{
// 加锁失败或者异常后,返回false
redisLock = new RedisLock(lockMark, redisTemplate);
LOG.info("addLock:" + lockMark);
return redisLock.lock(timeout, expire);
}
catch (Exception e)
{
LOG.error("add Lock Error,key:" + lockMark + ",异常:", e);
// 异常之后发邮件通知管理员
return false;
}
}
@Override
public void delRedisLock(String lockMark)
{
try
{
redisLock = new RedisLock(lockMark, redisTemplate);
LOG.warn("解锁:" + lockMark);
redisLock.unlock();
}
catch (Exception e)
{
e.printStackTrace();
LOG.error("删除锁失败,key:" + lockMark + ",异常:", e);
// 异常之后发邮件通知管理员
}
}
}
redislock对象
public class RedisLock
{
private static Logger LOG = Logger.getLogger(RedisLock.class);
/** 加锁标志 */
public static final String LOCKED = "TRUE";
/** 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒 */
public static final long MILLI_NANO_CONVERSION = 1000 * 1000L;
/** 默认超时时间(毫秒) */
public static final long DEFAULT_TIME_OUT = 5000;
public static final Random RANDOM = new Random();
/** 锁的超时时间(秒),过期删除 */
public static final int EXPIRE = 5 * 60;
private RedisTemplate redisTemplate;
private String key;
// 锁状态标志
private boolean locked = false;
public RedisLock(String key, RedisTemplate redisTemplate)
{
Date date = new Date();
try
{ this.redisTemplate = redisTemplate;
}
catch (Exception e)
{
LOG.error("获取redis连接错误", e);
}
this.key = key + "_lock";
}
/**
* 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
*
* @param timeout 超时时间
* @return 成功或失败标志
*/
public boolean lock(long timeout)
{
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
try
{
while ((System.nanoTime() - nano) < timeout)
{
if (setNx(this.key,LOCKED))
{
redisTemplate.expire(this.key, EXPIRE, TimeUnit.SECONDS);
this.locked = true;
// LOG.warn("the key is lock -- " + this.key + " exe param log :\n"
// + StackTraceElementUtils.getStackStr(3));
return this.locked;
}
// 短暂休眠,避免出现活锁
Thread.sleep(100, RANDOM.nextInt(500));
}
}
catch (Exception e)
{
LOG.error(this, e);
throw new RuntimeException("Locking error", e);
}
// LOG.warn(StackTraceElementUtils.getStackStr(3) + " \n lock error " + this.key);
return false;
}
public boolean checkLockExit(long timeout)
{
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
try
{
while ((System.nanoTime() - nano) < timeout)
{
RedisConnection connection = getConnection();
if (connection.exists(redisTemplate.getStringSerializer().serialize(key)))
{
closeRedisConnection(connection);
return true;
}
closeRedisConnection(connection);
// 短暂休眠,避免出现活锁
Thread.sleep(100, RANDOM.nextInt(500));
}
}
catch (Exception e)
{
LOG.error(this, e);
throw new RuntimeException("Locking error", e);
}
// LOG.warn(StackTraceElementUtils.getStackStr(3) + " \n lock error " + this.key);
return false;
}
/**
* 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
*
* @param timeout 超时时间(毫秒)
* @param expire 锁的超时时间(秒),过期删除
* @return 成功或失败标志
*/
public boolean lock(long timeout, int expire)
{
long nano = System.nanoTime();
timeout *= MILLI_NANO_CONVERSION;
try
{
while ((System.nanoTime() - nano) < timeout)
{
if (setNx(this.key, LOCKED))
{
LOG.debug("获取到lock" + this.key);
this.redisTemplate.expire(this.key, expire,TimeUnit.SECONDS);
this.locked = true;
return this.locked;
}
// 短暂休眠,避免出现活锁
Thread.sleep(10);
}
}
catch (Exception e)
{
LOG.error("加锁失败", e);
throw new RuntimeException("Locking error", e);
}
return false;
}
/**
* 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
*
* @return 成功或失败标志
*/
public boolean lock()
{
return lock(DEFAULT_TIME_OUT);
}
/**
* 解锁 无论是否加锁成功,都需要调用unlock 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
*/
public void unlock()
{
try
{
RedisConnection connection = getConnection();
connection.del(redisTemplate.getStringSerializer().serialize(this.key));
closeRedisConnection(connection);
}
catch (Exception e)
{
LOG.error("解锁错误:" + this.key, e);
}
}
/**
* 封装设置分布式锁api
* @param keyStr
* @param valueStr
* @return
*/
private Boolean setNx(String keyStr, String valueStr){
RedisSerializer<String> redisSerializer = this.redisTemplate.getStringSerializer();
byte[] key = redisSerializer.serialize(keyStr);
byte[] value = redisSerializer.serialize(valueStr);
RedisConnection connection = getConnection();
boolean result = connection.setNX(key,value);
closeRedisConnection(connection);
return result;
}
/**
* 归还 connection
* @param connection connection
*/
private void closeRedisConnection(RedisConnection connection){
if(connection!=null && !connection.isClosed())
{
connection.close();
}
}
/**
* 获取redis连接
* @return
*/
private RedisConnection getConnection(){
return redisTemplate.getConnectionFactory().getConnection();
}
表达式解析:
/**
*Spel 表达式解析器
* @ProjectName: framework_v2_idea2
* @Package: com.ylm.common.spring
* @ClassName: SpelUtil
* @Author: JackWang
* @CreateDate: 2019/1/8 0008 14:24
* @UpdateUser: JackWang
* @UpdateDate: 2019/1/8 0008 14:24
* @Version: 1.0
*/
public class SpelUtil {
public static String parse(String spel, Method method, Object[] args) {
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法参数放入SPEL上下文中
for (int i = 0; i < paraNameArr.length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(spel).getValue(context, String.class);
}
/**
* 支持 #p0 参数索引的表达式解析
* @param rootObject 根对象,method 所在的对象
* @param spel 表达式
* @param method ,目标方法
* @param args 方法入参
* @return 解析后的字符串
*/
public static String parse(Object rootObject,String spel, Method method, Object[] args) {
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject,method,args,u);
//把方法参数放入SPEL上下文中
for (int i = 0; i < paraNameArr.length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(spel).getValue(context, String.class);
}
}