分布式防止重复请求或者高并发防止重复提交

1:自定义注解JRepeat

java 复制代码
package com.huan.study.mybatis.config;



import java.lang.annotation.*;

/**
 * 防止重复提交的注解
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface JRepeat {

    /**
     * 超时时间
     *
     * @return
     */
    int lockTime();


    /**
     * redis 锁key的
     *
     * @return redis 锁key
     */
    String lockKey() default "";



}

2:自定义切面

java 复制代码
package com.huan.study.mybatis.aspect;

/**
 * @author zyf
 */

import com.huan.study.mybatis.config.JRepeat;
import com.huan.study.mybatis.config.RedissonLockClient;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 防止重复提交分布式锁拦截器
 *
 */
@Aspect
@Component
public class RepeatSubmitAspect extends BaseAspect {

    @Resource
    private RedissonLockClient redissonLockClient;

    /***
     * 定义controller切入点拦截规则,拦截JRepeat注解的业务方法
     */
    @Pointcut("@annotation(jRepeat)")
    public void pointCut(JRepeat jRepeat) {
    }

    /**
     * AOP分布式锁拦截
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    @Around("pointCut(jRepeat)")
    public Object repeatSubmit(ProceedingJoinPoint joinPoint,JRepeat jRepeat) throws Throwable {
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        if (Objects.nonNull(jRepeat)) {
            // 获取参数
            Object[] args = joinPoint.getArgs();
            // 进行一些参数的处理,比如获取订单号,操作人id等
            StringBuffer lockKeyBuffer = new StringBuffer();
            String key =getValueBySpEL(jRepeat.lockKey(), parameterNames, args,"RepeatSubmit").get(0);
            // 公平加锁,lockTime后锁自动释放
            boolean isLocked = false;
            try {
                isLocked = redissonLockClient.fairLock(key, TimeUnit.SECONDS, jRepeat.lockTime());
                // 如果成功获取到锁就继续执行
                if (isLocked) {
                    // 执行进程
                    return joinPoint.proceed();
                } else {
                    // 未获取到锁
                    throw new RuntimeException("请勿重复提交");
                }
            } finally {
                // 如果锁还存在,在方法执行完成后,释放锁
                if (isLocked) {
                    redissonLockClient.unlock(key);
                }
            }
        }
        return joinPoint.proceed();
    }
}
java 复制代码
package com.huan.study.mybatis.aspect;

import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.ArrayList;
import java.util.List;


@Slf4j
public class BaseAspect {

    /**
     * 通过spring SpEL 获取参数
     *
     * @param key            定义的key值 以#开头 例如:#user
     * @param parameterNames 形参
     * @param values         形参值
     * @param keyConstant    key的常亮
     * @return
     */
    public List<String> getValueBySpEL(String key, String[] parameterNames, Object[] values, String keyConstant) {
        List<String> keys = new ArrayList<>();
        if (!key.contains("#")) {
            String s = "redis:lock:" + key + keyConstant;
            log.debug("lockKey:" + s);
            keys.add(s);
            return keys;
        }
        //spel解析器
        ExpressionParser parser = new SpelExpressionParser();
        //spel上下文
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], values[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) {
            if (value instanceof List) {
                List value1 = (List) value;
                for (Object o : value1) {
                    addKeys(keys, o, keyConstant);
                }
            } else if (value.getClass().isArray()) {
                Object[] obj = (Object[]) value;
                for (Object o : obj) {
                    addKeys(keys, o, keyConstant);
                }
            } else {
                addKeys(keys, value, keyConstant);
            }
        }
        log.info("表达式key={},value={}", key, keys);
        return keys;
    }

    private void addKeys(List<String> keys, Object o, String keyConstant) {
        keys.add("redis:lock:" + o.toString() + keyConstant);
    }
}

3:自定义RedissonLockClient

java 复制代码
package com.huan.study.mybatis.config;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁实现基于Redisson
 *
 * @author zyf
 * @date 2020-11-11
 */
@Slf4j
@Component
public class RedissonLockClient {

    @Autowired
    private RedissonClient redissonClient;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取锁
     */
    public RLock getLock(String lockKey) {
        return redissonClient.getLock(lockKey);
    }

    /**
     * 加锁操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long expireSeconds) {
        return tryLock(lockName, 0, expireSeconds);
    }


    /**
     * 加锁操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long waitTime, long expireSeconds) {
        RLock rLock = getLock(lockName);
        boolean getLock = false;
        try {
            getLock = rLock.tryLock(waitTime, expireSeconds, TimeUnit.SECONDS);
            if (getLock) {
                log.info("获取锁成功,lockName={}", lockName);
            } else {
                log.info("获取锁失败,lockName={}", lockName);
            }
        } catch (InterruptedException e) {
            log.error("获取式锁异常,lockName=" + lockName, e);
            getLock = false;
        }
        return getLock;
    }


    public boolean fairLock(String lockKey, TimeUnit unit, int leaseTime) {
        RLock fairLock = redissonClient.getFairLock(lockKey);
        try {
            boolean existKey = existKey(lockKey);
            // 已经存在了,就直接返回
            if (existKey) {
                return false;
            }
            return fairLock.tryLock(3, leaseTime, unit);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean existKey(String key) {
        return redisTemplate.hasKey(key);
    }
    /**
     * 锁lockKey
     *
     * @param lockKey
     * @return
     */
    public RLock lock(String lockKey) {
        RLock lock = getLock(lockKey);
        lock.lock();
        return lock;
    }

    /**
     * 锁lockKey
     *
     * @param lockKey
     * @param leaseTime
     * @return
     */
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return lock;
    }


    /**
     * 解锁
     *
     * @param lockName 锁名称
     */
    public void unlock(String lockName) {
        try {
            redissonClient.getLock(lockName).unlock();
        } catch (Exception e) {
            log.error("解锁异常,lockName=" + lockName, e);
        }
    }
}

4:demo所著相同请求5秒,如果方法执行完成释放

java 复制代码
    @SneakyThrows
    @GetMapping("test")
    @JRepeat(lockKey = "#phone", lockTime = 5)
    public void test(String phone) {

        new Thread(() -> {
            try {
                // 尝试获取锁,最多等待10秒,获取锁后10秒自动释放
                System.out.println(new Date() + Thread.currentThread().getName() + " 获取到锁,开始处理任务...");
                Thread.sleep(10000); // 模拟处理任务需要花费一些时间
                System.out.println(new Date() + Thread.currentThread().getName() + " 处理任务完成,释放锁...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        System.out.println("===" + new Date());

    }

5:yml配置redis

yml 复制代码
spring:
  redis:
    database: 0
    host: 127.0.0.1
    password: 123456
    port: 6379

注意:因为是基于AOP切面,如果在方法内,调用的方法上添加该注解会失效

相关推荐
方圆想当图灵1 小时前
如何让百万 QPS 下的服务更高效?
分布式·后端
Dobby_056 小时前
【Hadoop】分布式文件系统 HDFS
大数据·hadoop·分布式
哈哈很哈哈6 小时前
Spark 核心 RDD详解
大数据·分布式·spark·scala
项目題供诗6 小时前
Hadoop(十一)
大数据·hadoop·分布式
学习中的阿陈11 小时前
Hadoop伪分布式环境配置
大数据·hadoop·分布式
CesareCheung11 小时前
JMeter分布式压力测试
分布式·jmeter·压力测试
失散1313 小时前
分布式专题——10.5 ShardingSphere的CosID主键生成框架
java·分布式·架构·分库分表·shadingsphere
Cxzzzzzzzzzz17 小时前
RabbitMQ 在实际开发中的应用场景与实现方案
分布式·rabbitmq
在未来等你17 小时前
Kafka面试精讲 Day 16:生产者性能优化策略
大数据·分布式·面试·kafka·消息队列
王大帅の王同学17 小时前
Thinkphp6接入讯飞星火大模型Spark Lite完全免费的API
大数据·分布式·spark