AOP 实现分布式锁
在web项目开发过程中,经常会遇到分布式资源控制的场景,通过加锁从而保证资源访问的互斥性。本文主要介绍在没有redis情况下通过mysql进行分布式锁的实现。
场景:
线程A与线程B执行前需要判断资源R的状态,当R的状态为1时,则可以执行,当R的状态为0时,则不容许执行,且同一时刻只容许一个线程执行。线程执行时资源R状态置0,线程执行结束后R状态重新置1.
上述场景中,若不对资源R进行互斥访问,则可能出现A、B线程同时访问资源R时且发现资源状态为1,从而都启动执行无法满足系统要求。
当资源R状态的获取和资源状态R的置位需要操作mysql进行实现时,我们则需要将查找和更新操作作为原子性操作,这种情况下,我们会考虑到事务,事务有如下几种隔离级别:
public enum Isolation { DEFAULT(-1),READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8);
}
而通常在web项目配置过程中,事务的隔离级别为READ_COMMITTED(2),可以避免脏读,依旧无法避免不可重复读。因此不可避免的需要将查找和更新操作进行加锁,同一时刻只允许一个线程访问操作。
AOP 实现
1.定义DB锁接口
public interface DBLock {/*** 加锁** @param timeout 超时时间* @param expireTime 过期时间* @param timeUnit 时间单位* @return 是否加锁成功*/default boolean lock(long timeout, long expireTime, TimeUnit timeUnit) {throw new ServerException("unimplemented function");}/*** 释放锁*/default void unlock() {throw new ServerException("unimplemented function");}
}
2.可重复锁实现
@Slf4j
public class DBReentrantLock implements DBLock {/*** 锁资源*/private LockService lockService;/*** 锁路径*/private String lockPath;/*** 可重入数量*/@Getterprivate int count = 0;/*** 过期时间*/@Getterprivate long expireTime = 0;DBReentrantLock(LockService lockService, String lockPath) {this.lockService = lockService;this.lockPath = lockPath;this.count = 0;}/*** 获取锁** @param timeout 超时时间* @param expireTime 过期时间* @param timeUnit 时间单位* @return 是否获取到锁*/@Overridepublic boolean lock(long timeout, long expireTime, TimeUnit timeUnit) {try {// 已获得锁if (this.count > 0) {this.count++;return true;}if (timeout < 0) {return false;}lockService.clearExpired();if (lockService.lock(lockPath, timeUnit.toMillis(expireTime))) {this.count = 1;this.expireTime = System.currentTimeMillis() + timeUnit.toMillis(expireTime);return true;}// 获取锁是不等待if (timeout == 0) {return false;}long threshold = timeUnit.toMillis(timeout) + System.currentTimeMillis();while (System.currentTimeMillis() < threshold) {// 清除过期锁lockService.clearExpired();if (lockService.lock(lockPath, timeUnit.toMillis(timeout))) {this.count = 1;this.expireTime = System.currentTimeMillis() + timeUnit.toMillis(expireTime);return true;}try {Thread.sleep(200);} catch (Exception ignored) {};}return false;} catch (Exception e) {log.error("can not acquire {} lock, unknown error happened", lockPath, e);throw new ServerException("this should not happened", e);}}/*** 释放锁*/@Overridepublic void unlock() {try {this.count--;if (this.count == 0) {this.expireTime = 0;this.lockService.release(lockPath);}log.debug("release {} lock successfully", lockPath);} catch (Exception e) {log.error("can not release {} lock, unknown error happened", lockPath, e);}}}
3.锁上下文实现
@Slf4j
@Component
public class LockContext {@Autowiredprivate LockService lockService;/*** 锁池*/private ThreadLocal<Map<String, DBReentrantLock>> lockPool = ThreadLocal.withInitial(Maps::newHashMap);/*** 获取可重入锁** @param key 锁key* @return DB锁*/public DBLock getReentrantLock(String key) {String lockPath = this.getLockPath(LockType.REENTRANT_LOCK, key);try {Map<String, DBReentrantLock> lockPool = this.lockPool.get();DBReentrantLock lock = lockPool.get(lockPath);// 锁已过期,则清理if (null != lock) {if (lock.getExpireTime() < System.currentTimeMillis()) {lockPool.remove(lockPath);} else {return lock;}}lock = new DBReentrantLock(lockService, lockPath);lockPool.put(lockPath, lock);return lock;} catch (Exception e) {log.error("fail to get lock, key is {}", key, e);throw new ServerException("this should not happen", e);}}/*** 获取锁路径** @param lockType 锁类型* @param key 锁key* @return 锁路径*/private String getLockPath(LockType lockType, String key) {return new StringBuilder().append("/").append(lockType.toString()).append("/").append(key).toString();}
}
4.锁行为注解
/*** 锁行为** @author ginger* @create 2019-11-06 10:29 下午*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LockAction {/*** 锁资源,支持Spring EL表达式*/String key() default "'default'";/*** 锁类型,默认可重入锁*/LockType lockType() default LockType.REENTRANT_LOCK;/*** 获取锁的等待时间,默认10秒,单位对应unit()*/long waitTime() default 10000L;/*** 锁过期时间,默认1分钟,单位对应unit()*/long expireTime() default 60000L;/*** 时间单位,默认毫秒*/TimeUnit unit() default TimeUnit.MILLISECONDS;}
5.锁切面实现
@Slf4j
@Aspect
@Order(100)
@Component
public class LockAspect {@Autowiredprivate LockContext lockContext;/*** Spring EL表达式解析器*/final private ExpressionParser parser = new SpelExpressionParser();final private LocalVariableTableParameterNameDiscoverer discoverer =new LocalVariableTableParameterNameDiscoverer();@Around("@annotation(com.netease.hz.bdms.ed.service.lock.LockAction)")public Object around(ProceedingJoinPoint pjp) throws Throwable {Method method = ((MethodSignature) pjp.getSignature()).getMethod();LockAction lockAction = method.getAnnotation(LockAction.class);String spel = lockAction.key();Object[] args = pjp.getArgs();String key = parse(spel, method, args);DBLock lock = lockContext.getReentrantLock(key);if (lockAction.lockType() != LockType.REENTRANT_LOCK) {throw new LockFailureException("unsupported lock type, key is " + key);}if (!lock.lock(lockAction.waitTime(), lockAction.expireTime(), lockAction.unit())) {log.debug("acquire lock unsuccessfully, key is {}", key);throw new LockFailureException("acquire lock unsuccessfully, key is " + key);}log.debug("acquire lock successfully, key is {}", key);try {return pjp.proceed();} finally {lock.unlock();}}private String parse(String spel, Method method, Object[] args) {String[] params = discoverer.getParameterNames(method);EvaluationContext context = new StandardEvaluationContext();if (null != params) {for (int i = 0; i < params.length; i++) {context.setVariable(params[i], args[i]);}}return parser.parseExpression(spel).getValue(context, String.class);}}
AOP 实现分布式锁相关推荐
- labview报表生成工具包_后台开发java 常见工具包 netty、mq 、分布式锁等,干货
why搞这么一个玩意 用过vue-cli的小伙伴会很轻松的搭建出一个包含vue骨架的前端开发组件,然后install所需要的模块,后台常见springcloud框架也是如此,但是少了所需要的好多模块, ...
- 22-09-20 西安 谷粒商城(04)Redisson做分布式锁、布隆过滤器、AOP赋能、自定义注解做缓存管理、秒杀测试
Redisson 1.Redisson做分布式锁 分布式锁主流的实现方案: 基于数据库实现分布式锁 基于缓存(Redis),性能最高 基于Zookeeper,可靠性最高 Redisson是一个在Re ...
- Day140-142.尚品汇:AOP+Redis缓存+redssion分布式锁、CompletableFuture异步编排、首页三级分类展示、Nginx静态代理
目录 Day08 一.获取商品详情 加入缓存 二.全局缓存:分布式锁与aop 整合 三.布隆过滤器 四.CompletableFuture 异步编排 jdk1.8 Day09 1. 将item 改为多 ...
- nx set 怎么实现的原子性_基于Redis的分布式锁实现
前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...
- 简单介绍redis分布式锁解决表单重复提交的问题
在系统中,有些接口如果重复提交,可能会造成脏数据或者其他的严重的问题,所以我们一般会对与数据库有交互的接口进行重复处理.本文就详细的介绍一下redis分布式锁解决表单重复提交,感兴趣的可以了解一下 假 ...
- redis完整笔记总结-数据类型-事务与锁-集群-分布式锁-常见问题(缓存穿透、击穿、雪崩)
1. 数据类型 五大基本类型 String hash -> 类似map list set -> zset -> 基于set的有序集合 新增 bitmaps:其实就是string,主要 ...
- 基于redis分布式锁实现“秒杀”
作者丨lsfire https://blog.csdn.net/u010359884/article/details/50310387 最近在项目中遇到了类似"秒杀"的业务场景,在 ...
- java分布式锁终极解决方案之 redisson
目前有很多项目还在使用redis的 setNx 充当分布式锁,然而这个锁是有问题的,redisson是java支持redis的redlock的唯一实现,.目前支持集群模式,云托管模式,单Redis节点 ...
- Java程序猿笔记——基于redis分布式锁实现“秒杀”
最近在项目中遇到了类似"秒杀"的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓"秒杀"的基本思路. 业务场景 所谓秒杀,从业务角度看,是短时 ...
最新文章
- windows安装RabbitMQ注意事项
- regedit或child_process添加注册表
- WordPress 5.1:从CSRF到RCE
- VC用ADO访问数据库全攻略 作者 相生昌
- Oracle 隐含参数的查询
- 浏览器字体大小怎么调_音乐文件如何升降调
- 移动pc一套代码_【腾讯】如何避免 CDN 为 PC 端缓存移动端页面
- Java基础练习题---this
- 用C语言求解一元高次方程论文,一元高次方程C语言实现(最高五次
- 大学计算机知识竞赛幽默主持词,知识竞赛幽默主持词.docx
- dw文件html代码预览效果,VSCode设置网页代码实时预览
- MATLAB编写m函数理解 y=f(g(x))*h(x)
- thrift php,Thrift-简单实用
- 纸壳CMS 3.0升级.Net Core 2.1性能大提升
- 直观理解Neural Tangent Kernel
- leafler如何清除地图上的图标点
- 【2019-游记】中山纪念中学暑期游Day2
- 校园疫情管理系统-校园疫情防控系统-校园疫情管理
- streaming文件数过多
- 【前端】-【less】-学习笔记
热门文章
- int 转 char 、string c++
- Linux C 判断文件是否存在,是否可读,可写,可执行
- 数睿数据深度 | 商业智能红海,下一代BI还能激起多大的浪花
- 算法分析与设计exp3 PrimKruskal C语言代码
- linux控制台超时自动注销
- (附源码)基于web的校园论坛的设计与实现-计算机毕设92291
- H3CSE园区网综合实验
- Python shutil copy(),copyfile() 和 copytree()函数
- 【VA 最终用户许可协议】(英)
- 朋友圈将新增访客记录?微信相关人士:假的 图是P的