>  기사  >  데이터 베이스  >  거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.

거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.

青灯夜游
青灯夜游앞으로
2021-11-02 11:02:411682검색

이 기사는 Redis 분산 잠금 구현 방법을 소개합니다. 도움이 되길 바랍니다!

거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.

안녕하세요 여러분, 오늘은 redisson이 구현한 다중 유형 잠금, 거의 모든 잠금 시나리오를 지원하고 소규모 MQ 및 Redis의 다양한 데이터 작업도 지원하는 redis 분산 잠금 구현을 공유하겠습니다. [관련 권장 사항: Redis 동영상 튜토리얼]

이론적인 부분

이전 기사에서는 Redis를 통해 분산 잠금을 구현하는 두 가지 방법을 소개했습니다.

  • redis와 함께 제공되는 명령을 통해: setNX

  • redisson 클라이언트를 통해: redisson

저자는 redisson이 interlock, red lock, 읽기-쓰기 잠금, 공정 잠금 등과 같은 더 많은 잠금 유형을 지원하기 때문에 redisson 클라이언트 사용을 권장합니다. Redisson 구현은 더 간단합니다. 개발자는 해당 API만 호출하면 되며 기본 잠금 프로세스와 잠금 해제의 원자성 문제에 대해 걱정할 필요가 없습니다. API即可,无需关心底层加锁的过程和解锁的原子性问题。

在Redis分布式锁中,列出了redisson对于多种的锁类型的简单实现,即编程式实现。这样的实现完全能够满足我们的日常开发需求,但是缺点也很明显。

譬如:

  • 代码嵌入较多,不够优雅
  • 重复代码
  • 对锁的参数运用不直观
  • 容易忘掉解锁的步骤

使用过Spring的同学,肯定都知道@Transactional注解,Spring即支持编程式事务,也支持注解式(声明式)事务。

我们是否也可以参考这样的实现呢?

答案是:完全OK!

AOP就是专门干这种事的。

实战部分

1、引入redisson依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.2</version>
</dependency>Copy to clipboardErrorCopied

2、自定义注解

/**
 * 分布式锁自定义注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {

    /**
     * 锁的模式:如果不设置自动模式,当参数只有一个.使用 REENTRANT 参数多个 MULTIPLE
     */
    LockModel lockModel() default LockModel.AUTO;

    /**
     * 如果keys有多个,如果不设置,则使用 联锁
     *
     * @return
     */
    String[] keys() default {};

    /**
     * key的静态常量:当key的spel的值是LIST、数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,
     * 而我们如果又需要一个常量的话。这个参数将会在拼接在每个元素的后面
     *
     * @return
     */
    String keyConstant() default "";

    /**
     * 锁超时时间,默认30000毫秒(可在配置文件全局设置)
     *
     * @return
     */
    long watchDogTimeout() default 30000;

    /**
     * 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待(可在配置文件全局设置)
     *
     * @return
     */
    long attemptTimeout() default 10000;
}

3、常量类

/**
 * Redisson常量类
 */
public class RedissonConst {
    /**
     * redisson锁默认前缀
     */
    public static final String REDISSON_LOCK = "redisson:lock:";
    /**
     * spel表达式占位符
     */
    public static final String PLACE_HOLDER = "#";
}

4、枚举

/**
 * 锁的模式
 */
public enum LockModel {
    /**
     * 可重入锁
     */
    REENTRANT,
    /**
     * 公平锁
     */
    FAIR,
    /**
     * 联锁
     */
    MULTIPLE,
    /**
     * 红锁
     */
    RED_LOCK,
    /**
     * 读锁
     */
    READ,
    /**
     * 写锁
     */
    WRITE,
    /**
     * 自动模式,当参数只有一个使用 REENTRANT 参数多个 RED_LOCK
     */
    AUTO
}

5、自定义异常

/**
 * 分布式锁异常
 */
public class ReddissonException extends RuntimeException {

    public ReddissonException() {
    }

    public ReddissonException(String message) {
        super(message);
    }

    public ReddissonException(String message, Throwable cause) {
        super(message, cause);
    }

    public ReddissonException(Throwable cause) {
        super(cause);
    }

    public ReddissonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

6、AOP切面

   /**
 * 分布式锁aop
 */
@Slf4j
@Aspect
public class LockAop {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private LockStrategyFactory lockStrategyFactory;

    @Around("@annotation(lock)")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable {
        // 需要加锁的key数组
        String[] keys = lock.keys();
        if (ArrayUtil.isEmpty(keys)) {
            throw new ReddissonException("redisson lock keys不能为空");
        }
        // 获取方法的参数名
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
        Object[] args = proceedingJoinPoint.getArgs();
        // 等待锁的超时时间
        long attemptTimeout = lock.attemptTimeout();
        if (attemptTimeout == 0) {
            attemptTimeout = redissonProperties.getAttemptTimeout();
        }
        // 锁超时时间
        long lockWatchdogTimeout = lock.watchdogTimeout();
        if (lockWatchdogTimeout == 0) {
            lockWatchdogTimeout = redissonProperties.getLockWatchdogTimeout();
        }
        // 加锁模式
        LockModel lockModel = getLockModel(lock, keys);
        if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.RED_LOCK) && keys.length > 1) {
            throw new ReddissonException("参数有多个,锁模式为->" + lockModel.name() + ",无法匹配加锁");
        }
        log.info("锁模式->{},等待锁定时间->{}毫秒,锁定最长时间->{}毫秒", lockModel.name(), attemptTimeout, lockWatchdogTimeout);
        boolean res = false;
        // 策略模式获取redisson锁对象
        RLock rLock = lockStrategyFactory.createLock(lockModel, keys, parameterNames, args, lock.keyConstant(), redissonClient);
        //执行aop
        if (rLock != null) {
            try {
                if (attemptTimeout == -1) {
                    res = true;
                    //一直等待加锁
                    rLock.lock(lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                } else {
                    res = rLock.tryLock(attemptTimeout, lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                }
                if (res) {
                    return proceedingJoinPoint.proceed();
                } else {
                    throw new ReddissonException("获取锁失败");
                }
            } finally {
                if (res) {
                    rLock.unlock();
                }
            }
        }
        throw new ReddissonException("获取锁失败");
    }

    /**
     * 获取加锁模式
     *
     * @param lock
     * @param keys
     * @return
     */
    private LockModel getLockModel(Lock lock, String[] keys) {
        LockModel lockModel = lock.lockModel();
        // 自动模式:优先匹配全局配置,再判断用红锁还是可重入锁
        if (lockModel.equals(LockModel.AUTO)) {
            LockModel globalLockModel = redissonProperties.getLockModel();
            if (globalLockModel != null) {
                lockModel = globalLockModel;
            } else if (keys.length > 1) {
                lockModel = LockModel.RED_LOCK;
            } else {
                lockModel = LockModel.REENTRANT;
            }
        }
        return lockModel;
    }
}

这里使用了策略模式

Redis 분산 잠금에는 Redisson의 다양한 잠금 유형에 대한 간단한 구현, 즉 프로그래밍 방식 구현이 나열되어 있습니다. 이러한 구현은 일상적인 개발 요구 사항을 완벽하게 충족할 수 있지만 단점도 분명합니다.

예:

코드가 너무 많이 포함되어 있고 우아하지 않습니다.

중복 코드

  • 잠금 매개변수 사용이 직관적이지 않습니다.
잠금 해제 단계를 잊어버리기 쉽습니다.
  • Spring을 사용하는 학생들은 모두 @Transactional 주석을 알아야 합니다. Spring은 프로그래밍 방식 트랜잭션과 주석이 달린(선언적) 트랜잭션을 모두 지원합니다.
이런 구현도 참조할 수 있나요?
  • 답은: 완전히 괜찮습니다! AOP는 이런 종류의 작업을 수행하도록 특별히 설계되었습니다.
    실용적인 부분
  • 1. Redisson 종속성 소개
/**
 * 锁策略抽象基类
 */
@Slf4j
abstract class LockStrategy {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 创建RLock
     *
     * @param keys
     * @param parameterNames
     * @param args
     * @param keyConstant
     * @return
     */
    abstract RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient);

    /**
     * 获取RLock
     *
     * @param keys
     * @param parameterNames
     * @param args
     * @param keyConstant
     * @return
     */
    public RLock[] getRLocks(String[] keys, String[] parameterNames, Object[] args, String keyConstant) {
        List<RLock> rLocks = new ArrayList<>();
        for (String key : keys) {
            List<String> valueBySpel = getValueBySpel(key, parameterNames, args, keyConstant);
            for (String s : valueBySpel) {
                rLocks.add(redissonClient.getLock(s));
            }
        }
        RLock[] locks = new RLock[rLocks.size()];
        int index = 0;
        for (RLock r : rLocks) {
            locks[index++] = r;
        }
        return locks;
    }

    /**
     * 通过spring Spel 获取参数
     *
     * @param key            定义的key值 以#开头 例如:#user
     * @param parameterNames 形参
     * @param args           形参值
     * @param keyConstant    key的常亮
     * @return
     */
    List<String> getValueBySpel(String key, String[] parameterNames, Object[] args, String keyConstant) {
        List<String> keys = new ArrayList<>();
        if (!key.contains(PLACE_HOLDER)) {
            String s = REDISSON_LOCK + key + keyConstant;
            log.info("没有使用spel表达式value->{}", 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], args[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) {
            if (value instanceof List) {
                List valueList = (List) value;
                for (Object o : valueList) {
                    keys.add(REDISSON_LOCK + o.toString() + keyConstant);
                }
            } else if (value.getClass().isArray()) {
                Object[] objects = (Object[]) value;
                for (Object o : objects) {
                    keys.add(REDISSON_LOCK + o.toString() + keyConstant);
                }
            } else {
                keys.add(REDISSON_LOCK + value.toString() + keyConstant);
            }
        }
        log.info("spel表达式key={},value={}", key, keys);
        return keys;
    }
}

    2 맞춤 주석
  • /**
     * 可重入锁策略
     */
    public class ReentrantLockStrategy extends LockStrategy {
    
        @Override
        public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
            List<String> valueBySpel = getValueBySpel(keys[0], parameterNames, args, keyConstant);
            //如果spel表达式是数组或者集合 则使用红锁
            if (valueBySpel.size() == 1) {
                return redissonClient.getLock(valueBySpel.get(0));
            } else {
                RLock[] locks = new RLock[valueBySpel.size()];
                int index = 0;
                for (String s : valueBySpel) {
                    locks[index++] = redissonClient.getLock(s);
                }
                return new RedissonRedLock(locks);
            }
        }
    }
    3, 상수 클래스
/**
 * 公平锁策略
 */
public class FairLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        return redissonClient.getFairLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
    }
}

    4, 열거형
  • /**
     * 联锁策略
     */
    public class MultipleLockStrategy extends LockStrategy {
    
        @Override
        public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
            RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
            return new RedissonMultiLock(locks);
        }
    }

    5. 사용자 정의 예외
/**
 * 红锁策略
 */
public class RedLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
        return new RedissonRedLock(locks);
    }
}

6. AOP 측면

/**
 * 读锁策略
 */
public class ReadLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
        return rwLock.readLock();
    }
}

전략 모드는 여기에서 다양한 잠금 유형을 제공하는 데 사용됩니다.

7. 잠금 전략 구현거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.먼저 잠금 전략의

추상 기본 클래스

를 정의합니다(인터페이스를 사용할 수도 있음).

/**
 * 写锁策略
 */
public class WriteLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
        return rwLock.writeLock();
    }
}
🎜 그런 다음 다양한 잠금 모드의 특정 구현을 제공합니다. 🎜🎜🎜🎜 반복 가능 입력 잠금: 🎜🎜🎜
/**
 * 锁的策略工厂
 */
@Service
public class LockStrategyFactory {

    private LockStrategyFactory() {
    }

    private static final Map<LockModel, LockStrategy> STRATEGIES = new HashMap<>(6);

    static {
        STRATEGIES.put(LockModel.FAIR, new FairLockStrategy());
        STRATEGIES.put(LockModel.REENTRANT, new ReentrantLockStrategy());
        STRATEGIES.put(LockModel.RED_LOCK, new RedLockStrategy());
        STRATEGIES.put(LockModel.READ, new ReadLockStrategy());
        STRATEGIES.put(LockModel.WRITE, new WriteLockStrategy());
        STRATEGIES.put(LockModel.MULTIPLE, new MultipleLockStrategy());
    }

    public RLock createLock(LockModel lockModel, String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        return STRATEGIES.get(lockModel).createLock(keys, parameterNames, args, keyConstant, redissonClient);
    }
}
🎜🎜🎜공정한 잠금:🎜🎜🎜
    @Lock(keys = "#query.channel") // 支持spel
    @ApiOperation("分页列表")
    @GetMapping
    public ApiPageResult list(VendorProjectItemQuery query, Pagination pagination) {
        return ApiPageResult.success(pagination, vendorProjectItemService.list(query, pagination), vendorProjectItemService.count(query));
    }
🎜🎜🎜Interlock🎜🎜🎜rrreee🎜🎜🎜빨간색 잠금🎜🎜🎜rrreee🎜🎜🎜읽기 잠금 🎜🎜🎜rrreee🎜🎜🎜쓰기 잠금🎜 🎜🎜rrreee🎜마지막으로 🎜전략 팩토리🎜초기화 잠금 전략 제공: 🎜rrreee🎜8. 사용 방법 🎜rrreee🎜완료되었습니다. 성공적으로 완료되었습니다.🎜🎜🎜🎜🎜더 많은 프로그래밍 관련 지식을 보려면 다음 사이트를 방문하세요. : 🎜프로그래밍 영상🎜! ! 🎜

위 내용은 거의 모든 잠금 시나리오를 지원하는 Redis에서 분산 잠금을 구현하는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제