基于注解的redis的分布式锁实现

基于redis的分布式锁实现的优化

上篇的redis的分布式锁实现,但还是过于繁杂,代码侵入性高

*本遍是将上篇的实现调整成注解方式,利用aop实现分布式锁
只用在方法上加个注解,同时加上了重试机制
*

1.列子

@RedisLock(lockPrefix = AbstractRedisContants.DIST_LOCK_FUND, lockParameter = "fundId")
    public void handle(FundAmountOptTypeEnum optType, Long fundId, BigDecimal amount, String operator, String remark) {
        strategyMap.get(optType).handle(fundId, amount, operator, remark);
    }

2.RedisLock

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.Order;

/**
 * redis锁注解
 * 
 * @author qyl
 * @date 2018/12/27
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Order(value = 10)
public @interface RedisLock {
    // 锁前缀
    String lockPrefix() default "";

    // 方法参数名(用于取参数名的值与锁前缀拼接成锁名),尽量不要用对象map等,对象会toString后与锁前缀拼接
    String lockParameter() default "";

    // 尝试加锁,最多等待时间(毫秒)
    long lockWait() default 3000L;

    // 自动解锁时间 (毫秒)
    long autoUnlockTime() default 10000L;

    // 重试次数
    int retryNum() default 0;

    // 重试等待时间 (毫秒)
    long retryWait() default 500L;

}

@Order(value = 10)
当使用了@Transactional 或其它切面时,相当于在执行执行多次次AOP切面。那么我们需要通过order 属性去定义AOP切面的先后执行顺序。 order越小,在AOP的chain 中越靠前,越先执行。(chain模式)

3.RedisLockAspect

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.feitai.jieya.server.common.annotation.RedisLock;
import com.feitai.jieya.server.common.exception.BusinessException;
import com.feitai.jieya.server.utils.StringUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * Description: 分布式锁
 * <p>
 * 先获取锁, 获取不到则继续等待(指定时间), 失败次数(指定)次后跳出, 消费降级(抛出,系统繁忙稍后再试) 如果没有重试次数,方法返回null 记得捕获NP 当重试次数有, 但是重试间隔时间没写, 默认200ms 间隔
 * </p>
 * @author qyl
 * 
 */
@Aspect
@Component
@Slf4j
@Order(10)
public class RedisLockAspect {

    private static final String LOCK_NAME = "lockName";
    private static final String lOCK_WAIT = "lockWait";
    private static final String AUTO_UNLOCK_TIME = "autoUnlockTime";
    private static final String RETRY_NUM = "retryNum";
    private static final String RETRY_WAIT = "retryWait";

    /**
     * redis工具类
     */
    @Autowired
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.feitai.jieya.server.common.annotation.RedisLock)")
    public void lockAspect() {}

    @Around("lockAspect()")
    public Object lockAroundAction(ProceedingJoinPoint proceeding) throws Throwable {

        // 获取注解中的参数
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
        String lockName = (String)annotationArgs.get(LOCK_NAME);
        Assert.notNull(lockName, "分布式,锁名不能为空");
        int retryNum = (int)annotationArgs.get(RETRY_NUM);
        long retryWait = (long)annotationArgs.get(RETRY_WAIT);
        long lockWait = (long)annotationArgs.get(lOCK_WAIT);
        long autoUnlockTime = (long)annotationArgs.get(AUTO_UNLOCK_TIME);

        // 获取锁
        RLock lock = redissonClient.getLock(lockName);
        try {
            boolean res = lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS);
            if (res) {
                // 执行主逻辑
                return proceeding.proceed();

            } else {
                // 如果重试次数为零, 则不重试
                if (retryNum <= 0) {
                    log.info(String.format("{%s}已经被锁, 不重试", lockName));
                    throw new BusinessException(String.format("{%s}已经被锁, 不重试", lockName));
                }

                if (retryWait == 0) {
                    retryWait = 200L;
                }
                // 设置失败次数计数器, 当到达指定次数时, 返回失败
                int failCount = 1;
                while (failCount <= retryNum) {
                    // 等待指定时间ms
                    Thread.sleep(retryWait);
                    if (lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS)) {
                        // 执行主逻辑
                        return proceeding.proceed();
                    } else {
                        log.info(String.format("{%s}已经被锁, 正在重试[ %s/%s ],重试间隔{%s}毫秒", lockName, failCount, retryNum,
                            retryWait));
                        failCount++;
                    }
                }
                throw new BusinessException("系统繁忙, 请稍等再试");
            }
        } catch (Throwable throwable) {
            log.error(String.format("执行分布式锁发生异常锁名:{%s},异常名称:{%s}", lockName, throwable.getMessage()));
            throw throwable;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取锁参数
     *
     * @param proceeding
     * @return
     */
    private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
        // if (!(objs[i] instanceof ExtendedServletRequestDataBinder)
        // && !(objs[i] instanceof HttpServletResponseWrapper)) {

        proceeding.getArgs();
        Object[] objs = proceeding.getArgs();
        String[] argNames = ((MethodSignature)proceeding.getSignature()).getParameterNames(); // 参数名

        Class target = proceeding.getTarget().getClass();
        Method[] methods = target.getMethods();
        String methodName = proceeding.getSignature().getName();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Map<String, Object> result = new HashMap<String, Object>();
                RedisLock redisLock = method.getAnnotation(RedisLock.class);

                if (StringUtils.isNotBlank(redisLock.lockParameter())) {
                    for (int i = 0; i < objs.length; i++) {
                        if (redisLock.lockParameter().equals(argNames[i])) {
                            result.put(LOCK_NAME, redisLock.lockPrefix() + objs[i]);
                            break;
                        }

                    }
                } else {
                    result.put(LOCK_NAME, redisLock.lockPrefix());
                }
                result.put(lOCK_WAIT, redisLock.lockWait());
                result.put(AUTO_UNLOCK_TIME, redisLock.autoUnlockTime());
                result.put(RETRY_NUM, redisLock.retryNum());
                result.put(RETRY_WAIT, redisLock.retryWait());

                return result;
            }
        }
        throw new RuntimeException("异常");

    }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis分布式锁是一种基于Redis分布式锁实现方式,通过Redis实现对分布式系统中共享资源的互斥访问。具体注解如下: 1. 分布式锁实现方式:使用Redis的setnx命令实现分布式锁,即在Redis中创建一个键值对,当键不存在时才能创建成功,表示获取锁成功,否则获取锁失败。在释放锁时,删除对应的键值对即可。 2. 锁的唯一性:分布式锁需要保证锁的唯一性,可以通过在锁的键名中添加全局唯一的标识符,如应用程序的名称、服务器的IP地址等。 3. 锁的过期时间:为了防止死锁,需要给锁设置过期时间,可通过Redis的expire命令设置键的过期时间。 4. 锁的重入性:分布式锁需要支持重入,即同一个线程可以多次获取同一个锁,需要在锁的值中添加线程ID等标识符,以便在释放锁时判断是否是同一个线程。 5. 锁的释放方式:分布式锁需要支持手动和自动释放,手动释放即调用del命令删除锁的键值对,自动释放即设置锁的过期时间,在过期后Redis会自动删除键值对。 6. 锁的容错性:分布式锁需要保证容错性,即在获取锁失败时不会导致系统异常或数据丢失,可以通过重试机制或设置超时时间来实现。 7. 锁的并发性:分布式锁需要保证并发性,即在高并发情况下多个线程可以同时获取锁,需要使用Redis的lua脚本实现原子性操作,避免并发问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值