ホームページ  >  記事  >  データベース  >  Redis で Redisson レッドロックを使用する原理は何ですか

Redis で Redisson レッドロックを使用する原理は何ですか

王林
王林転載
2023-05-30 21:35:181430ブラウズ

Redis のレッド ロックを使用する理由

マスター/スレーブ構造の分散ロックの問題

Redis 分散ロックを実装する最も簡単な方法は、Redis A で作成することです。キーの場合、このキーには有効期限 (TTL) があり、ロックが最終的に自動的に解放されるようになります。クライアントがリソースを解放する (ロックを解除する) と、キーは削除されます。

表面的にはうまく機能しているように見えますが、深刻な単一障害点の問題があります。それは、Redis がハングしたらどうなるでしょうか?この問題はスレーブノードを追加することで解決できると言えるかもしれません。しかし、これは通常は機能しません。 Redis のマスターとスレーブの同期は通常非同期であるため、リソースの排他的使用を実現できません。

このシナリオ (マスター/スレーブ構造) には明らかな競合状態があります:

  • クライアント A がマスターからロックを取得します

  • マスターがスレーブにロックを同期する前に、マスターがクラッシュします。

  • #スレーブ ノードがマスター ノードに昇格されます

  • クライアント B が新しいマスターからロックを取得します

    • このロックに対応するリソースは、クライアント A によって以前に取得されています。セキュリティの失敗!

#プログラムには、偶然が重なることがあります。たとえば、ノードがハングアップすると、複数のクライアントが同時にロックを取得することがあります。この低いエラーの可能性を許容できる限り、このレプリケーション ベースのソリューションを採用することに問題はありません。それ以外の場合は、以下で説明する解決策を実装することをお勧めします。

解決策: レッド ロックを使用する

はじめに

Redis では、この状況に対してレッド ロックの概念を導入しています。レッド ロック システムでは、ロックの取得または解放が成功した兆候は、半分以上のノードで操作が成功したことです。

原則

Redis の分散環境に N 個の Redis マスターがあると仮定します。これらのノードは互いに完全に独立しており、マスター/スレーブ レプリケーションやその他のクラスター調整メカニズムはありません。 Redis の単一インスタンスでロックを安全に取得および解放する方法を以前に説明しました。すべての (N 個の) インスタンスでこのメソッドを使用してロックが取得および解放されることを保証します。この例では、Redis マスター ノードが 5 つあると仮定します。これはより合理的な設定であるため、これらのインスタンスを 5 台のマシンまたは 5 台の仮想マシンで実行して、すべてが同時にダウンしないようにする必要があります。

ロックを取得するには、クライアントは次の操作を実行する必要があります:

  • 現在の Unix 時間をミリ秒単位で取得します。

  • 同じキーとランダムな値を使用して、N 個のインスタンスから順番にロックを取得してみます。

    • Redis にロックを設定する場合、クライアントはネットワーク接続と応答タイムアウトを設定する必要があります。これは、ロックの有効期限よりも短くする必要があります。

    • ロックが 10 秒で自動的に期限切れになる場合は、タイムアウトを 5 ~ 50 ミリ秒に設定する必要があります。 Redis サーバーがダウンしている場合でも、クライアントが応答結果を際限なく待機することを防ぎます。指定された時間内にサーバーからの応答が受信されない場合、クライアントはできるだけ早く他の Redis インスタンスへの接続を試行する必要があります。

  • #クライアントは、現在時刻からロックの取得開始時刻 (手順 1 で記録された時刻) を引いた時刻を使用して、ロックの取得に使用された時間を取得します。

    • #ロックを正常に取得するための条件は、大部分の Redis ノード (3 ノード) からロックを取得する必要があり、使用時間がロックの有効期限を超えてはいけないことです。時間。

  • ロックが取得された場合、キーの実際の有効時間は、有効時間からロックの取得に使用された時間を引いたものになります (手順 3 で計算された結果)。 。

  • 何らかの理由でロックの取得が失敗した場合 (少なくとも N/2 1 Redis インスタンスでロックが取得されなかった場合、またはロックの取得時間が有効時間を超えた場合)、クライアントはすべての Redis インスタンスのロックを解除する必要があります (一部の Redis インスタンスが正常にロックされていない場合でも)。

Redisson レッド ロック インスタンス

公式 Web サイト

公式 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 インスタンスが閉じられる前にロックの有効期間を継続的に延長することです。デフォルトでは、ウォッチドッグ チェック ロック タイムアウトは 30 秒ですが、Config.lockWatchdogTimeout を変更することで個別に指定することもできます。

Redisson は、leaseTime パラメーターをロックして設定することで、ロック時間を指定することもできます。この時間が経過すると、ロックは自動的に解除されます。

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 Red Lock Principle

RedissonRedLock は RedissonMultiLock を拡張するため、実際には redLock.tryLock が org.redisson.RedissonMultiLock.java#tryLock() を呼び出してから、同様の tryLock (長い waitTime、長いリースタイム、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 レッドロックを使用する原理は何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。