Implementieren einer verteilten Sperre mit MySQL

Implementieren einer verteilten Sperre mit MySQL

einführen

In einem verteilten System ist die verteilte Sperre die grundlegendste Werkzeugklasse. Wenn beispielsweise zwei Mikrodienste mit Zahlungsfunktionen bereitgestellt werden, kann ein Benutzer zwei Zahlungsvorgänge für eine Bestellung initiieren und diese beiden Anforderungen können an zwei Dienste gesendet werden. Daher muss eine verteilte Sperre verwendet werden, um doppelte Übermittlungen zu verhindern. Der Dienst, der die Sperre erhält, führt den Zahlungsvorgang normal aus, und der Dienst, der die Sperre nicht erhält, fordert zu doppelten Vorgängen auf.

Unser Unternehmen hat eine große Anzahl grundlegender Werkzeugklassen gekapselt. Wenn wir verteilte Sperren verwenden möchten, müssen wir nur drei Dinge tun:

1. Erstellen Sie eine Globallocktable-Tabelle in der Datenbank
2. Führen Sie das entsprechende JAR-Paket ein
3. Schreiben Sie @Autowired GlobalLockComponent globalLockComponent in den Code, um diese Komponente zu verwenden

Nachdem Sie diesen Artikel gelesen haben, können Sie auch Springboot-Starter verwenden, um dieselbe Funktion zu erreichen. Aber wir haben es nicht so umgesetzt. Wir werden einen weiteren Artikel schreiben, um zu analysieren, wie wir es umgesetzt haben.

Dieser Artikel analysiert zunächst die Implementierung der MySQL-Verteilung

Erstellen einer Tabelle

TABELLE „globallocktable“ erstellen (
 `id` int(11) NICHT NULL AUTO_INCREMENT,
 `lockKey` varchar(60) NOT NULL COMMENT 'Sperrname',
 `createTime` datetime NICHT NULL KOMMENTAR 'Erstellungszeit',
 Primärschlüssel (`id`),
 EINZIGARTIGER SCHLÜSSEL `lockKey` (`lockKey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Globale Sperre';

Komponenten zur Verwendung durch andere

@Komponente
öffentliche Klasse GlobalLockComponent {
 @Ressource
 GlobalLockTableDAO globalLockDAO;
 /**
  * Versuchen Sie, die Sperre zu erhalten. „true“ bei Erfolg, „false“ bei Fehlschlag
  */
 öffentliches Boolean tryLock(String-Schlüssel) {
  gibt GlobalLockUtil.tryLock(this.globalLockDAO, Schlüssel) zurück;
 }
 /**
  * Wenn ein anderes Programm die Sperre belegt hat und das Timeout (Millisekunden) überschritten wird, wird die Sperre zwangsweise aufgehoben. * Das heißt, zuerst den Datensatz entsprechend dem Schlüssel löschen und dann den Datensatz hinzufügen */
 öffentliche boolean tryLockWithClear(String-Schlüssel, Long timeoutMs) {
  gibt GlobalLockUtil.tryLockWithClear zurück (this.globalLockDAO, Schlüssel, TimeoutMs);
 }
 /**
  * Sperre aufheben und Datensatz laut Schlüssel löschen */
 public void releaseLock(String-Schlüssel) {
  GlobalLockUtil.releasLock(this.globalLockDAO, Schlüssel);
 }
}

Das Sperrobjekt wird wie folgt definiert

öffentliche Klasse GlobalLockTable {

 private Integer-ID;
 private Zeichenfolge „lockKey“;
 privates Datum, Erstellungszeit;
 // Get- und Set-Methoden weglassen}

GlobalLockTableDAO ist wie folgt definiert: öffentliche Schnittstelle GlobalLockTableDAO {
 int deleteByPrimaryKey(Integer-ID);
 : In diesem Beispiel wird die Zeichenfolge deleteByLockKey als Schlüsselwert für die Anweisung deleteByLockKey verwendet.
 : GlobalLockTable selectByLockKey (String-Schlüssel);
 int insertSelectiveWithTest(GlobalLockTable-Datensatz);
}

Spezifische Sperr- und Entsperrlogik

öffentliche Klasse GlobalLockUtil {
 privater statischer Logger logger = LoggerFactory.getLogger(GlobalLockUtil.class);
 private static GlobalLockTable tryLockInternal(GlobalLockTableDAO lockDAO, String key) {
  GlobalLockTable einfügen = neue GlobalLockTable();
  einfügen.setCreateTime(neues Datum());
  einfügen.setLockKey(Schlüssel);
  // Anmerkung 1
  int count = lockDAO.insertSelectiveWithTest(einfügen);
  wenn (Anzahl == 0) {
   GlobalLockTable bereit = lockDAO.selectByLockKey(Schlüssel);
   logger.warn("kann den Schlüssel nicht sperren: {}, {}, {}", insert.getLockKey(), ready.getCreateTime(),
     bereit.getId());
   Rückkehr bereit;
  }
  logger.info("ja, habe das Schloss mit dem Schlüssel erhalten: {}", insert.getId(), insert.getLockKey());
  gibt null zurück;
 }
 /** Zeitüberschreitung zum Aufheben der Sperre und erneuten Sperren**/
 öffentliche statische Boolesche tryLockWithClear (GlobalLockTableDAO lockDAO, String-Schlüssel, Long timeoutMs) {
  GlobalLockTable-Sperre = tryLockInternal (lockDAO, Schlüssel);
  wenn (Sperre == null) true zurückgeben;
  wenn (System.currentTimeMillis() - lock.getCreateTime().getTime() <= timeoutMs) {
   logger.warn("Entschuldigung, kann den Schlüssel nicht abrufen. : {}, {}, {}", key, lock.getId(), lock.getCreateTime());
   gibt false zurück;
  }
  logger.warn("für den Schlüssel ist bereits vorher eine Zeitüberschreitung aufgetreten: {}, {}, wird gelöscht", key, timeoutMs);
  // Anmerkung 2
  : In diesem Fall ist lockDAO.deleteByPrimaryKey die einzige Funktion, die lock.getId() verwendet.
  wenn (Anzahl == 0) {
   logger.warn("Entschuldigung, der Schlüssel wurde bereits von anderen vorweggenommen: {}, {}", lock.getId(), lock.getLockKey());
   gibt false zurück;
  }
  Sperre = tryLockInternal(lockDAO, Schlüssel);
  Sperre zurückgeben != null? false : true;
 }
 /** Sperren **/
 öffentliche statische Boolesche tryLock (GlobalLockTableDAO lockDAO, String-Schlüssel) {
  returniere tryLockInternal(lockDAO, key) == null ? true : false;
 }
 /** Entsperren **/
 öffentliche statische Leere freigebenLock (GlobalLockTableDAO lockDAO, String-Schlüssel) {
  lockDAO.deleteByLockKey(Schlüssel);
 }
}

An dieser Tool-Klasse sind zwei Dinge besonders interessant. Schauen wir uns zunächst Punkt 2 an (im obigen Code markiert)

1. Um zu vermeiden, dass die Sperre längere Zeit nicht freigegeben wird, können Sie bei der Implementierung mit Redis ein Sperrtimeout festlegen. Nach Ablauf des Timeouts wird die Sperre automatisch freigegeben (ich werde später darüber schreiben, wie verteilte Sperren mit Redis implementiert werden). Wenn es mit MySQL implementiert wird, können Sie es zuerst löschen und dann hinzufügen. Man sieht, dass beim Löschen die ID und nicht der Name zum Löschen verwendet wird. Warum? Denken Sie zuerst darüber nach

Denn wenn Sie es namentlich löschen, ist es möglich, dass jemand anderes die Sperre gelöscht und dann vor Ablauf der Zeitüberschreitung eine namentliche Sperre hinzugefügt hat, Sie diese jedoch namentlich gelöscht haben. Wenn Sie nach ID löschen und die zurückgegebene ID = 0 ist, bedeutet dies, dass jemand anderes es erneut gesperrt hat und Sie es erneut abrufen müssen.

2. Die anderen Methoden der GlobalLockTable-Objekt-Dao-Ebene sind selbsterklärend. Schauen wir uns diese Methode einmal an. Das heißt, Anmerkung 1 im Code
Sie können sehen, dass Sie bei jedem Sperrversuch nicht zuerst auswählen, sondern direkt SelectiveWithTest einfügen, was Abfragezeit spart und die Effizienz verbessert.

Die Funktion von insertSelectiveWithTest besteht darin, den Einfügevorgang nicht auszuführen, wenn lockKey vorhanden ist, und 0 zurückzugeben. Wenn der Sperrschlüssel nicht existiert, führen Sie eine Einfügeoperation aus und geben Sie 1 zurück.

<insert id="insertSelectiveWithTest" useGeneratedKeys="true" keyProperty="id" parameterType="com.javashitang.middleware.lock.mysql.pojo.GlobalLockTable">
 einfügen in `globallocktable` (`id`,
 `Schlüsselsperre`, `Zeiterstellung` )
  wählen Sie #{id,jdbcType=INTEGER}, #{lockKey,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}
  von dual, wo nicht existiert
  (Wählen Sie 1 aus der Globallock-Tabelle, wobei LockKey = #{lockKey,jdbcType=VARCHAR})
</einfügen>

verwenden

Wenn wir es verwenden möchten, müssen wir nur die Geschäftslogik schreiben, was sehr praktisch ist.

wenn (!globalLockComponent.tryLock(name)) {
 //Zurück, wenn die Sperre nicht erworben wurde;
}
versuchen {
 // Schreiben Sie hier die Geschäftslogik} catch (Exception e) {
Endlich
 globalLockComponent.releasLock(Name)

Zusammenfassen

Das Obige ist die Einführung des Herausgebers in die Verwendung von MySQL zum Implementieren einer verteilten Sperre. Ich hoffe, es wird für alle hilfreich sein!

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung der Idee der verteilten Sperre in MySQL mit Hilfe von DB

<<:  JavaScript-Wissen: Konstruktoren sind auch Funktionen

>>:  So kopieren Sie schnell große Dateien unter Linux

Artikel empfehlen

So testen Sie die maximale Anzahl von TCP-Verbindungen in Linux

Vorwort Es besteht ein Missverständnis bezüglich ...

Vue implementiert das Senden von Emoticons im Chatfenster

Der spezifische Code zum Senden von Emoticons im ...

So kapseln Sie die Rich-Text-Komponente von WangEditor in Angular

Die Rich-Text-Komponente ist eine sehr häufig ver...

Lösung für den erfolgreichen Start von MySQL, aber ohne Überwachung des Ports

Problembeschreibung MySQL wurde erfolgreich gesta...

MySQL-Transaktionsdetails

Inhaltsverzeichnis Einführung Vier Merkmale von T...

Tiefgreifendes Verständnis des Javascript-Klassenarrays

js-Arrays sind wahrscheinlich jedem bekannt, da s...

HTML Mehrere spezielle Trennlinieneffekte

1. Grundlinien 2. Spezialeffekte (die Effekte sin...

Vue verwendet Drag & Drop, um einen Strukturbaum zu erstellen

In diesem Artikelbeispiel wird der spezifische Co...