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;//加锁失败 } }
public static void unLock(Jedis jedis, String lockKey, String requestId) { // 第一步: 使用 requestId 判断加锁与解锁是不是同一个客户端 if (requestId.equals(jedis.get(lockKey))) { // 第二步: 若在此时,这把锁突然不是这个客户端的,则会误解锁 jedis.del(lockKey); } }
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; } }
利用 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!