Implementierung verteilter Sperren basierend auf Zookeeper

Vorwort

In verteilten Systemen ist die Gewährleistung der Datenkonsistenz und die Vermeidung von Konflikten ein Kernproblem, das normalerweise durch verteilte Sperren gelöst wird. Verteilte Sperren sind im Wesentlichen ein Synchronisationsmechanismus, der zur Steuerung des Zugriffs auf gemeinsam genutzte Ressourcen oder kritische Abschnitte verwendet wird.

Als verteilter Koordinationsdienst stellt Zookeeper eine effektive Plattform für die Implementierung verteilter Sperren bereit. In diesem Artikel wird anhand eines einfachen Beispiels vorgestellt, wie verteilte Sperren basierend auf den von Zookeeper bereitgestellten Schnittstellen und Mechanismen implementiert werden.

Stellungnahme

Der im Artikel bereitgestellte Code dient nur als Referenz. Er soll Entwicklern eine praktische Methode zur Implementierung verteilter Sperren bieten und den Lesern helfen, zu verstehen, wie die Funktionen und Mechanismen von Zookeeper zum Verwalten von Sperren in verteilten Systemen verwendet werden. Bitte beachten Sie, dass diese Codes nicht für den Einsatz in realen Anwendungen gedacht sind .

vorausgesetzte Kenntnisse

Prinzipien des verteilten Schlossdesigns

Die Implementierung einer verteilten Sperre muss die folgenden Grundvoraussetzungen erfüllen:

  1. Gegenseitige Ausschließlichkeit/Exklusivität : Nur ein Kunde darf gleichzeitig die Sperre halten.
  2. Verfügbarkeit : Wenn auf dem Client eine Ausnahme auftritt, kann die Sperre normal aufgehoben werden, um einen Deadlock zu vermeiden.
  3. Homologie : Die Sperre kann nicht von anderen Threads aufgehoben werden, da sonst der gegenseitige Ausschluss/die Exklusivität zerstört wird.
  4. Wiedereintritt : Derselbe Client kann die Sperre wiederholt und rekursiv ohne Deadlock aufrufen.

Darüber hinaus muss auch berücksichtigt werden, ob der Client blockiert und wartet, bevor er die Sperre erhält, oder ob dies als Erwerbsfehler angesehen wird, was vom Geschäftsszenario abhängt.

Tierpfleger

Zookeeper ist ein traditioneller verteilter Koordinierungsdienst, der eher als Koordinator eingesetzt wird, beispielsweise zur Koordinierung und Verwaltung von Hadoop-Clustern, zur Koordinierung der Wahl des Kafka-Führers usw.

Welche Funktionen und Mechanismen von Zookeeper können verteilte Sperranforderungen effizient umsetzen?

  1. Temporärer Knoten : Der Lebenszyklus eines temporären Knotens hängt von der Sitzung ab, in der er erstellt wurde. Wenn die Sitzung endet, wird der temporäre Knoten gelöscht. Diese Funktion kann die Verfügbarkeit verteilter Sperren erfüllen.
  2. Sequentielle Knoten : Beim Erstellen eines sequentiellen Knotens weist Zookeeper einen inkrementellen Zähler zu, und der oberste Knoten kann die Sperre erwerben. Diese Funktion ermöglicht eine faire Sperrung. (Kein grundlegender Entwickler kann das Erstellen eines Knotens auf diese Weise verstehen: Durch das Erstellen eines Knotens im selben Verzeichnis wird eine Sperre erworben.)
  3. Watcher-Mechanismus : Durch den Watcher-Mechanismus kann der aktuelle Knoten die Änderungen des vorherigen Knotens überwachen. Wenn der vorherige Knoten gelöscht wird, kann der aktuelle Knoten erkennen, dass die Sperre aufgehoben wurde, und dadurch die Sperre erwerben.
  4. Knotendaten : Legen Sie beim Erstellen eines Knotens die eindeutige Kennung der Clientsitzung als Wert fest, um Wiedereintritt zu erreichen.

Gegenseitige Exklusivität/Exklusivität und Homologie müssen vom Client kontrolliert werden, was im Codebeispiel erläutert wird.

Implementierung einer verteilten Sperre

Erstellen Sie ein Maven-Projekt, importieren Sie die zkclient-Abhängigkeit und beginnen Sie mit dem Codieren

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

Der folgende Beispielcode erfüllt die Grundanforderungen verteilter Sperren und ist eine blockierende verteilte Sperre, dh der Client blockiert und wartet, bis die Sperre erhalten wird.

public class DistributedLock {
    
    

    private ZooKeeper client;

    // 连接信息
    private String connectString = "127.0.0.1:2181";

    // 超时时间
    private int sessionTimeOut = 30000;

    // 等待zk连接成功
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    // 等待节点变化
    private CountDownLatch waitLatch = new CountDownLatch(1);

    //当前节点
    private String currentNode;

    //前一个节点路径
    private String waitPath;

    private final String ROOT_PATH = "/locks";

    //1. 在构造方法中获取连接
    public DistributedLock() throws Exception {
    
    
        client = new ZooKeeper(connectString, sessionTimeOut, watchedEvent -> {
    
    
            //  连上ZK,可以释放
            if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
    
    
                countDownLatch.countDown();
            }

            //waitLatch 需要释放 (节点被删除并且删除的是前一个节点)
            if (watchedEvent.getType() == Watcher.Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {
    
    
                waitLatch.countDown();
            }
        });

        //等待Zookeeper连接成功,连接完成继续往下走
        countDownLatch.await();
        //2. 判断节点是否存在
        Stat stat = client.exists(ROOT_PATH, false);
        if (stat == null) {
    
    
            //创建一下根节点
            client.create(ROOT_PATH, ROOT_PATH.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);


        }

    }

    //3.对ZK加锁
    public boolean zkLock() {
    
    

        try {
    
    
            String sessionId = String.valueOf(client.getSessionId());
            List<String> children = client.getChildren(ROOT_PATH, false);
            if (!children.isEmpty()) {
    
    
                Collections.sort(children);
                String path = children.get(0);
                byte[] data = client.getData(ROOT_PATH + "/" + path, false, null);
                //最小序号节点是当前客户端创建的不用再次获取
                if (sessionId.equals(new String(data))) {
    
    
                    System.out.println("重入锁");
                    return true;
                }
            }

            //创建 临时带序号节点,将当前客户端id作为值,实现可重入
            currentNode = client.create(ROOT_PATH + "/seq-", sessionId.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            children = client.getChildren(ROOT_PATH, false);
            //如果创建的节点只有一个值,就直接获取到锁,如果不是,监听它前一个节点
            if (children.size() == 1) {
    
    
                return false;
            } else {
    
    
                //先排序
                Collections.sort(children);

                //获取节点名称
                String nodeName = currentNode.substring((ROOT_PATH + "/").length());

                //通过名称获取该节点在集合的位置
                int index = children.indexOf(nodeName);

                if (index == -1) {
    
    
                    System.out.println("数据异常,nodeName:" + nodeName);
                    return false;
                } else if (index == 0) {
    
    
                    //创建的节点是否是最小序号节点,如果是 就获取到锁;如果不是就监听前一个节点
                    return true;
                } else {
    
    
                    //需要监听前一个节点变化
                    waitPath = ROOT_PATH + "/" + children.get(index - 1);
                    client.getData(waitPath, true, null);

                    //等待监听执行
                    waitLatch.await();
                    return true;
                }
            }

        } catch (KeeperException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return false;
    }

    public void unZkLock() throws KeeperException, InterruptedException {
    
    
        //删除节点
        client.delete(currentNode, -1);
    }


}

Im Beispiel wird ein Stammknoten mit dem Namen /locksals Kennung der Sperre erstellt. Er wird aufgerufen, wenn der Client die Sperre erwerben muss zkLock(). Diese Methode ermittelt zunächst, ob der aktuelle Client bereits über die Sperre verfügt. Wenn dies der Fall ist, wird dies bei keinem Knoten der Fall sein erstellt werden (hier ist die Implementierung, die Reentrancy kann), andernfalls /lockswird ein temporärer Sequenzknoten am Wurzelknoten erstellt. Dies ist der Fall, wenn mehrere Clients gleichzeitig das Sperrknotenverzeichnis erwerben.

├── locks
│   └── seq-0000000006
│   └── seq-0000000005
│   └── seq-0000000004
│   └── seq-0000000003
│   └── seq-0000000002
│   └── seq-0000000001

Wenn der vom Client erstellte Knoten der kleinste Knoten ist, wird der Sperrverarbeitungsdienst erfolgreich abgerufen. Andernfalls wird der vorherige Knoten überwacht, blockiert und gewartet. Wenn der vorherige Knoten gelöscht wird, wird der Client benachrichtigt, die Sperre zu erhalten.

Wenn der Client aufruft, unZkLock()löschen Sie den von ihm erstellten Knoten, um die Sperre aufzuheben. Da der von ihm erstellte Knoten gelöscht wird, ist die Homologie natürlich erfüllt.

Der gesamte Prozess und die Interaktion sind wie unten dargestellt

Fügen Sie hier eine Bildbeschreibung ein

Hierbei ist zu beachten, dass Zookeeper den Client nacheinander in der Reihenfolge der Knoten benachrichtigt, wenn sich ein Knoten ändert. Wenn das Diagramm aufgrund eines Fehlers zuerst gelöscht wird, muss daher auch auf die seq-0000000002Löschung /seq-0000000003gewartet /seq-0000000001werden Bevor Sie seq-0000000002die Löschbenachrichtigung erhalten, hören Sie einfach zu, wie der vorherige Knoten gelöscht wird.

Nachdem der obige Code geschrieben wurde, können Sie ihn direkt überall dort aufrufen, wo Sie verteilte Sperren verwenden müssen. Der Code lautet wie folgt:

public static void main(String[] args) {
    
    
    try {
    
    
        DistributedLock lock = new DistributedLock();
        if (lock.zkLock()) {
    
    
            System.out.println(Thread.currentThread() + "获取到锁");
            Thread.sleep(20 * 1000);
            lock.unZkLock();
            System.out.println(Thread.currentThread() + "释放锁");
        }

    } catch (InterruptedException | KeeperException e) {
    
    
        e.printStackTrace();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

Das Curator-Framework implementiert verteilte Sperren

Curator ist ein gekapseltes Client-Framework, das auf der nativen API-Schnittstelle von Zookeeper basiert. Es löst die Entwicklungsprobleme der zugrunde liegenden Details und stellt eine Reihe von APIs auf hoher Ebene bereit, um Funktionen wie verteilte Sperrdienste, Wahl des Clusterleiters, gemeinsame Zähler, Caching-Mechanismen usw. zu implementieren. verteilte Warteschlangen usw. Verschiedene Anwendungsszenarien.

Die Verwendung von Curator zum Implementieren verteilter Sperren kann das Schreiben von Code erheblich vereinfachen. Sie müssen lediglich relevante Abhängigkeiten einführen und die gekapselte Schnittstelle direkt aufrufen. Das Prinzip ähnelt der oben beschriebenen verteilten Sperrimplementierung. Code wie folgt anzeigen:

Kuratorbezogene Abhängigkeiten

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.0</version>
</dependency>
public static void main(String[] args) {
    
    
        CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", (i, l, retrySleeper) -> false);
        client.start();
        InterProcessMutex lock = new InterProcessMutex(client, "/locks");
        try {
    
    
            // 获取互斥锁
            lock.acquire();

            // 执行需要互斥访问的代码
            // 释放互斥锁
            lock.release();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 关闭Curator Framework客户端
            client.close();
        }
}

InterProcessMutexEs handelt sich um eine Implementierung der von Curator bereitgestellten verteilten Sperre. Sie InterProcessMutexkann den gegenseitig ausschließenden Zugriff auf gemeinsam genutzte Ressourcen zwischen mehreren Prozessen sicherstellen und so Datenkonflikte und Parallelitätsprobleme vermeiden.

Zusammenfassen

Die Funktionen von Zookeeper bieten eine große Hilfe bei der Implementierung verteilter Sperren, und seine hohe Verfügbarkeit und starke Konsistenz machen verteilte Sperren zuverlässiger und effizienter. In diesem Artikel werden zwei verteilte Lösungen basierend auf Zookeeper vorgestellt. Unabhängig davon, ob die Zookeeper-API oder die Curator-API verwendet wird, sind die Prinzipien dieselben. Daher können wir diese ausgereiften Frameworks direkt nutzen, wenn wir ihre Prinzipien verstehen.

Supongo que te gusta

Origin blog.csdn.net/qq_28314431/article/details/132893863
Recomendado
Clasificación