>  기사  >  데이터 베이스  >  Redis에서 Redisson red lock을 사용하는 원리는 무엇입니까?

Redis에서 Redisson red lock을 사용하는 원리는 무엇입니까?

王林
王林앞으로
2023-05-30 21:35:181388검색

Redis의 Red Lock을 사용하는 이유

마스터-슬레이브 구조 분산 잠금의 문제점

Redis 분산 잠금을 구현하는 가장 간단한 방법은 Redis에서 키를 생성하는 것입니다. 잠금이 결국 자동으로 해제될 것임을 보장합니다. 클라이언트가 리소스를 해제(잠금 해제)하면 키가 삭제됩니다.

표면적으로는 잘 작동하는 것처럼 보이지만 심각한 단일 실패 지점 문제가 있습니다. Redis가 중단되면 어떻게 될까요? 슬레이브 노드를 추가하면 이 문제가 해결될 수 있다고 말할 수도 있습니다. 그러나 이것은 일반적으로 작동하지 않습니다. Redis의 마스터-슬레이브 동기화는 일반적으로 비동기식이므로 리소스를 독점적으로 사용할 수 없습니다.

이 시나리오에는 명백한 경쟁 조건이 있습니다(마스터-슬레이브 구조):

  • 클라이언트 A가 마스터로부터 잠금을 획득합니다

  • 마스터가 잠금을 슬레이브에 동기화하기 전에 마스터가 실패합니다.

  • 슬레이브 노드가 마스터 노드로 승격됩니다

  • 클라이언트 B가 새 마스터로부터 잠금을 획득합니다

    • 이 잠금에 해당하는 리소스는 클라이언트 A가 이전에 획득한 적이 있습니다. 보안실패!

때로는 프로그램이 운이 좋을 때가 있습니다. 예를 들어 노드가 끊어지면 여러 클라이언트가 동시에 잠금을 획득하는 경우가 있습니다. 이러한 낮은 오류 가능성을 허용할 수 있는 한 이 복제 기반 솔루션을 채택하는 데 문제가 없습니다. 그렇지 않은 경우 아래 설명된 솔루션을 구현하는 것이 좋습니다.

해결책: 빨간색 자물쇠 사용

소개

Redis는 이러한 상황을 위해 빨간색 자물쇠 개념을 도입했습니다. 빨간색 잠금 시스템에서 잠금 획득 또는 해제의 성공적인 신호는 노드의 절반 이상에서 작업이 성공한다는 것입니다.

Principle

Redis의 분산 환경에는 N개의 Redis 마스터가 있다고 가정.. 이러한 노드는 서로 완전히 독립적이며 마스터-슬레이브 복제 또는 기타 클러스터 조정 메커니즘이 없습니다. 우리는 이전에 Redis의 단일 인스턴스에서 잠금을 안전하게 획득하고 해제하는 방법을 설명했습니다. 우리는 모든 (N) 인스턴스에서 이 방법을 사용하여 잠금을 획득하고 해제하도록 보장합니다. 이 예에서는 5개의 Redis 마스터 노드가 있다고 가정합니다. 이는 합리적인 설정이므로 이러한 인스턴스가 동시에 다운되지 않도록 5개의 머신 또는 5개의 가상 머신에서 실행해야 합니다.

잠금을 얻으려면 클라이언트는 다음 작업을 수행해야 합니다.

  • 현재 Unix 시간을 밀리초 단위로 가져옵니다.

  • 동일한 키와 임의의 값을 사용하여 N개의 인스턴스에서 순차적으로 잠금을 획득해 보세요.

    • Redis에 잠금을 설정할 때 클라이언트는 네트워크 연결 및 응답 시간 초과를 설정해야 하며 이는 잠금 만료 시간보다 작아야 합니다.

    • 잠금이 10초 후에 자동으로 만료되는 경우 제한 시간은 5~50밀리초로 설정해야 합니다. Redis 서버가 다운된 경우에도 클라이언트가 응답 결과를 끝없이 기다리지 않도록 합니다. 지정된 시간 내에 서버로부터 응답이 수신되지 않으면 클라이언트는 가능한 한 빨리 다른 Redis 인스턴스에 연결을 시도해야 합니다.

  • 클라이언트는 현재 시간에서 잠금 획득 시작 시간을 뺀 시간(1단계에서 기록한 시간)을 사용하여 잠금 획득에 소요된 시간을 가져옵니다.

    • 락 획득에 성공하기 위한 조건은 다수의 Redis 노드(3개 노드)에서 잠금을 획득해야 하며, 사용 시간은 잠금 만료 시간을 초과할 수 없다는 것입니다.

  • 잠금이 획득되면 키의 실제 유효 시간은 유효 시간에서 잠금을 획득하는 데 사용된 시간을 뺀 값과 같습니다(3단계에서 계산된 결과).

  • 어떤 이유로 잠금 획득에 실패하는 경우(최소 N/2+1 Redis 인스턴스에서 잠금이 획득되지 않았거나 잠금 획득 시간이 유효 시간을 초과한 경우) 클라이언트는 모든 Redis 인스턴스에서 잠금을 해제해야 합니다( 일부 Redis 인스턴스가 전혀 성공적으로 잠기지 않은 경우에도 마찬가지입니다.

Redisson 빨간색 잠금 인스턴스

공식 웹사이트

공식 github: 8. 분산 잠금 및 동기화 장치 · redisson/redisson Wik

Redis 기반 Redisson 빨간색 잠금 RedissonRedLock 개체는 Redlock에서 도입한 잠금 알고리즘을 구현합니다. 이 개체는 여러 RLock 개체를 빨간색 잠금으로 연결하는 데에도 사용할 수 있습니다. 각 RLock 개체 인스턴스는 다른 Redisson 인스턴스에서 나올 수 있습니다.

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
 
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

특정 분산 잠금을 저장하는 역할을 하는 일부 Redis 노드가 다운되고 이러한 잠금이 잠긴 상태가 되면 해당 잠금이 잠길 것이라는 사실은 누구나 알고 있습니다. 이러한 상황을 피하기 위해 Redisson은 잠금을 모니터링하는 감시 장치를 내부적으로 제공합니다. 그 기능은 Redisson 인스턴스가 닫히기 전에 잠금의 유효 기간을 지속적으로 연장하는 것입니다. 기본적으로 Watchdog의 잠금 확인 시간 제한은 30초이며, Config.lockWatchdogTimeout을 수정하여 별도로 지정할 수도 있습니다.

Redisson은 rentTime 매개변수를 잠그고 설정하여 잠금 시간을 지정할 수도 있습니다. 이 시간이 지나면 잠금 장치가 자동으로 잠금 해제됩니다.

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);
 
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

Redisson 빨간색 잠금 원리

RedissonRedLock은 RedissonMultiLock을 확장하므로 실제로 redLock.tryLock은 실제로 org.redisson.RedissonMultiLock.java#tryLock()을 호출한 다음 유사한 tryLock(long waitTime, long rentTime, TimeUnit 단위)을 호출합니다. ), 입력 매개변수는 tryLock(-1, -1, null)

입니다.

org.redisson.RedissonMultiLock.java#tryLock(long waitTime, long leaseTime, TimeUnit unit)源码如下:

final List<RLock> locks = new ArrayList<>();
 
/**
 * Creates instance with multiple {@link RLock} objects.
 * Each RLock object could be created by own Redisson instance.
 *
 * @param locks - array of locks
 */
public RedissonMultiLock(RLock... locks) {
    if (locks.length == 0) {
        throw new IllegalArgumentException("Lock objects are not defined");
    }
    this.locks.addAll(Arrays.asList(locks));
}
 
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    long newLeaseTime = -1;
    if (leaseTime != -1) {
        newLeaseTime = unit.toMillis(waitTime)*2;
    }
    
    long time = System.currentTimeMillis();
    long remainTime = -1;
    if (waitTime != -1) {
        remainTime = unit.toMillis(waitTime);
    }
    long lockWaitTime = calcLockWaitTime(remainTime);
    /**
     * 1. 允许加锁失败节点个数限制(N-(N/2+1))
     */
    int failedLocksLimit = failedLocksLimit();
    /**
     * 2. 遍历所有节点通过EVAL命令执行lua加锁
     */
    List<RLock> acquiredLocks = new ArrayList<>(locks.size());
    for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
        RLock lock = iterator.next();
        boolean lockAcquired;
        /**
         *  3.对节点尝试加锁
         */
        try {
            if (waitTime == -1 && leaseTime == -1) {
                lockAcquired = lock.tryLock();
            } else {
                long awaitTime = Math.min(lockWaitTime, remainTime);
                lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
            }
        } catch (RedisResponseTimeoutException e) {
            // 如果抛出这类异常,为了防止加锁成功,但是响应失败,需要解锁所有节点
            unlockInner(Arrays.asList(lock));
            lockAcquired = false;
        } catch (Exception e) {
            // 抛出异常表示获取锁失败
            lockAcquired = false;
        }
        
        if (lockAcquired) {
            /**
             *4. 如果获取到锁则添加到已获取锁集合中
             */
            acquiredLocks.add(lock);
        } else {
            /**
             * 5. 计算已经申请锁失败的节点是否已经到达 允许加锁失败节点个数限制 (N-(N/2+1))
             * 如果已经到达, 就认定最终申请锁失败,则没有必要继续从后面的节点申请了
             * 因为 Redlock 算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功
             */
            if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                break;
            }
 
            if (failedLocksLimit == 0) {
                unlockInner(acquiredLocks);
                if (waitTime == -1 && leaseTime == -1) {
                    return false;
                }
                failedLocksLimit = failedLocksLimit();
                acquiredLocks.clear();
                // reset iterator
                while (iterator.hasPrevious()) {
                    iterator.previous();
                }
            } else {
                failedLocksLimit--;
            }
        }
 
        /**
         * 6.计算 目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,返回false
         */
        if (remainTime != -1) {
            remainTime -= System.currentTimeMillis() - time;
            time = System.currentTimeMillis();
            if (remainTime <= 0) {
                unlockInner(acquiredLocks);
                return false;
            }
        }
    }
 
    if (leaseTime != -1) {
        List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());
        for (RLock rLock : acquiredLocks) {
            RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
            futures.add(future);
        }
        
        for (RFuture<Boolean> rFuture : futures) {
            rFuture.syncUninterruptibly();
        }
    }
 
    /**
     * 7.如果逻辑正常执行完则认为最终申请锁成功,返回true
     */
    return true;
}

위 내용은 Redis에서 Redisson red lock을 사용하는 원리는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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