本文共 12859 字,大约阅读时间需要 42 分钟。
关键字: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@Componentpublic 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@Documentedpublic @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){ RedisSerializerredisSerializer = 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); }}
转载地址:http://vgwni.baihongyu.com/