Heim > Datenbank > Redis > Hauptteil

Worauf sollten wir bei der Implementierung verteilter Sperren in Redis achten? [Zusammenfassung der Vorsichtsmaßnahmen]

青灯夜游
Freigeben: 2022-03-04 16:21:58
nach vorne
3651 Leute haben es durchsucht

Was sollten Sie bei der Implementierung verteilter Sperren in Redis beachten? Im folgenden Artikel werden einige Punkte zusammengefasst und mit Ihnen geteilt, die Sie bei der Verwendung von Redis als verteilte Sperre beachten sollten. Ich hoffe, dass er Ihnen hilfreich sein wird!

Worauf sollten wir bei der Implementierung verteilter Sperren in Redis achten? [Zusammenfassung der Vorsichtsmaßnahmen]

Redis implementiert verteilte Sperren

Ich habe kürzlich einen guten Artikel über den Prozess des Lesens verteilter Sperren gesehen und speziell mein eigenes Verständnis verarbeitet:

Drei Aspekte der Implementierung verteilter Redis-Sperren Kernelemente:

1. Der einfachste Weg zum Sperren

ist die Verwendung des Befehls setnx. Der Schlüssel ist die eindeutige Kennung der Sperre, die entsprechend dem Unternehmen benannt wird. Der Wert ist die Thread-ID des aktuellen Threads. [Verwandte Empfehlung: Redis-Video-Tutorial]

Wenn Sie beispielsweise die Flash-Sale-Aktivität eines Produkts sperren möchten, können Sie den Schlüssel „lock_sale_ID“ nennen. Und auf welchen Wert ist der Wert eingestellt? Wir können es vorübergehend auf 1 setzen. Der Pseudocode zum Sperren lautet wie folgt:

setnx(key, 1)Wenn ein Thread setnx ausführt, gibt er 1 zurück, was darauf hinweist, dass der Thread die Sperre erfolgreich erhalten hat setnx gibt 0 zurück, was darauf hinweist, dass der Schlüssel bereits vorhanden ist und der Thread die Sperre nicht ergreifen konnte.

2. Entsperren

Wenn Sie sperren möchten, müssen Sie entsperren. Wenn der Thread, der die Sperre erhalten hat, seine Aufgabe abschließt, muss er die Sperre aufheben, damit andere Threads eintreten können. Der einfachste Weg, die Sperre aufzuheben, besteht darin, die Anweisung del auszuführen. Der Pseudocode lautet wie folgt:

del(key)Nachdem die Sperre aufgehoben wurde, können andere Threads weiterhin den Befehl setnx ausführen, um sie abzurufen das Schloss.

3. Sperrzeitüberschreitung

Was bedeutet Sperrzeitüberschreitung? Wenn ein Thread, der die Sperre erhält, während der Ausführung der Aufgabe stirbt und keine Zeit hat, die Sperre explizit aufzuheben, wird die Ressource für immer gesperrt und andere Threads können nie mehr darauf zugreifen.

Der Schlüssel von setnx muss also eine Zeitüberschreitungszeit festlegen, um sicherzustellen, dass die Sperre nach einer bestimmten Zeit automatisch aufgehoben wird, auch wenn er nicht explizit freigegeben wird. setnx unterstützt keine Timeout-Parameter, daher sind zusätzliche Anweisungen erforderlich:

expire (Schlüssel, 30) Zusammenfassend lautet die erste Version unseres Pseudocodes für die verteilte Sperrenimplementierung wie folgt:

if(setnx(key,1) == 1){
    expire(key,30)
    try {
        do something ......
    }catch()  {  }  finally {
       del(key)
    }

}
Nach dem Login kopieren

Aufgrund des oben Gesagten gibt es im Pseudocode drei schwerwiegende Probleme:

1. Die Nichtatomarität von setnx und Expire

Stellen Sie sich ein extremes Szenario vor, wenn ein Thread setnx ausführt und die Sperre erfolgreich erhält:

setnx wurde gerade erfolgreich ausgeführt. Bevor der Ablaufbefehl ausgeführt werden konnte, starb Knoten 1 mit einem Duang-Geräusch.

if(setnx(key,1) == 1){  //此处挂掉了.....
    expire(key,30)
    try {
        do something ......
    }catch()
  {
  }
  finally {
       del(key)
    }
 
}
Nach dem Login kopieren

Auf diese Weise ist für die Sperre keine Ablaufzeit festgelegt und sie wird „unsterblich“, und andere Threads können die Sperre nicht mehr erhalten.

Wie kann man es lösen? Die setnx-Anweisung selbst unterstützt das eingehende Timeout nicht. Redis 2.6.12 oder höher fügt der set-Anweisung optionale Parameter hinzu: set (key, 1, 30, NX), die setnx ersetzen können Anleitung. .

2. Die Verwendung von del nach einer Zeitüberschreitung führt dazu, dass die Sperren anderer Threads versehentlich gelöscht werden. Dies ist ein weiteres extremes Szenario. Angenommen, ein Thread erhält die Sperre erfolgreich und die Zeitüberschreitung ist auf 30 Sekunden eingestellt . Wenn Thread A aus irgendeinem Grund sehr langsam ausgeführt wird und die Ausführung nach 30 Sekunden noch nicht abgeschlossen ist, wird die Sperre nach Ablauf automatisch aufgehoben und Thread B erhält die Sperre. Anschließend schließt Thread A die Aufgabe ab und Thread A führt dann die Del-Anweisung aus, um die Sperre aufzuheben. Zu diesem Zeitpunkt ist die Ausführung von Thread B jedoch noch nicht abgeschlossen. Thread A löscht tatsächlich die von Thread B hinzugefügte Sperre.

Wie vermeide ich diese Situation? Sie können eine Beurteilung vornehmen, bevor del die Sperre aufhebt, um zu überprüfen, ob es sich bei der aktuellen Sperre um eine von Ihnen selbst hinzugefügte Sperre handelt.

Was die spezifische Implementierung betrifft, können Sie beim Sperren die aktuelle Thread-ID als Wert verwenden und überprüfen, ob der dem Schlüssel entsprechende Wert die ID Ihres eigenen Threads ist, bevor Sie ihn löschen.

加锁:
String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)
doSomething.....
 
解锁:
if(threadId .equals(redisClient.get(key))){
    del(key)
}
Nach dem Login kopieren

Allerdings bringt dies ein neues Problem mit sich, wenn Beurteilung und Freigabe der Sperre zwei unabhängige und nicht atomare Vorgänge sind.

Wir sind alle Programmierer, die nach Perfektion streben, daher muss dieser Teil mit Lua-Skript implementiert werden:

String luaScript = 'if redis.call('get', KEYS[1]) == ARGV[ 1] then return

redis

.call('del', KEYS[1]) else return 0 end';redisClient.eval(luaS

cript

, Collections.singletonList( key), Collections.singletonList(threadId));Auf diese Weise ist der Überprüfungs- und Löschvorgang eine atomare Operation. 3.

Möglichkeit der Parallelität

还是刚才第二点所描述的场景,虽然我们避免了线程A误删掉key的情况,但是同一时间有A,B两个线程在访问代码块,仍然是不完美的。

怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”

当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。

当线程A执行完任务,会显式关掉守护线程。

另一种情况,如果节点1 忽然断电,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了。

memcache实现分布式锁

首页top 10, 由数据库加载到memcache缓存n分钟
微博中名人的content cache, 一旦不存在会大量请求不能命中并加载数据库
需要执行多个IO操作生成的数据存在cache中, 比如查询db多次
问题
在大并发的场合,当cache失效时,大量并发同时取不到cache,会同一瞬间去访问db并回设cache,可能会给系统带来潜在的超负荷风险。我们曾经在线上系统出现过类似故障。

解决方法

if (memcache.get(key) == null) {
// 3 min timeout to avoid mutex holder crash
if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
value = db.get(key);
memcache.set(key, value);
memcache.delete(key_mutex);
} else {
 
sleep(50);
retry();
}
}
Nach dem Login kopieren

在load db之前先add一个mutex key, mutex key add成功之后再去做加载db, 如果add失败则sleep之后重试读取原cache数据。为了防止死锁,mutex key也需要设置过期时间。伪代码如下

Zookeeper实现分布式缓存

Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Znode

Znode分为四种类型:

  • 1.持久节点 (PERSISTENT)

默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在 。

  • 2.持久节点顺序节点(PERSISTENT_SEQUENTIAL)

所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号:

  • 3.临时节点(EPHEMERAL)

和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除:

  • 4.临时顺序节点(EPHEMERAL_SEQUENTIAL)

顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除。

Zookeeper分布式锁恰恰应用了临时顺序节点。具体如何实现呢?让我们来看一看详细步骤:

  • 获取锁

首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1

之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。

这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2

Client2查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。

Also Client2 registriert Watcher beim Knoten Lock1, der nur einen höheren Rang als dieser hat, um Lock1zu überwachen > > Ob der Knoten existiert. Das bedeutet, dass Client2 die Sperre nicht ergreifen konnte und in den Wartezustand wechselte. Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。

这时候,如果又有一个客户端Client3前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock3

Client3查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock3是不是顺序最靠前的一个,结果同样发现节点Lock3并不是最小的。

于是,Client3向排序仅比它靠前的节点Lock2注册Watcher,用于监听Lock2节点是否存在。这意味着Client3同样抢锁失败,进入了等待状态。

这样一来,Client1得到了锁,Client2监听了Lock1Client3监听了Lock2。这恰恰形成了一个等待队列,很像是Java当中ReentrantLock所依赖的AQS(AbstractQueuedSynchronizer)

  • 释放锁

释放锁分为两种情况:

1.任务完成,客户端显示释放

当任务完成时,Client1会显示调用删除节点Lock1的指令。

2.任务执行过程中,客户端崩溃

获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除。

由于Client2一直监听着Lock1的存在状态,当Lock1节点被删除,Client2会立刻收到通知。这时候Client2会再次查询ParentLock下面的所有节点,确认自己创建的节点Lock2是不是目前最小的节点。如果是最小,则Client2顺理成章获得了锁。

同理,如果Client2也因为任务完成或者节点崩溃而删除了节点Lock2,那么Cient3就会接到通知。

最终,Client3

Zu diesem Zeitpunkt Wenn ein anderer Client Client3 die Sperre erhält, lädt er einen temporären Sequenzknoten Lock3 in ParentLock herunter und erstellt ihn.

Client3 Suchen Sie alle temporären Sequenzknoten unter ParentLock, sortieren Sie sie und beurteilen Sie, ob der von Ihnen erstellte Knoten Lock3 derjenige mit der höchsten Reihenfolge ist. Der Knoten Lock3 ist nicht der kleinste.

Also registriert Client3 Watcher bei dem Knoten Lock2, der nur einen höheren Rang als dieser hat, um Lock2 zu überwachen Ob der Knoten vorhanden ist. Dies bedeutet, dass auch Client3 die Sperre nicht ergreifen konnte und in den Wartezustand wechselte.

Auf diese Weise Client1 hat die Sperre erhalten, Client2 hat auf Lock1 gelauscht und Client3 hat auf Lock2 gelauscht . Dies bildet lediglich eine Warteschlange, ähnlich wie der AQS (AbstractQueuedSynchronizer), auf den ReentrantLock in Java angewiesen ist.

  • Sperre aufheben
Es gibt zwei Situationen zum Aufheben der Sperre:

1 Wenn die Aufgabe abgeschlossen ist, zeigt der Client die Freigabe an. Wenn die Aufgabe abgeschlossen ist, zeigt Client1 den Aufruf an um die Knotenanweisung Lock1 zu löschen.

🎜🎜🎜2 Während des Vorgangs stürzt der Client ab 🎜🎜Client1, der die Sperre erhalten hat. Wenn Duang während der Ausführung der Aufgabe abstürzt, wird die Verbindung zum Zookeeper-Server getrennt. Entsprechend den Eigenschaften des temporären Knotens wird der zugehörige Knoten Lock1 automatisch gelöscht. 🎜🎜🎜🎜Wegen Client2 hat den Existenzstatus von Lock1 überwacht. Wenn der Knoten Lock1 gelöscht wird, erhält Client2 sofort eine Benachrichtigung. Zu diesem Zeitpunkt fragt Client2 alle Knoten unter ParentLock erneut ab, um zu bestätigen, ob der von ihm selbst erstellte Knoten Lock2 derzeit der kleinste Knoten ist. Wenn es der kleinste ist, erhält Client2 die Sperre auf natürliche Weise. 🎜🎜🎜🎜Ähnlich, wenn Client2 löscht auch den Knoten Lock2 aufgrund des Abschlusses der Aufgabe oder eines Knotenabsturzes, dann wird Cient3 benachrichtigt. 🎜🎜🎜🎜Abschließend Client3 hat die Sperre erfolgreich erhalten. 🎜🎜🎜🎜🎜🎜🎜🎜🎜🎜🎜Vergleich der verteilten Sperren von Zookeeper und Redis🎜🎜Die folgende Tabelle fasst die Vor- und Nachteile der verteilten Sperren von Zookeeper und Redis zusammen: , Bitte besuchen Sie : 🎜Einführung in die Programmierung🎜! ! 🎜

Das obige ist der detaillierte Inhalt vonWorauf sollten wir bei der Implementierung verteilter Sperren in Redis achten? [Zusammenfassung der Vorsichtsmaßnahmen]. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:cnblogs.com
Erklärung dieser Website
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
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!