在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 实现分布式锁相关推荐

  1. labview报表生成工具包_后台开发java 常见工具包 netty、mq 、分布式锁等,干货

    why搞这么一个玩意 用过vue-cli的小伙伴会很轻松的搭建出一个包含vue骨架的前端开发组件,然后install所需要的模块,后台常见springcloud框架也是如此,但是少了所需要的好多模块, ...

  2. 22-09-20 西安 谷粒商城(04)Redisson做分布式锁、布隆过滤器、AOP赋能、自定义注解做缓存管理、秒杀测试

    Redisson 1.Redisson做分布式锁  分布式锁主流的实现方案: 基于数据库实现分布式锁 基于缓存(Redis),性能最高 基于Zookeeper,可靠性最高 Redisson是一个在Re ...

  3. Day140-142.尚品汇:AOP+Redis缓存+redssion分布式锁、CompletableFuture异步编排、首页三级分类展示、Nginx静态代理

    目录 Day08 一.获取商品详情 加入缓存 二.全局缓存:分布式锁与aop 整合 三.布隆过滤器 四.CompletableFuture 异步编排 jdk1.8 Day09 1. 将item 改为多 ...

  4. nx set 怎么实现的原子性_基于Redis的分布式锁实现

    前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...

  5. 简单介绍redis分布式锁解决表单重复提交的问题

    在系统中,有些接口如果重复提交,可能会造成脏数据或者其他的严重的问题,所以我们一般会对与数据库有交互的接口进行重复处理.本文就详细的介绍一下redis分布式锁解决表单重复提交,感兴趣的可以了解一下 假 ...

  6. redis完整笔记总结-数据类型-事务与锁-集群-分布式锁-常见问题(缓存穿透、击穿、雪崩)

    1. 数据类型 五大基本类型 String hash -> 类似map list set -> zset -> 基于set的有序集合 新增 bitmaps:其实就是string,主要 ...

  7. 基于redis分布式锁实现“秒杀”

    作者丨lsfire https://blog.csdn.net/u010359884/article/details/50310387 最近在项目中遇到了类似"秒杀"的业务场景,在 ...

  8. java分布式锁终极解决方案之 redisson

    目前有很多项目还在使用redis的 setNx 充当分布式锁,然而这个锁是有问题的,redisson是java支持redis的redlock的唯一实现,.目前支持集群模式,云托管模式,单Redis节点 ...

  9. Java程序猿笔记——基于redis分布式锁实现“秒杀”

    最近在项目中遇到了类似"秒杀"的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓"秒杀"的基本思路. 业务场景 所谓秒杀,从业务角度看,是短时 ...

最新文章

  1. windows安装RabbitMQ注意事项
  2. regedit或child_process添加注册表
  3. WordPress 5.1:从CSRF到RCE
  4. VC用ADO访问数据库全攻略  作者 相生昌
  5. Oracle 隐含参数的查询
  6. 浏览器字体大小怎么调_音乐文件如何升降调
  7. 移动pc一套代码_【腾讯】如何避免 CDN 为 PC 端缓存移动端页面
  8. Java基础练习题---this
  9. 用C语言求解一元高次方程论文,一元高次方程C语言实现(最高五次
  10. 大学计算机知识竞赛幽默主持词,知识竞赛幽默主持词.docx
  11. dw文件html代码预览效果,VSCode设置网页代码实时预览
  12. MATLAB编写m函数理解 y=f(g(x))*h(x)
  13. thrift php,Thrift-简单实用
  14. 纸壳CMS 3.0升级.Net Core 2.1性能大提升
  15. 直观理解Neural Tangent Kernel
  16. leafler如何清除地图上的图标点
  17. 【2019-游记】中山纪念中学暑期游Day2
  18. 校园疫情管理系统-校园疫情防控系统-校园疫情管理
  19. streaming文件数过多
  20. 【前端】-【less】-学习笔记

热门文章

  1. int 转 char 、string c++
  2. Linux C 判断文件是否存在,是否可读,可写,可执行
  3. 数睿数据深度 | 商业智能红海,下一代BI还能激起多大的浪花
  4. 算法分析与设计exp3 PrimKruskal C语言代码
  5. linux控制台超时自动注销
  6. (附源码)基于web的校园论坛的设计与实现-计算机毕设92291
  7. H3CSE园区网综合实验
  8. Python shutil copy(),copyfile() 和 copytree()函数
  9. 【VA 最终用户许可协议】(英)
  10. 朋友圈将新增访客记录?微信相关人士:假的 图是P的