Artikelverzeichnis
- Vorwort
- Code-Praxis
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-framework
werden curator-recipes
:
curator-framework
: Stellt allgemeine zk-bezogene zugrunde liegende Operationen bereitcurator-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.0
Support zookeeper 3.4.x
und 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 curator
hinzufü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 JDK
derselbe ReentrantLock
Client 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 InterProcessMutex
Aufrufen 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 Lock
ist ein verschlossener Behälter. Bei Aufruf acquire
werden alle Sperren erworben acquire
, und wenn die Anfrage fehlschlägt, werden alle Sperren erworben release
. Alle Schlösser werden bei Aufruf release
gesperrt 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 :Semaphore
JDK
Semaphore
permits
Cubator
Lease
numberOfLeases
acquire
finally
close
session
crash
close
InterProcessSemaphoreV2
// 构造方法
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
-
DistributedBarrier
Die Parameter im KonstruktorbarrierPath
werden verwendet, um einen Zaun zu bestimmen, solange diebarrierPath
Parameter gleich sind (der Pfad ist derselbe), ist es derselbe Zaun. Typischerweise werden Zäune wie folgt verwendet:- Meister
client
stellte einen Zaun auf - Andere Clients rufen an, um darauf zu
waitOnBarrier()
warten, dass der Zaun entfernt wird, und der Programmverarbeitungs-Thread wird blockiert - Sobald der
client
Zaun entfernt ist, werden andere Client-Handler in der Zwischenzeit weiter ausgeführt.
- Meister
DistributedBarrier
Die Hauptmethode der Klasse lautet wie folgt:
setBarrier() - 设置栅栏
waitOnBarrier() - 等待栅栏移除
removeBarrier() - 移除栅栏
-
DistributedDoubleBarrier
Doppelzä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.DistributedDoubleBarrier
Verwirklichte 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) - 等待同时离开栅栏
memberQty
die Anzahl der Mitglieder ist, wenn die enter
Methode aufgerufen wird, werden die Mitglieder blockiert, bis alle Mitglieder aufgerufen werden enter
. Wenn die leave
Methode aufgerufen wird, blockiert sie auch den aufrufenden Thread, bis alle Mitglieder aufgerufen wurden leave
.
Hinweis: memberQty
Der 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 barrierPath
Parameter des Doppelzauns verwendet, um festzustellen, ob es sich um denselben Zaun handelt.Die Verwendung des Doppelzauns ist wie folgt:
- Erstellen Sie einen doppelten Zaun ( ) auf demselben Pfad von mehreren Clients
DistributedDoubleBarrier
und rufen Sie dannenter()
die Methode auf, um den Zaun zu betreten, wenn die Anzahl der Zäune erreicht istmemberQty
. - Wenn die Anzahl der Zäune erreicht ist
memberQty
, hören mehrere Clients mit dem Blockieren auf und laufen weiter, bis dieleave()
Methode ausgeführt wird, und warten darauf, dassmemberQty
eine Anzahl von Zäunenleave()
gleichzeitig die Methode blockiert. memberQty
Eine Reihe von Barrieren werdenleave()
gleichzeitig in die Methode blockiert, und die Methoden mehrerer Clientsleave()
hören auf zu blockieren und laufen weiter.
8. Gemeinsame Zähler
Profitieren Sie ZooKeeper
von Zählern, die in einem Cluster gemeinsam genutzt werden können. Verwenden Sie einfach dasselbe path
, um den neuesten Zählerwert zu erhalten, was ZooKeeper
durch die Konsistenz von garantiert wird. Curator
Es gibt zwei Zähler, einen int
zum Zählen und einen long
zum Zählen.
8.1. SharedCount
Die Methoden im Zusammenhang mit gemeinsam genutzten Zählern SharedCount
lauten 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
DistributedAtomicNumber
Die Schnittstelle ist eine Abstraktion des verteilten atomaren numerischen Typs, die die Methoden definiert, die der verteilte atomare numerische Typ bereitstellen muss.
DistributedAtomicNumber
Die Schnittstelle hat zwei Implementierungen: DistributedAtomicLong
undDistributedAtomicInteger
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 DistributedAtomicLong
ein Beispiel zur Demonstration
DistributedAtomicLong
Neben dem größeren Zählbereich SharedCount
ist SharedCount
es 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 InterProcessMutex
Methode, um den Zählerwert zu aktualisieren. Dieser Zähler hat eine Reihe von Operationen:
get()
: Holen Sie sich den aktuellen Wertincrement()
:Plus einsdecrement()
: minus einsadd()
: Einen bestimmten Wert erhöhensubtract()
: Einen bestimmten Wert subtrahierentrySet()
: versuchen, den Zählwert einzustellenforceSet()
: 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.