Sperre ist ein sehr häufiges Werkzeug im Entwicklungsprozess. Sie müssen damit vertraut sein: pessimistische Sperre, optimistische Sperre, exklusive Sperre, faire Sperre, unfaire Sperre usw., wenn Sie es sind Mit Java vertraut Wenn Sie die Sperren noch nicht verstehen, können Sie auf diesen Artikel verweisen: Java-„Sperren“, die erwähnt werden müssen. Für Anfänger ist es jedoch möglicherweise nicht möglich, die Konzepte dieser Sperren zu kennen Aufgrund mangelnder praktischer Berufserfahrung können Sie die drei Schlüsselwörter Volatile, Synchronized und ReentrantLock verwenden, um Thread-Sicherheit in Java zu erreichen erste Runde grundlegender Vorstellungsgespräche (Sie müssen damit vertraut sein).
In einem verteilten System kann die Java-Sperrtechnologie den Code nicht auf zwei Maschinen gleichzeitig sperren, daher muss er durch verteilte Sperren implementiert werden. Auch die kompetente Verwendung verteilter Sperren ist eine Fähigkeit, die von großen Entwicklern beherrscht werden muss.
Problemanalyse: Diese Frage dient hauptsächlich als Einführung. Sie müssen zunächst verstehen, in welchen Szenarien verteilte Sperren verwendet werden müssen und welche Probleme verteilte Sperren lösen müssen verteilte Sperren.
Die Szenarien für die Verwendung verteilter Sperren müssen im Allgemeinen die folgenden Szenarien erfüllen:
Das System ist ein verteiltes System und Java-Sperren können nicht mehr gesperrt werden.
Betreiben Sie gemeinsam genutzte Ressourcen, z. B. die einzigen Benutzerdaten in der Bibliothek.
Synchroner Zugriff, das heißt, mehrere Prozesse betreiben gleichzeitig gemeinsame Ressourcen.
Antwort: Lassen Sie mich Ihnen ein Beispiel für ein Szenario nennen, in dem ich verteilte Sperren in einem Projekt verwende:
Verbrauchspunkte sind in vielen Systemen verfügbar, darunter Kreditkarten, E-Commerce-Websites, Punkte zum Eintauschen von Geschenken usw. Hier ist der Betrieb von „Verbrauchspunkten“. Dies ist ein typisches Szenario, in dem Sperren erforderlich sind.
Am Beispiel der Punkteeinlösung für Geschenke ist der gesamte Prozess des Punkteverbrauchs einfach in 3 Schritte unterteilt:
A1: Der Benutzer wählt das Produkt aus, leitet den Umtausch ein und sendet die Bestellung ab.
A2: Das System liest die verbleibenden Punkte des Benutzers: ermittelt, ob die aktuellen Punkte des Benutzers ausreichen.
A3: Benutzerpunkte werden abgezogen.
Das System verteilt Punkte in drei einfachen Schritten an Benutzer:
B1: Berechnen Sie die Punkte, die der Benutzer für den Tag verdient.
B2: Lesen Sie die ursprünglichen Punkte des Benutzers.
B3: Addieren Sie diese Zeit zum Original Punkte Punkte sammeln
Dann stellt sich die Frage, was passiert, wenn der Benutzer Punkte verbraucht und gleichzeitig Punkte sammelt (Benutzerpunkte werden gleichzeitig betrieben)?
Annahme: Während der Benutzer Punkte verbraucht, werden zufällig Punkte berechnet und Punkte an den Benutzer ausgegeben (z. B. basierend auf der Verbrauchsmenge des Benutzers an diesem Tag). Die folgende Logik ist etwas kompliziert. Seien Sie also geduldig und verstehen Sie sie.
Benutzer U hat 1.000 Punkte (die Datenaufzeichnung der Benutzerpunkte kann als gemeinsam genutzte Ressourcen verstanden werden), und diese Einlösung verbraucht 999 Punkte.
Freigeschaltete Situation: Wenn das Ereignis-A-Programm Schritt 2 zum Lesen von Punkten erreicht, beträgt das in Operation A:2 gelesene Ergebnis 1000 Punkte. Es wird beurteilt, dass die verbleibenden Punkte für diese Einlösung ausreichen, und dann wird Schritt 3 A ausgeführt: Für 3 Operationen werden Punkte abgezogen (1000 - 999 = 1). Das normale Ergebnis sollte immer noch 1 Punkt für den Benutzer sein. Zu diesem Zeitpunkt wird jedoch auch Ereignis B ausgeführt, 100 Punkte werden an Benutzer U ausgegeben. Zwei Threads führen dies gleichzeitig aus (synchroner Zugriff, es besteht die folgende Möglichkeit: A:2 -). > B :2 -> A:3 -> B:3 , bevor A:3 abgeschlossen ist (Punkte abgezogen, 1000 - 999), werden die Gesamtpunkte von Benutzer U vom Thread von Ereignis B gelesen und schließlich von Benutzer U Gesamtpunkte Es wurden 1100 Punkte, und ich habe ein Geschenk von 999 Punkten vergeblich eingelöst, was offensichtlich nicht den erwarteten Ergebnissen entsprach.
Manche Leute fragen sich, wie es möglich ist, Benutzerpunkte so zufällig gleichzeitig zu betreiben, und die CPU so schnell ist. Solange es genügend Benutzer gibt und die Parallelität groß genug ist, wird Murphys Gesetz früher oder später in Kraft treten. Es ist nur eine Frage der Zeit, bis der oben genannte Fehler auftritt. Wenn Sie als Entwickler diese versteckte Gefahr lösen möchten, müssen Sie die Verwendung von Fehlern verstehen entsperren.
(Code schreiben ist eine anspruchsvolle Sache!)
Java selbst bietet zwei integrierte Sperrimplementierungen, eine wird von der JVM synchronisiert und die Sperre wird vom JDK bereitgestellt, und viele atomare Operationsklassen sind Threads sicher, wenn Ihre Anwendung Wenn es sich um eine eigenständige Anwendung oder eine Einzelprozessanwendung handelt, können Sie diese beiden Sperren verwenden, um Sperren zu implementieren.
Aber die meisten aktuellen Internet-Unternehmenssysteme sind derzeit verteilt und die mit Java gelieferten Sperren können die Sperranforderungen in einer verteilten Umgebung nicht mehr erfüllen, da der Code auf mehreren Computern bereitgestellt wird Um dieses Problem zu lösen, sind verteilte Sperren entstanden, die auf mehreren physischen Maschinen basieren. Die gemeinsame Lösung basiert auf verteilten Sperren Auf Redis oder ZooKeeper verteiltes Lock.
(Ich kann es nicht genauer analysieren, warum ist der Interviewer nicht zufrieden?)
Es gibt derzeit zwei Hauptimplementierungsmethoden, um das Problem der verteilten Sperren zu lösen: 1. Einer basiert auf dem Redis-Cluster-Modus und der andere ist ... 2. Basierend auf dem Zookeeper-Clustermodus.
Priorisieren Sie die Beherrschung dieser beiden, dann werden Sie im Grunde keine Probleme haben, das Vorstellungsgespräch zu meistern.
Antwort:
Methode 1: Verwenden Sie den Befehl setnx, um
public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) { // 第一步:加锁 Long result = jedis.setnx(lockKey, requestId); if (result == 1) { // 第二步:设置过期时间 jedis.expire(lockKey, expireTime); } }
Code-Erklärung:
Der Befehl setnx
bedeutet „set“, wenn er nicht vorhanden ist. Wenn das Ergebnis nach erfolgreichem Speichern 1 zurückgibt, bedeutet dies, dass die Einstellung erfolgreich ist . Andere Threads haben es bereits festgelegt. setnx
命令,意思就是 set if not exist,如果lockKey不存在,把key存入Redis,保存成功后如果result返回1,表示设置成功,如果非1,表示失败,别的线程已经设置过了。
expire()
expire()
, legen Sie die Ablaufzeit fest, um einen Deadlock zu verhindern. Gehen Sie davon aus, dass die Sperre einer immer vorhandenen Sperre entspricht, wenn sie nicht gelöscht wird Deadlock. (An dieser Stelle möchte ich dem Interviewer ein „Aber“ sagen) Denken Sie darüber nach: Was sind die Mängel meiner oben genannten Methode? Erklären Sie dem Interviewer weiter ...Das Sperren besteht aus zwei Schritten. Der erste Schritt ist jedis.setnx und der zweite Schritt ist jedis.expire, um die Ablaufzeit festzulegen keine atomare Operation. Wenn das Programm nach Abschluss des ersten Schritts ausgeführt wird, ist eine Ausnahme aufgetreten. Der zweite Schritt jedis.expire(lockKey, ExpireTime) wurde nicht ausgeführt, was bedeutet, dass die Sperre keine Ablaufzeit hat und ein Deadlock auftreten kann. Wie kann dieses Problem verbessert werden? Verbesserung: public class RedisLockDemo { private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean getLock(Jedis jedis, String lockKey, String requestId, int expireTime) { // 两步合二为一,一行代码加锁并设置 + 过期时间。 if (1 == jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)) { return true;//加锁成功 } return false;//加锁失败 } }Code-Erklärung: Kombinieren Sie das Sperren und Festlegen der Ablaufzeit in einer einzigen Codezeile und einer atomaren Operation. (Der Interviewer war sehr zufrieden, bevor er weitere Fragen stellte)3. Was ist mit dem Entriegelungsvorgang? Antwort: Das Aufheben der Sperre bedeutet das Löschen des SchlüsselsZum Entsperren den Befehl „del“ verwenden
public static void unLock(Jedis jedis, String lockKey, String requestId) { // 第一步: 使用 requestId 判断加锁与解锁是不是同一个客户端 if (requestId.equals(jedis.get(lockKey))) { // 第二步: 若在此时,这把锁突然不是这个客户端的,则会误解锁 jedis.del(lockKey); } }Code-Erklärung: Beurteilen nach requestId Ist das Sperren und Entsperren derselbe Client und jedis.del(lockKey)? Theoretisch wird nach Abschluss des ersten Beurteilungsvorgangs angezeigt, dass die Sperre tatsächlich abgelaufen ist und erworben wurde Bei anderen Threads ist dies gleichbedeutend mit dem Aufheben der Sperre einer anderen Person, was unvernünftig ist. Dies ist natürlich eine sehr extreme Situation. Wenn im ersten und zweiten Schritt der UnLock-Methode keine anderen Geschäftsvorgänge vorhanden sind, kann es sein, dass das Online-Schalten des oben genannten Codes keine wirklichen Probleme verursacht nicht hoch, dieser Fehler wird überhaupt nicht aufgedeckt, daher ist das Problem nicht groß. Aber das Schreiben von Code ist eine harte Arbeit, und um perfekt zu sein, muss man perfekt sein. Es werden Verbesserungen vorgeschlagen, um die Probleme im obigen Code zu beheben. Code-Verbesserung:
public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }Code-Erklärung: Verwenden Sie die Eval-Methode des Jedis-Clients und nur eine Zeile Skript, um Methode eins Atomic zu lösen damit verbundene Probleme. 3. Interviewer: Prinzip der verteilten Sperre basierend auf ZooKeeper Antwort: Noch ein Beispiel für Punktverbrauch und Punkteakkumulation: Ereignis A Der Punktänderungsvorgang muss gleichzeitig mit Ereignis B ausgeführt werden. Die beiden Maschinen führen ihn gleichzeitig aus. Die richtige Geschäftslogik besteht darin, ihn zuerst und dann von der anderen Maschine ausführen zu lassen Ereignis B wird zuerst ausgeführt. Nur so können wir sicherstellen, dass A:2 -> Diese Art von Fehler geht online, der Chef wird wütend sein, ich werde vielleicht weinen). Was tun? Verwenden Sie verteilte Zookeeper-Sperren. Nachdem eine Maschine die Anfrage erhalten hat, erhält sie zunächst eine verteilte Sperre für Zookeeper (zk erstellt einen Znode) und führt den Vorgang aus. Dann versucht eine andere Maschine ebenfalls, diesen Znode zu erstellen, und stellt fest, dass dies nicht möglich ist erstellt werden, weil es von jemand anderem erstellt wurde. Sie können nur warten, bis die erste Maschine die Ausführung abgeschlossen hat, bevor Sie die Sperre erhalten können. Wenn wir mit der sequentiellen Knotenfunktion von ZooKeeper 3 Knoten im Verzeichnis /lock/ erstellen, erstellt der ZK-Cluster die Knoten in der Reihenfolge, in der sie erstellt wurden. Die Knoten sind in / unterteilt. lock/0000000001, /lock /0000000002, /lock/0000000003, die letzte Ziffer wird der Reihe nach erhöht und der Knotenname wird durch zk vervollständigt. Es gibt auch einen Knoten, der als temporärer Knoten bezeichnet wird. Der temporäre Knoten wird von einem Client erstellt. Wenn der Client die Verbindung zum ZK-Cluster trennt, wird der Knoten automatisch gelöscht. EPHEMERAL_SEQUENTIAL ist ein temporärer Sequenzknoten. Die Grundlogik der verteilten Sperre besteht darin, das Vorhandensein oder Fehlen von Knoten in ZK als Sperrstatus zu verwenden, um die verteilte Sperre zu implementieren
利用 Mysql 的锁表,创建一张表,设置一个 UNIQUE KEY 这个 KEY 就是要锁的 KEY,所以同一个 KEY 在mysql表里只能插入一次了,这样对锁的竞争就交给了数据库,处理同一个 KEY 数据库保证了只有一个节点能插入成功,其他节点都会插入失败。
这样 lock 和 unlock 的思路就很简单了,伪代码:
def lock : exec sql: insert into locked—table (xxx) values (xxx) if result == true : return true else : return false def unlock : exec sql: delete from lockedOrder where order_id='order_id'
使用流水号+时间戳做幂等操作,可以看作是一个不会释放的锁。
Das obige ist der detaillierte Inhalt vonWie implementiert man die verteilte Redis-Sperre und welche Anwendungsszenarien gibt es?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!