Der Kurator realisiert eine verteilte Sperre (reentrant non-reentrant read-write interlocking semaphor fence counter)

Vorwort

Curatorzookeeper ist eine Reihe von Open-Source- Clients von netflix und ist derzeit das Top-Level-Projekt von Apache. Verglichen mit dem von Zookeeper bereitgestellten nativen Client hat Curator eine höhere Abstraktionsebene, was die Entwicklung des Zookeeper-Clients vereinfacht . Curator löst viele Details auf sehr niedriger Ebene der Zookeeper-Client-Entwicklung, einschließlich Verbindungswiederverbindung, wiederholte Registrierung von Watchcer- und NodeExistsException-Ausnahmen usw.

Curator löst hauptsächlich drei Arten von Problemen:

  • Kapseln Sie die Verbindungsverarbeitung zwischen dem ZooKeeper-Client und dem ZooKeeper-Server
  • Stellt eine Reihe von Betriebs-APIs im Fluent-Stil bereit
  • Bieten eine abstrakte Kapselung der verschiedenen Anwendungsszenarien von ZooKeeper (Rezepte wie: Distributed Lock Service, Cluster Leader Election, Shared Counter, Cache-Mechanismus, Distributed Queue usw.), diese Implementierungen folgen den Best Practices von zk und betrachten verschiedene als Extremfälle

Curator besteht aus einer Reihe von Modulen, die für allgemeine Entwickler am häufigsten verwendet curator-frameworkwerden curator-recipes:

  • curator-framework: Stellt allgemeine zk-bezogene zugrunde liegende Operationen bereit
  • curator-recipes: Enthält einige Referenzen zu typischen Anwendungsszenarien von zk. Die verteilten Sperren, auf die sich dieser Abschnitt konzentriert, werden von diesem Paket bereitgestellt

Code-Praxis

curator 4.3.0Support zookeeper 3.4.xund 3.5, aber Sie müssen auf die übergebenen Abhängigkeiten achten curator, die mit der Version übereinstimmen müssen, die auf der tatsächlichen Serverseite verwendet wird zookeeper 3.4.14.

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.3.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.3.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
</dependency>

1. Konfiguration

Clientkonfiguration curatorhinzufügen:

@Configuration
public class CuratorConfig {
    
    

    @Bean
    public CuratorFramework curatorFramework(){
    
    
        // 重试策略,这里使用的是指数补偿重试策略,重试3次,初始重试间隔1000ms,每次重试之后重试间隔递增。
        RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);
        // 初始化Curator客户端:指定链接信息 及 重试策略
        CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.111:2181", retry);
        client.start(); // 开始链接,如果不调用该方法,很多方法无法工作
        return client;
    }
}

2. Reentrant-Sperre InterProcessMutex

ReentrantÄhnlich wie und bedeutet, dass JDKderselbe ReentrantLockClient mehrere Male erwerben kann, während er die Sperre besitzt, ohne blockiert zu werden. Es wird durch Klassen implementiert InterProcessMutex.

// 常用构造方法
public InterProcessMutex(CuratorFramework client, String path)
// 获取锁
public void acquire();
// 带超时时间的可重入锁
public boolean acquire(long time, TimeUnit unit);
// 释放锁
public void release();

Testmethode:

@Autowired
private CuratorFramework curatorFramework;

public void checkAndLock() {
    
    
     InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/curator/lock");
    try {
    
    
        // 加锁
        mutex.acquire();

        // 处理业务
        // 例如查询库存 扣减库存
        
        // this.testSub(mutex); 如想重入,则需要使用同一个InterProcessMutex对象

        // 释放锁
        mutex.release();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

public void testSub(InterProcessMutex mutex) {
    
    

    try {
    
    
        mutex.acquire();
    	System.out.println("测试可重入锁。。。。");
        mutex.release();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

Hinweis: Wenn Sie wiedereintreten möchten, müssen Sie dasselbe InterProcessMutex-Objekt verwenden.

3. Nicht wiedereintretende Sperre InterProcessSemaphoreMutex

Spezifische Implementierung: InterProcessSemaphoreMutexÄhnlich wie beim InterProcessMutexAufrufen einer Methode besteht der Unterschied darin, dass die Sperre nicht wiedereintrittsfähig ist und nicht in demselben Thread wiedereintrittsfähig sein kann.

public InterProcessSemaphoreMutex(CuratorFramework client, String path);
public void acquire();
public boolean acquire(long time, TimeUnit unit);
public void release();

Fall:

@Autowired
private CuratorFramework curatorFramework;

public void deduct() {
    
    

    InterProcessSemaphoreMutex mutex = new InterProcessSemaphoreMutex(curatorFramework, "/curator/lock");
    try {
    
    
        mutex.acquire();

        // 处理业务
        // 例如查询库存 扣减库存

    } catch (Exception e) {
    
    
        e.printStackTrace();
    } finally {
    
    
        try {
    
    
            mutex.release();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4. Reentrante Lese-Schreib-Sperre InterProcessReadWriteLock

ähnlich JDK._ ReentrantReadWriteLock_ Ein Thread mit einer Schreibsperre kann erneut in eine Lesesperre eintreten, aber eine Lesesperre kann nicht in eine Schreibsperre eintreten. Dies bedeutet auch, dass Schreibsperren auf Lesesperren heruntergestuft werden können. Ein Upgrade von einer Lesesperre auf eine Schreibsperre ist nicht möglich. Die Hauptimplementierungsklasse InterProcessReadWriteLock:

// 构造方法
public InterProcessReadWriteLock(CuratorFramework client, String basePath);
// 获取读锁对象
InterProcessMutex readLock();
// 获取写锁对象
InterProcessMutex writeLock();

Hinweis: Schreibsperren blockieren den anfordernden Thread, bis sie freigegeben werden, während Lesesperren dies nicht tun

public void testZkReadLock() {
    
    
    try {
    
    
        InterProcessReadWriteLock rwlock = new InterProcessReadWriteLock(curatorFramework, "/curator/rwlock");
        rwlock.readLock().acquire(10, TimeUnit.SECONDS);
        // TODO:一顿读的操作。。。。
        //rwlock.readLock().unlock();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

public void testZkWriteLock() {
    
    
    try {
    
    
        InterProcessReadWriteLock rwlock = new InterProcessReadWriteLock(curatorFramework, "/curator/rwlock");
        rwlock.writeLock().acquire(10, TimeUnit.SECONDS);
        // TODO:一顿写的操作。。。。
        //rwlock.writeLock().unlock();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

5. Verriegeln Sie InterProcessMultiLock

Multi Shared Lockist ein verschlossener Behälter. Bei Aufruf acquirewerden alle Sperren erworben acquire, und wenn die Anfrage fehlschlägt, werden alle Sperren erworben release. Alle Schlösser werden bei Aufruf releasegesperrt release(Störung wird ignoriert). Grundsätzlich ist es der Repräsentant der Gruppensperre, und die Anforderungsfreigabeoperation darauf wird an alle darin enthaltenen Sperren weitergegeben. Implementierungsklasse InterProcessMultiLock:

// 构造函数需要包含的锁的集合,或者一组ZooKeeper的path
public InterProcessMultiLock(List<InterProcessLock> locks);
public InterProcessMultiLock(CuratorFramework client, List<String> paths);

// 获取锁
public void acquire();
public boolean acquire(long time, TimeUnit unit);

// 释放锁
public synchronized void release();

6. Semaphore InterProcessSemaphoreV2

Eine Zählsemaphore ist ähnlich JDK. Eine Reihe von Lizenzen ( ), die in verwaltet und in Leases ( ) genannt werden. Beachten Sie, dass alle Instanzen denselben Wert verwenden müssen. Der Aufruf gibt ein Lease-Objekt zurück. Der Auftraggeber muss sich in diesen Mietobjekten aufhalten, sonst gehen die Mietverträge verloren. Wenn der Client jedoch aus irgendeinem Grund verloren geht, werden die von diesen Clients gehaltenen Leases automatisch wiederhergestellt , sodass andere Clients diese Leases weiterhin verwenden können. Die Hauptimplementierungsklasse :SemaphoreJDKSemaphorepermitsCubatorLeasenumberOfLeasesacquirefinallyclosesessioncrashcloseInterProcessSemaphoreV2

// 构造方法
public InterProcessSemaphoreV2(CuratorFramework client, String path, int maxLeases);

// 注意一次你可以请求多个租约,如果Semaphore当前的租约不够,则请求线程会被阻塞。
// 同时还提供了超时的重载方法
public Lease acquire();
public Collection<Lease> acquire(int qty);
public Lease acquire(long time, TimeUnit unit);
public Collection<Lease> acquire(int qty, long time, TimeUnit unit)

// 租约还可以通过下面的方式返还
public void returnAll(Collection<Lease> leases);
public void returnLease(Lease lease);

Fallcode:

Methode in StockController hinzufügen:

@GetMapping("test/semaphore")
public String testSemaphore(){
    
    
    this.stockService.testSemaphore();
    return "hello Semaphore";
}

Methode in StockService hinzufügen:

public void testSemaphore() {
    
    
    // 设置资源量 限流的线程数
    InterProcessSemaphoreV2 semaphoreV2 = new InterProcessSemaphoreV2(curatorFramework, "/locks/semaphore", 5);
    try {
    
    
        Lease acquire = semaphoreV2.acquire();// 获取资源,获取资源成功的线程可以继续处理业务操作。否则会被阻塞住
        this.redisTemplate.opsForList().rightPush("log", "10010获取了资源,开始处理业务逻辑。" + Thread.currentThread().getName());
        TimeUnit.SECONDS.sleep(10 + new Random().nextInt(10));
        this.redisTemplate.opsForList().rightPush("log", "10010处理完业务逻辑,释放资源=====================" + Thread.currentThread().getName());
        semaphoreV2.returnLease(acquire); // 手动释放资源,后续请求线程就可以获取该资源
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

7. Zaunbarriere

  1. DistributedBarrierDie Parameter im Konstruktor barrierPathwerden verwendet, um einen Zaun zu bestimmen, solange die barrierPathParameter gleich sind (der Pfad ist derselbe), ist es derselbe Zaun. Typischerweise werden Zäune wie folgt verwendet:

    1. Meister clientstellte einen Zaun auf
    2. Andere Clients rufen an, um darauf zu waitOnBarrier()warten, dass der Zaun entfernt wird, und der Programmverarbeitungs-Thread wird blockiert
    3. Sobald der clientZaun entfernt ist, werden andere Client-Handler in der Zwischenzeit weiter ausgeführt.

DistributedBarrierDie Hauptmethode der Klasse lautet wie folgt:

setBarrier() - 设置栅栏
waitOnBarrier() - 等待栅栏移除
removeBarrier() - 移除栅栏
  1. DistributedDoubleBarrierDoppelzäune, die es Clients ermöglichen, sich am Anfang und am Ende von Berechnungen zu synchronisieren. Prozesse beginnen mit der Berechnung, wenn genügend Prozesse dem doppelten Zaun beigetreten sind, und verlassen den Zaun, wenn die Berechnung abgeschlossen ist. DistributedDoubleBarrierVerwirklichte die Funktion des doppelten Zauns. Der Konstruktor ist wie folgt:

    // client - the client
    // barrierPath - path to use
    // memberQty - the number of members in the barrier
    public DistributedDoubleBarrier(CuratorFramework client, String barrierPath, int memberQty);
    
    enter()enter(long maxWait, TimeUnit unit) - 等待同时进入栅栏
    leave()leave(long maxWait, TimeUnit unit) - 等待同时离开栅栏
    

memberQtydie Anzahl der Mitglieder ist, wenn die enterMethode aufgerufen wird, werden die Mitglieder blockiert, bis alle Mitglieder aufgerufen werden enter. Wenn die leaveMethode aufgerufen wird, blockiert sie auch den aufrufenden Thread, bis alle Mitglieder aufgerufen wurden leave.

Hinweis: memberQtyDer Wert des Parameters ist nur ein Schwellenwert, kein Grenzwert. Wenn die Anzahl der wartenden Zäune größer oder gleich diesem Wert ist, öffnet sich der Zaun!

Wie beim Zaun ( DistributedBarrier) werden auch die barrierPathParameter des Doppelzauns verwendet, um festzustellen, ob es sich um denselben Zaun handelt.Die Verwendung des Doppelzauns ist wie folgt:

  1. Erstellen Sie einen doppelten Zaun ( ) auf demselben Pfad von mehreren Clients DistributedDoubleBarrierund rufen Sie dann enter()die Methode auf, um den Zaun zu betreten, wenn die Anzahl der Zäune erreicht ist memberQty.
  2. Wenn die Anzahl der Zäune erreicht ist memberQty, hören mehrere Clients mit dem Blockieren auf und laufen weiter, bis die leave()Methode ausgeführt wird, und warten darauf, dass memberQtyeine Anzahl von Zäunen leave()gleichzeitig die Methode blockiert.
  3. memberQtyEine Reihe von Barrieren werden leave()gleichzeitig in die Methode blockiert, und die Methoden mehrerer Clients leave()hören auf zu blockieren und laufen weiter.

8. Gemeinsame Zähler

Profitieren Sie ZooKeepervon Zählern, die in einem Cluster gemeinsam genutzt werden können. Verwenden Sie einfach dasselbe path, um den neuesten Zählerwert zu erhalten, was ZooKeeperdurch die Konsistenz von garantiert wird. CuratorEs gibt zwei Zähler, einen intzum Zählen und einen longzum Zählen.

8.1. SharedCount

Die Methoden im Zusammenhang mit gemeinsam genutzten Zählern SharedCountlauten wie folgt:

// 构造方法
public SharedCount(CuratorFramework client, String path, int seedValue);
// 获取共享计数的值
public int getCount();
// 设置共享计数的值
public void setCount(int newCount) throws Exception;
// 当版本号没有变化时,才会更新共享变量的值
public boolean  trySetCount(VersionedValue<Integer> previous, int newCount);
// 通过监听器监听共享计数的变化
public void addListener(SharedCountListener listener);
public void addListener(final SharedCountListener listener, Executor executor);
// 共享计数在使用之前必须开启
public void start() throws Exception;
// 关闭共享计数
public void close() throws IOException;

Anwendungsfälle:

StockController:

@GetMapping("test/zk/share/count")
public String testZkShareCount(){
    
    
    this.stockService.testZkShareCount();
    return "hello shareData";
}

StockService:

public void testZkShareCount() {
    
    
    try {
    
    
        // 第三个参数是共享计数的初始值
        SharedCount sharedCount = new SharedCount(curatorFramework, "/curator/count", 0);
        // 启动共享计数器
        sharedCount.start();
        // 获取共享计数的值
        int count = sharedCount.getCount();
        // 修改共享计数的值
        int random = new Random().nextInt(1000);
        sharedCount.setCount(random);
        System.out.println("我获取了共享计数的初始值:" + count + ",并把计数器的值改为:" + random);
        sharedCount.close();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

8.2. DistributedAtomicNumber

DistributedAtomicNumberDie Schnittstelle ist eine Abstraktion des verteilten atomaren numerischen Typs, die die Methoden definiert, die der verteilte atomare numerische Typ bereitstellen muss.

DistributedAtomicNumberDie Schnittstelle hat zwei Implementierungen: DistributedAtomicLongundDistributedAtomicInteger

Bildbeschreibung hier einfügen

Diese beiden Implementierungen delegieren die Ausführung verschiedener atomarer Operationen an DistributedAtomicValue, sodass die beiden Implementierungen ähnlich sind, außer dass die Typen der dargestellten Werte unterschiedlich sind. Hier ist DistributedAtomicLongein Beispiel zur Demonstration

DistributedAtomicLongNeben dem größeren Zählbereich SharedCountist SharedCountes einfacher und einfacher zu bedienen. Es versucht zuerst, den Zähler mit optimistischem Sperren festzulegen, und wenn es nicht erfolgreich ist (z. B. wenn der Zähler während des Zeitraums von anderen Clients aktualisiert wurde), verwendet es die InterProcessMutexMethode, um den Zählerwert zu aktualisieren. Dieser Zähler hat eine Reihe von Operationen:

  • get(): Holen Sie sich den aktuellen Wert
  • increment():Plus eins
  • decrement(): minus eins
  • add(): Einen bestimmten Wert erhöhen
  • subtract(): Einen bestimmten Wert subtrahieren
  • trySet(): versuchen, den Zählwert einzustellen
  • forceSet(): Setzen Sie den Zählwert zwangsweise

Abschließend muss das zurückgegebene Ergebnis überprüft succeeded()werden, ob die Operation erfolgreich war. Wenn die Operation erfolgreich ist, preValue()stellt sie den Wert vor der Operation dar und postValue()stellt den Wert nach der Operation dar.

Acho que você gosta

Origin blog.csdn.net/weixin_43847283/article/details/128603255
Recomendado
Clasificación