栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 分布式服务重复提交解决方案 Redis

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 分布式服务重复提交解决方案 Redis

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

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



    4.0.0
 
    com.service
    springboot-repeat-submit
    1.0-SNAPSHOT
 
    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.3.RELEASE
    
 
    
        
        
            org.springframework.boot
            spring-boot-starter
        
 
        
            org.springframework.boot
            spring-boot-starter-web
        
 
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
 
        
        
            redis.clients
            jedis
            2.9.0
        
 
        
        
            org.projectlombok
            lombok
            1.18.2
            provided
        
 
        
            com.google.guava
            guava
            21.0
        
 
    
 

2.自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
public @interface CacheLock {
 
    
    String prefix() default "";
 
    
    int expire() default 5;
 
    
    TimeUnit timeUnit() default TimeUnit.SECONDS;
 
    
    String delimiter() default ":";
}
3.分布式锁key 生成
public class LockKeyGenerator implements CacheKeyGenerator {
 
    @Override
    public 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 {
    
    String name() default "";
}
public class LockKeyGenerator implements CacheKeyGenerator {
 
    @Override
    public 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 {

    @Override
    public 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 {
 
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }
 
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate 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;
 
    
    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));
            
            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);
    }
 
    
    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 {
            
            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分布式锁重复提交问题的解决方案,欢迎提更好的解决方案!!!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/770771.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号