Heim  >  Artikel  >  Datenbank  >  Verschiedene Implementierungsmethoden für verteilte Sperren

Verschiedene Implementierungsmethoden für verteilte Sperren

大家讲道理
大家讲道理Original
2016-11-08 10:08:361848Durchsuche

Derzeit werden fast viele große Websites und Anwendungen verteilt bereitgestellt. Die Frage der Datenkonsistenz in verteilten Szenarien war schon immer ein wichtiges Thema. Die verteilte CAP-Theorie besagt, dass „jedes verteilte System nicht gleichzeitig Konsistenz (Konsistenz), Verfügbarkeit (Verfügbarkeit) und Partitionstoleranz (Partitionstoleranz) erfüllen kann. Daher können viele Systeme nur zwei gleichzeitig erfüllen.“ Zu Beginn des Entwurfs muss eine Auswahl zwischen diesen drei getroffen werden. In den meisten Szenarien im Internetbereich muss eine starke Konsistenz im Austausch für eine hohe Systemverfügbarkeit geopfert werden. Das System muss häufig nur eine „letztendliche Konsistenz“ gewährleisten, solange die endgültige Zeit innerhalb des für den Benutzer akzeptablen Bereichs liegt.

Um die ultimative Datenkonsistenz sicherzustellen, benötigen wir in vielen Szenarien viele technische Lösungen zur Unterstützung, wie z. B. verteilte Transaktionen, verteilte Sperren usw. Manchmal müssen wir sicherstellen, dass eine Methode nur von demselben Thread gleichzeitig ausgeführt werden kann. In einer eigenständigen Umgebung stellt Java tatsächlich viele APIs für die gleichzeitige Verarbeitung bereit, diese APIs sind jedoch in verteilten Szenarien nutzlos. Mit anderen Worten: Die einfache Java-API kann keine verteilten Sperrfunktionen bereitstellen. Daher gibt es derzeit viele Lösungen für die Implementierung verteilter Sperren.

Für die Implementierung verteilter Sperren werden derzeit häufig die folgenden Lösungen verwendet:

Basierend auf der Datenbank zur Implementierung verteilter Sperren Basierend auf Cache (Redis, Memcached, Tair) Zur Implementierung verteilter Sperren basierend über Zookeeper Implementieren verteilter Sperren

Bevor wir diese Implementierungslösungen analysieren, überlegen wir uns zunächst, wie die verteilte Sperre aussehen sollte, die wir benötigen. (Methodensperre wird hier als Beispiel verwendet, Ressourcensperre ist dasselbe)

  • kann sicherstellen, dass in einem Anwendungscluster mit verteilter Bereitstellung dieselbe Methode jeweils nur von einer Maschine verwendet werden kann Gleichzeitig wird ein Thread ausgeführt.

  • Wenn es sich bei dieser Sperre um eine Wiedereintrittssperre handelt (um Deadlocks zu vermeiden)

  • Es ist am besten, wenn diese Sperre eine blockierende Sperre ist (Überlegen Sie, ob Sie Ich möchte dies basierend auf den Geschäftsanforderungen.)

  • Verfügen über hochverfügbare Sperrenerfassungs- und Sperrenfreigabefunktionen

  • Bessere Leistung beim Erwerb von Sperren und Freigabe von Sperren

Verteilte Sperre basierend auf der Datenbank implementieren

Basierend auf der Datenbanktabelle

Um eine verteilte Sperre zu implementieren, kann dies auf einfachste Weise erreicht werden durch direktes Erstellen einer Sperrtabelle und anschließendes Bearbeiten der Daten in der Tabelle.

Wenn wir eine Methode oder Ressource sperren möchten, fügen wir der Tabelle einen Datensatz hinzu und löschen den Datensatz, wenn wir die Sperre aufheben möchten.

Erstellen Sie eine Datenbanktabelle wie folgt:

CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
  `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

Wenn wir eine Methode sperren möchten, führen Sie das folgende SQL aus:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)

Weil wir an method_name interessiert sind Wenn mehrere Anforderungen gleichzeitig an die Datenbank gesendet werden, stellt die Datenbank sicher, dass nur eine Operation erfolgreich sein kann. Dann können wir davon ausgehen, dass der Thread die Sperre der Methode erhalten hat Führen Sie den Inhalt des Methodenkörpers aus.

Nachdem die Methode ausgeführt wurde und Sie die Sperre aufheben möchten, müssen Sie die folgende SQL ausführen:

delete from methodLock where method_name ='method_name'

Die obige einfache Implementierung weist die folgenden Probleme auf:

1. Diese Sperre hängt stark von der Verfügbarkeit der Datenbank ab. Sobald die Datenbank hängt, ist das Geschäftssystem nicht mehr verfügbar.

2. Diese Sperre hat keine Ablaufzeit. Sobald der Entsperrvorgang fehlschlägt, bleibt der Sperrdatensatz in der Datenbank und andere Threads können die Sperre nicht mehr erhalten.

3. Diese Sperre kann nur nicht blockierend sein, denn wenn der Dateneinfügevorgang fehlschlägt, wird direkt ein Fehler gemeldet. Threads, die die Sperre nicht erhalten haben, werden nicht in die Warteschlange aufgenommen. Wenn Sie die Sperre erneut erwerben möchten, müssen Sie den Sperrenerfassungsvorgang erneut auslösen.

4. Diese Sperre ist nicht wiedereintrittsfähig. Derselbe Thread kann die Sperre nicht erneut erhalten, bevor er die Sperre aufhebt. Weil die Daten in den Daten bereits vorhanden sind.

Natürlich können wir die oben genannten Probleme auch auf andere Weise lösen.

Ist die Datenbank ein einzelner Punkt? Erstellen Sie zwei Datenbanken und synchronisieren Sie Daten in beide Richtungen. Sobald dies fehlschlägt, wechseln Sie schnell zur Standby-Datenbank.

Kein Ablaufdatum? Führen Sie einfach eine geplante Aufgabe aus und bereinigen Sie in bestimmten Abständen die Timeout-Daten in der Datenbank.

Nicht blockierend? Erstellen Sie eine While-Schleife, bis die Einfügung erfolgreich ist, und geben Sie dann den Erfolg zurück.

Nicht-Wiedereinsteiger? Fügen Sie der Datenbanktabelle ein Feld hinzu, um die Host- und Thread-Informationen des Computers aufzuzeichnen, der derzeit die Sperre erhält. Fragen Sie dann zuerst die Datenbank ab, ob die Host- und Thread-Informationen des aktuellen Computers möglich sind direkt in der Datenbank gefunden werden. Ordnen Sie ihm einfach die Sperre zu.

Basierend auf einer datenbankexklusiven Sperre

Zusätzlich zum Hinzufügen und Löschen von Datensätzen in der Datentabelle können Sie auch die integrierten Sperren in den Daten verwenden, um verteilte Sperren zu implementieren.

Wir verwenden immer noch die Datenbanktabelle, die wir gerade erstellt haben. Verteilte Sperren können durch exklusive Sperren der Datenbank implementiert werden. Die auf MySql basierende InnoDB-Engine kann die folgenden Methoden zum Implementieren von Sperrvorgängen verwenden:

public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from methodLock where method_name=xxx for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){
        }
        sleep(1000);
    }
    return false;
}

Nach der Abfrageanweisung zur Aktualisierung hinzufügen, und die Datenbank fügt der Datenbanktabelle während des Abfragevorgangs eine exklusive Sperre hinzu. Wenn einem Datensatz eine exklusive Sperre hinzugefügt wird, können andere Threads dem Datensatz keine exklusiven Sperren mehr hinzufügen.

我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁:

public void unlock(){
    connection.commit();
}

通过connection.commit()操作来释放锁。

这种方法可以有效的解决上面提到的无法释放锁和阻塞锁的问题。

阻塞锁? for update语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。

锁定之后服务宕机,无法释放?使用这种方式,服务宕机之后数据库会自己把锁释放掉。

但是还是无法直接解决数据库单点和可重入问题。

总结

总结一下使用数据库来实现分布式锁的方式,这两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。

数据库实现分布式锁的优点

直接借助数据库,容易理解。

数据库实现分布式锁的缺点

会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。

操作数据库需要一定的开销,性能问题需要考虑。

基于缓存实现分布式锁

相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点。而且很多缓存是可以集群部署的,可以解决单点问题。

目前有很多成熟的缓存产品,包括Redis,memcached以及我们公司内部的Tair。

这里以Tair为例来分析下使用缓存实现分布式锁的方案。关于Redis和memcached在网络上有很多相关的文章,并且也有一些成熟的框架及算法可以直接使用。

基于Tair的实现分布式锁在内网中有很多相关文章,其中主要的实现方式是使用TairManager.put方法来实现。

public boolean trylock(String key) {
    ResultCode code = ldbTairManager.put(NAMESPACE, key, "This is a Lock.", 2, 0);
    if (ResultCode.SUCCESS.equals(code))
        return true;
    else
        return false;
}
public boolean unlock(String key) {
    ldbTairManager.invalid(NAMESPACE, key);
}

以上实现方式同样存在几个问题:

1、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在tair中,其他线程无法再获得到锁。

2、这把锁只能是非阻塞的,无论成功还是失败都直接返回。

3、这把锁是非重入的,一个线程获得锁之后,在释放锁之前,无法再次获得该锁,因为使用到的key在tair中已经存在。无法再执行put操作。

当然,同样有方式可以解决。

没有失效时间?tair的put方法支持传入失效时间,到达时间之后数据会自动删除。

非阻塞?while重复执行。

非可重入?在一个线程获取到锁之后,把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。

但是,失效时间我设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。这个问题使用数据库实现分布式锁同样存在

总结

可以使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现分布式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对数据的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。

使用缓存实现分布式锁的优点

性能好,实现起来较为方便。

使用缓存实现分布式锁的缺点

通过超时时间来控制锁的失效时间并不是十分的靠谱。

基于Zookeeper实现分布式锁

基于zookeeper临时有序节点可以实现的分布式锁。

大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

来看下Zookeeper能不能解决前面提到的问题。

锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。

Nicht blockierendes Schloss? Mit Zookeeper können Blockierungssperren erstellt und Listener an die Knoten gebunden werden. Sobald sich der Knoten ändert, kann der Client überprüfen, ob der von ihm erstellte Knoten aktuell ist die kleinste Sequenznummer unter allen Knoten. Wenn dies der Fall ist, haben Sie die Sperre erhalten und können die Geschäftslogik ausführen.

Kein Wiedereintritt? Die Verwendung von Zookeeper kann auch das Problem der Nichtwiedereintrittsfähigkeit effektiv lösen. Wenn der Client einen Knoten erstellt, schreibt er die Hostinformationen und Threadinformationen des aktuellen Clients direkt in den Knoten der derzeit kleinste Knoten. Vergleichen Sie einfach die Daten in . Wenn es mit Ihren eigenen Informationen übereinstimmt, erhalten Sie die Sperre direkt. Wenn es anders ist, erstellen Sie einen temporären Sequenzknoten und nehmen an der Warteschlange teil.

Einzelne Frage? Mit Zookeeper können Einzelpunktprobleme effektiv gelöst werden. Solange mehr als die Hälfte der Maschinen im Cluster überleben, können externe Dienste bereitgestellt werden.

Sie können direkt den Curator-Client der Zookeeper-Drittanbieterbibliothek verwenden, der einen wiedereintrittsfähigen Sperrdienst kapselt.

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
try {
    return interProcessMutex.acquire(timeout, unit);
} Catch (Exception e) {
e.printStackTrace();
}
return true;
}
public boolean unlock() {
try {
} interProcessMutex.release();
} Catch ( Throwable e) {
log.error(e.getMessage(), e);
} schließlich {
executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS);
}
return true;
}

InterProcessMutex von Curator ist die Implementierung einer verteilten Sperre. Der Benutzer der Erwerbsmethode erwirbt die Sperre und die Freigabemethode wird zum Freigeben der Sperre verwendet.

Die mit ZK implementierte verteilte Sperre scheint alle unsere Erwartungen an eine verteilte Sperre zu Beginn dieses Artikels vollständig zu erfüllen. Dies ist jedoch nicht der Fall. Die von Zookeeper implementierte verteilte Sperre weist tatsächlich einen Nachteil auf, nämlich dass die Leistung möglicherweise nicht so hoch ist wie die des Cache-Dienstes. Denn jedes Mal, wenn eine Sperre erstellt und freigegeben wird, müssen vorübergehende Knoten dynamisch erstellt und zerstört werden, um die Sperrfunktion zu implementieren. Das Erstellen und Löschen von Knoten in ZK kann nur über den Leader-Server erfolgen und die Daten werden dann nicht auf allen Follower-Maschinen gemeinsam genutzt.

Zusammenfassung

Vorteile der Verwendung von Zookeeper zur Implementierung verteilter Sperren

Einzelpunktprobleme, nicht wiedereintrittsfähige Probleme, nicht blockierende Probleme und Probleme, bei denen Sperren nicht freigegeben werden können, werden effektiv gelöst. Die Umsetzung ist relativ einfach.

Nachteile der Verwendung von Zookeeper zum Implementieren verteilter Sperren

Die Leistung ist nicht so gut wie die Verwendung des Caches zum Implementieren verteilter Sperren. Sie müssen die Prinzipien von ZK verstehen.

Vergleich von drei Lösungen

Aus Sicht der Verständlichkeit (von niedrig nach hoch)

Datenbank> Cache> Zookeeper

Aus der Implementierung Von eine Komplexitätsperspektive (von niedrig nach hoch)

Zookeeper >= Cache> Datenbank

Aus einer Leistungsperspektive (von hoch nach niedrig)

Cache> Zookeeper >= Datenbank

Aus Sicht der Zuverlässigkeit (von hoch nach niedrig)

Zookeeper > Cache> Datenbank


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn