本文实现一种分布式服务防重复提交的方案 也就是一线一个锁,在方法请求前,要先获取锁 如果锁存在则返回异常 。

下面简单介绍一下如何使用Redis实现分布式锁

  • CacheLock.java 为自定义注解接口,CacheLock方法注解用来指定分布式锁的key前缀和失效时间等信息
  • LockKeyGenerator.java为切面,用于拦截Heders中token参数,生成分布式锁的key
  • LockMethodInterceptor.java为切面,用于拦截@CacheLock方法,实现在执行方法之前要先获取锁逻辑
  • RedisLockHelper.java为分布式锁的实现

1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.service</groupId><artifactId>springboot-repeat-submit</artifactId><version>1.0-SNAPSHOT</version><!-- Spring Boot 启动父依赖 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.3.RELEASE</version></parent><dependencies><!-- Exclude Spring Boot's Default Logging --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency><!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.2</version><scope>provided</scope></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>21.0</version></dependency></dependencies></project>

2.自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheLock {/*** redis 锁key的前缀*/String prefix() default "";/*** redis key过期时间*/int expire() default 5;/*** 超时时间单位**/TimeUnit timeUnit() default TimeUnit.SECONDS;/*** Key分隔符* 比如:Key:1*/String delimiter() default ":";
}

3.分布式锁key 生成

public class LockKeyGenerator implements CacheKeyGenerator {@Overridepublic String getLockKey(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();CacheLock lockAnnotation = method.getAnnotation(CacheLock.class);StringBuilder builder = new StringBuilder();HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader(HttpHeaders.AUTHORIZATION);builder.append(lockAnnotation.delimiter()).append(token);return lockAnnotation.prefix() + builder.toString();}
}

如果想要使用请求参数中的属性生成分布式key
可以添加自定义注解 CacheParam.java 使用@CacheParam参数生产key
@CacheParam需要作用在请求参数上

@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam {/*** 字段名称** @return String*/String name() default "";
}
public class LockKeyGenerator implements CacheKeyGenerator {@Overridepublic String getLockKey(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();CacheLock lockAnnotation = method.getAnnotation(CacheLock.class);final Object[] args = pjp.getArgs();final Parameter[] parameters = method.getParameters();StringBuilder builder = new StringBuilder();//默认解析方法里面带CacheParam注解的属性,如果没有尝试着解析实体对象中的CacheParam注解属性for (int i = 0; i < parameters.length; i++) {final CacheParam annotation = parameters[i].getAnnotation(CacheParam.class);if (annotation == null) {continue;}builder.append(lockAnnotation.delimiter()).append(args[i]);}if (StringUtils.isEmpty(builder.toString())) {//CacheLock注解的方法参数没有CacheParam注解,则迭代解析参数实体中的CacheParam注解属性final Annotation[][] parameterAnnotations = method.getParameterAnnotations();for (int i = 0; i < parameterAnnotations.length; i++) {final Object object = args[i];final Field[] fields = object.getClass().getDeclaredFields();for (Field field : fields) {final CacheParam annotation = field.getAnnotation(CacheParam.class);if (annotation == null) {continue;}field.setAccessible(true);builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));}}}return lockAnnotation.prefix() + builder.toString();}
}

集成token
如果没有token 方法上添加@RequestParam @RequestBody注解 可在方法上添加@CacheParam(name=“对应的字段名”)

public class LockKeyGenerator implements CacheKeyGenerator {@Overridepublic String getLockKey(ProceedingJoinPoint pjp) {CacheLock lockAnnotation = null;StringBuilder builder = null;try {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();lockAnnotation = method.getAnnotation(CacheLock.class);PassLogin passLoginAnnotation = method.getAnnotation(PassLogin.class);builder = new StringBuilder();if(passLoginAnnotation == null){HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader(HttpHeaders.AUTHORIZATION);builder.append(lockAnnotation.delimiter()).append(token);} else {final Object[] args = pjp.getArgs();final Parameter[] parameters = method.getParameters();//默认解析方法里面带CacheParam注解的属性,如果没有尝试着解析实体对象中的CacheParam注解属性for (int i = 0; i < parameters.length; i++) {final CacheParam annotation = parameters[i].getAnnotation(CacheParam.class);if (annotation == null) {continue;} else {final Object object = args[i];if(object instanceof String){builder.append(lockAnnotation.delimiter()).append(args[i]);} else {final Field[] fields = object.getClass().getDeclaredFields();if(fields != null){for (Field field : fields) {if(field.getName().equals(annotation.name())){field.setAccessible(true);builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));break;}}}}}}if (StringUtils.isEmpty(builder.toString())) {//CacheLock注解的方法参数没有CacheParam注解,则迭代解析参数实体中的CacheParam注解属性final Annotation[][] parameterAnnotations = method.getParameterAnnotations();for (int i = 0; i < parameterAnnotations.length; i++) {final Object object = args[i];final Field[] fields = object.getClass().getDeclaredFields();for (Field field : fields) {final CacheParam annotation = field.getAnnotation(CacheParam.class);if (annotation == null) {continue;}field.setAccessible(true);builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));}}}}} catch (Exception e) {e.printStackTrace();}return lockAnnotation.prefix() + builder.toString();}

4.分布式锁实现

@Configuration
public class RedisConfig {@Beanpublic RedisConnectionFactory redisConnectionFactory() {RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);return new JedisConnectionFactory(redisStandaloneConfiguration);}@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}}
@Configuration
@RequiredArgsConstructor
public class RedisLockHelper {private static final String DELIMITER = "|";private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(10);private final StringRedisTemplate stringRedisTemplate;/*** 获取锁* @param lockKey lockKey* @param uuid    UUID* @param timeout 超时时间* @param unit    过期单位* @return true or false*/public boolean lock(String lockKey, final String uuid, long timeout, final TimeUnit unit) {final long milliseconds = Expiration.from(timeout, unit).getExpirationTimeInMilliseconds();boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);if (success) {/*设置过期时间,防止系统崩溃而导致锁迟迟不释放形成死锁*/stringRedisTemplate.expire(lockKey, timeout, unit);} else {String oldVal = stringRedisTemplate.opsForValue().get(lockKey);final String[] oldValues = oldVal.split(Pattern.quote(DELIMITER));/*缓存已经到过期时间,但是还没释放,避免ddl失效造成死锁*/if (Long.parseLong(oldValues[0]) + unit.toSeconds(1) <= System.currentTimeMillis()) {stringRedisTemplate.opsForValue().set(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);stringRedisTemplate.expire(lockKey, timeout, unit);return true;}}return success;}public void unlock(String lockKey, String value) {unlock(lockKey, value, 0, TimeUnit.MILLISECONDS);}/*** 延迟unlock** @param lockKey   key* @param uuid* @param delayTime 延迟时间* @param unit      时间单位*/private void unlock(final String lockKey, final String uuid, long delayTime, TimeUnit unit) {if (StringUtils.isEmpty(lockKey)) {return;}if (delayTime <= 0) {doUnlock(lockKey, uuid);} else {/*定时任务延迟unlock*/EXECUTOR_SERVICE.schedule(() -> doUnlock(lockKey, uuid), delayTime, unit);}}/*** @param lockKey key* @param uuid*/private void doUnlock(final String lockKey, final String uuid) {String val = stringRedisTemplate.opsForValue().get(lockKey);final String[] values = val.split(Pattern.quote(DELIMITER));if (values.length <= 0) {return;}if (uuid.equals(values[1])) {stringRedisTemplate.delete(lockKey);}}}

简单讲一下锁的实现,Redis是线程安全的,利用该的特性可以很轻松的实现一个分布式锁。opsForValue().setIfAbsent(key,value)的作用是如果缓存中没有当前Key则进行缓存同时返回true,否则返回false。只靠这一个逻辑其实也算是实现了锁,但是为了防止防止系统崩溃而导致锁迟迟不释放形成死锁,或者Redis ddl失效导致死锁,又添加一些比如key失效时间等逻辑。可以仔细读一下,并不难理解。

5.分布式锁切面

拦截@CacheLock注解方法,在方法执行前增加获取锁逻辑

@Aspect
@Configuration
@AllArgsConstructor
public class LockMethodInterceptor {private final RedisLockHelper redisLockHelper;private final CacheKeyGenerator cacheKeyGenerator;@Around("execution(public * *(..)) && @annotation(com.zhuoli.service.springboot.distributed.repeat.submit.annotation.CacheLock)")public Object interceptor(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();CacheLock lock = method.getAnnotation(CacheLock.class);if (StringUtils.isEmpty(lock.prefix())) {throw new RuntimeException("lock key don't null...");}final String lockKey = cacheKeyGenerator.getLockKey(pjp);String value = UUID.randomUUID().toString();try {final boolean success = redisLockHelper.lock(lockKey, value, lock.expire(), lock.timeUnit());if (!success) {throw new RuntimeException("重复提交");}try {return pjp.proceed();} catch (Throwable throwable) {throw new RuntimeException("系统异常");}} finally {//如果演示的话需要注释该代码,实际应该放开redisLockHelper.unlock(lockKey, value);}}
}

6.注解使用

该方法使用token生成key
key = prefix + “:” + token

    @CacheLock(prefix = "checkDeviceNo")@RequestMapping(value = "/checkDeviceNo", method = {RequestMethod.POST})public R checkDeviceNo(@RequestParam String deviceNo) {Integer countDeviceNo = cameraDeviceService.countDeviceNo(deviceNo);if (countDeviceNo != null && countDeviceNo > 0) {return R.failed(ErrorCodeEnum.PB10010001.msg());} else {return R.ok();}}

使用请求参数生成key
key = prefix + “:” + deviceNo

    @CacheLock(prefix = "checkDeviceNo")@RequestMapping(value = "/checkDeviceNo", method = {RequestMethod.POST})public R checkDeviceNo(@CacheParam(name = "deviceNo") @RequestParam String deviceNo) {Integer countDeviceNo = cameraDeviceService.countDeviceNo(deviceNo);if (countDeviceNo != null && countDeviceNo > 0) {return R.failed(ErrorCodeEnum.PB10010001.msg());} else {return R.ok();}}

以上就是实现Java分布式锁重复提交问题的解决方案,欢迎提更好的解决方案!!!

Java 分布式服务重复提交解决方案 Redis相关推荐

  1. 接口重复提交解决方案

    接口重复提交解决方案 参考文章: (1)接口重复提交解决方案 (2)https://www.cnblogs.com/java-le/p/11056635.html (3)https://www.cod ...

  2. Java的token解决方案,SpringMVC后台token防重复提交解决方案

    思路1.添加拦截器,拦截需要防重复提交的请求 2.通过注解@Token来添加token/移除token    3.前端页面表单添加(如果是Ajax请求则需要在请求的json数据中添加token值) 核 ...

  3. java分布式事务——seata,tcc解决方案总结!

    目录 1.分布式事务基础理论 1.1.CAP理论 1.2.BASE理论 2.分布式事务解决方案之2PC(两阶段提交) 2.2.1 XA方案 2.2.2 Seata方案 2.2.3分布式事务解决方案之T ...

  4. java mysql防重复提交_防止数据重复提交的6种方法(超简单)!

    有位朋友,某天突然问磊哥:在 Java 中,防止重复提交最简单的方案是什么? 这句话中包含了两个关键信息,第一:防止重复提交:第二:最简单. 于是磊哥问他,是单机环境还是分布式环境? 得到的反馈是单机 ...

  5. Java实现防重复提交

    欢迎访问我的个人博客:www.ifueen.com 防重复提交的重要性? 在业务开发中,为什么我们要去想办法解决重复提交这一问题发生?网上的概念很多:导致表单重复提交,造成数据重复,增加服务器负载,严 ...

  6. java后端 防重复提交_后台防止表单重复提交

    具体的做法: 1.获取用户填写用户名和密码的页面时向后台发送一次请求,这时后台会生成唯一的随机标识号,专业术语称为Token(令牌). 2.将Token发送到客户端的Form表单中,在Form表单中使 ...

  7. java mvc中重复提交表单,spring mvc 防止重复提交表单的两种方法,推荐第二种

    第一种方法:判断session中保存的token 比较麻烦,每次在提交表单时都必须传入上次的token.而且当一个页面使用ajax时,多个表单提交就会有问题. 注解Token代码: package c ...

  8. java mvc中重复提交_SpringMVC之——防止重复提交表单的方法(一)

    这篇博文介绍第一种方法:判断session或其他缓存中保存的token,这里以session为例,具体大家也可以自行扩展以其他的缓存实现. 这种方式比较麻烦,每次在提交表单时都必须传入上次的token ...

  9. java表单重复提交_JavaWeb防止表单重复提交(转载)

    转载自:http://blog.csdn.net/ye1992/article/details/42873219 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用 ...

  10. java分布式服务框架Dubbo的介绍与使用

    1. Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需 ...

最新文章

  1. Zabbix 3.0 从入门到精通(zabbix使用详解)
  2. 财务 计算机网络,计算机网络技术在财务系统中的应用
  3. JZOJ 3731. 【NOIP2014模拟7.10】庐州月
  4. mysql_常用命令
  5. .NET 4.5 Task异步编程学习资料
  6. 性#26684;倔强的HTML5员工
  7. 编程不仅是写代码!?
  8. nginx的高级配置(1)——为某个虚拟主机添加用户验证
  9. java逐行读取文件内容执行sql语句_[11/100] 文件和异常
  10. OpenCV 直方图均衡化 equalizeHist
  11. ubuntu20.04安装谷歌拼音输入法
  12. 奥克兰计算机科学专业世界排名,2020年新西兰计算机科学专业大学排名
  13. 一些关于罗马字符的知识
  14. 图/图的存储/图的遍历
  15. java 程序怎么设置中文_怎么让这个简单JAVA程序读写中文字符
  16. java web属于什么语言_java web开发是什么
  17. 三星 android 自定义物理按键,新机皇驾到!三星Note10将取消物理按键,网友:这造型逼死强迫症...
  18. [卓意听书]6月感恩活动,Q币送不停!
  19. Why Python is Slow? Looking Under the Hood
  20. b2b、c2c、B2C、B2B2C分别是什么意思?有什么区别?

热门文章

  1. 问题解决:你需要trustedinstaller提供的权限才能删除
  2. 完整版本的 poj 题目分类 转载
  3. 最近收集的中科院研究生教学视频
  4. Mesh网格编程(三) 正12面体
  5. 产品经理简历怎么写?看这一篇就够了
  6. 交换排序算法之快速排序-C语言版(带图详细)
  7. Latex(texlive)安装配置教程(详细)
  8. 正则表达式校验手机号
  9. 如何在Web页面里使用高拍仪扫描上传图像
  10. 地址总线、数据总线、控制总线详细解释