Das Problem der verteilten Sperre mit Master-Slave-Struktur
Der einfachste Weg, eine verteilte Sperre von Redis zu implementieren, besteht darin, einen Schlüssel in Redis zu erstellen. Es ist gewährleistet, dass die Sperre irgendwann automatisch aufgehoben wird. Wenn der Client die Ressource freigibt (entsperrt), wird der Schlüssel gelöscht.
Oberflächlich betrachtet scheint es gut zu funktionieren, aber es gibt ein ernstes Single-Point-of-Failure-Problem: Was passiert, wenn Redis hängt? Man könnte sagen, dass dieses Problem durch das Hinzufügen eines Slave-Knotens gelöst werden kann. Aber das funktioniert normalerweise nicht. Die Master-Slave-Synchronisation von Redis ist normalerweise asynchron, sodass keine ausschließliche Nutzung der Ressourcen erreicht werden kann.
In diesem Szenario (Master-Slave-Struktur) liegt eine offensichtliche Race-Bedingung vor:
Client A erwirbt die Sperre vom Master
Bevor der Master die Sperre mit dem Slave synchronisiert, fällt der Master aus.
Der Slave-Knoten wird zum Master-Knoten befördert
Client B erhält die Sperre vom neuen Master
Die dieser Sperre entsprechende Ressource wurde zuvor von Client A erworben. Sicherheitsversagen!
Manchmal hat das Programm so viel Glück. Wenn beispielsweise ein Knoten auflegt, erhalten mehrere Clients gleichzeitig die Sperre. Solange Sie diese geringe Fehlerwahrscheinlichkeit tolerieren können, ist die Einführung dieser replikationsbasierten Lösung kein Problem. Ansonsten empfehlen wir Ihnen, die unten beschriebene Lösung umzusetzen.
Einführung
Redis hat für diese Situation das Konzept des Red Lock eingeführt. In einem Red-Lock-System ist das erfolgreiche Zeichen für den Erwerb oder die Freigabe einer Sperre, dass der Vorgang auf mehr als der Hälfte der Knoten erfolgreich ist.
Prinzip
Angenommen, es gibt N Redis-Master in der verteilten Umgebung von Redis. Diese Knoten sind völlig unabhängig voneinander und es gibt keine Master-Slave-Replikation oder andere Cluster-Koordinationsmechanismen. Wir haben zuvor erklärt, wie Sie Sperren in einer einzelnen Instanz von Redis sicher erwerben und freigeben. Wir stellen sicher, dass die Sperre mit dieser Methode auf jeder (N) Instanz erworben und freigegeben wird. In diesem Beispiel gehen wir davon aus, dass es 5 Redis-Masterknoten gibt. Dies ist eine sinnvollere Einstellung, daher müssen wir diese Instanzen auf 5 Maschinen oder 5 virtuellen Maschinen ausführen, um sicherzustellen, dass sie nicht alle gleichzeitig ausfallen.
Um die Sperre zu erhalten, sollte der Client die folgenden Vorgänge ausführen:
Ermitteln Sie die aktuelle Unix-Zeit in Millisekunden.
Versuchen Sie, Sperren von N Instanzen nacheinander zu erhalten, indem Sie denselben Schlüssel und Zufallswert verwenden.
Beim Setzen einer Sperre für Redis sollte der Client ein Netzwerkverbindungs- und Antwort-Timeout festlegen, das kürzer als die Ablaufzeit der Sperre sein sollte.
Wenn Ihre Sperre automatisch in 10 Sekunden abläuft, sollte das Timeout zwischen 5 und 50 Millisekunden eingestellt werden. Verhindern Sie, dass der Client endlos auf Antwortergebnisse wartet, selbst wenn der Redis-Server ausgefallen ist. Wenn innerhalb der angegebenen Zeit keine Antwort vom Server eingeht, sollte der Client versuchen, so schnell wie möglich eine Verbindung zu anderen Redis-Instanzen herzustellen.
Der Client verwendet die aktuelle Zeit abzüglich der Startzeit zum Erwerb der Sperre (die in Schritt 1 aufgezeichnete Zeit), um die zum Erwerb der Sperre verwendete Zeit zu erhalten.
Die Voraussetzung für den erfolgreichen Erwerb der Sperre ist, dass die Sperre von der Mehrheit der Redis-Knoten (3 Knoten) erworben werden muss und die Nutzungsdauer die Ablaufzeit der Sperre nicht überschreiten darf.
Wenn das Schloss erworben wird, ist die tatsächliche Gültigkeitszeit des Schlüssels gleich der Gültigkeitszeit minus der Zeit, die für den Erwerb des Schlosses aufgewendet wurde (das in Schritt 3 berechnete Ergebnis).
Wenn die Sperrenerfassung aus irgendeinem Grund fehlschlägt (die Sperre wird nicht auf mindestens N/2+1 Redis-Instanzen erworben oder die Sperrenerfassungszeit hat die effektive Zeit überschritten), sollte der Client die Sperre auf allen Redis-Instanzen entsperren ( Auch wenn einige Redis-Instanzen überhaupt nicht erfolgreich gesperrt werden).
Offizielle Website
Offizieller Github: 8. Distributed Lock und Synchronizer · Redisson/Redisson Wik
Das auf Redis basierende Redisson Red Lock RedissonRedLock-Objekt implementiert den von Redlock eingeführten Sperralgorithmus. Dieses Objekt kann auch verwendet werden, um mehrere RLock-Objekte als rote Sperre zuzuordnen. Jede RLock-Objektinstanz kann von einer anderen Redisson-Instanz stammen.
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();
Jeder weiß, dass diese Sperren gesperrt werden, wenn einige Redis-Knoten, die für die Speicherung bestimmter verteilter Sperren verantwortlich sind, ausfallen und diese Sperren zufällig gesperrt sind. Um diese Situation zu vermeiden, stellt Redisson intern einen Watchdog bereit, der die Sperre überwacht. Seine Funktion besteht darin, die Gültigkeitsdauer der Sperre kontinuierlich zu verlängern, bevor die Redisson-Instanz geschlossen wird. Standardmäßig beträgt das Zeitlimit für die Watchdog-Überprüfung der Sperre 30 Sekunden, was auch separat durch Ändern von Config.lockWatchdogTimeout angegeben werden kann.
Redisson kann die Sperrzeit auch durch Sperren und Festlegen des LeaseTime-Parameters angeben. Nach Ablauf dieser Zeit wird das Schloss automatisch entriegelt.
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();
RedissonRedLock erweitert RedissonMultiLock, sodass redLock.tryLock tatsächlich Folgendes aufruft: org.redisson.RedissonMultiLock.java#tryLock() und dann seine ähnliche tryLock(long waitTime, long leaseTime, TimeUnit-Einheit) aufruft ), der Eingabeparameter ist: 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; }
Das obige ist der detaillierte Inhalt vonWas ist das Prinzip der Verwendung von Redisson Red Lock in Redis?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!