Dieser Artikel stellt Ihnen verteilte Sperren in Redis vor, warum verteilte Sperren benötigt werden und wie Redis verteilte Sperren implementiert. Ich hoffe, er wird Ihnen hilfreich sein!
Warum verteilte Sperren benötigt werden
Der Zweck der Verwendung verteilter Sperren besteht lediglich darin, sicherzustellen, dass nur ein Client gleichzeitig auf gemeinsam genutzten Ressourcen arbeiten kann.
Bei der logischen Verarbeitung in verteilten Anwendungen stoßen wir häufig auf Parallelitätsprobleme. [Verwandte Empfehlungen: Redis-Video-Tutorial]
Wenn beispielsweise für einen Vorgang eine Änderung des Benutzerstatus erforderlich ist, erfordert die Änderung des Status zunächst das Lesen des Benutzerstatus, die Änderung im Speicher und die erneute Speicherung nach Abschluss der Änderung . Wenn solche Vorgänge gleichzeitig ausgeführt werden, treten Parallelitätsprobleme auf, da die beiden Vorgänge Lesen und Speichern des Status nicht atomar sind.
Zu diesem Zeitpunkt müssen verteilte Sperren verwendet werden, um die gleichzeitige Ausführung des Programms einzuschränken. Als Caching-Middleware-System kann Redis einen solchen verteilten Sperrmechanismus bereitstellen. Sein Kern besteht darin, eine Grube in Redis zu besetzen und festzustellen, dass sie belegt ist. Warten Sie einfach und versuchen Sie es später erneut
Im Allgemeinen müssen verteilte Sperren, die in Produktionsumgebungen verfügbar sind, die folgenden Punkte erfüllen:
Der gegenseitige Ausschluss ist das Grundmerkmal von Sperren. Es gibt eine Sperre und Kritische Vorgänge werden ausgeführt. Timeout-Release ist eine weitere notwendige Funktion der Sperre. Sie können sie mit der Konfiguration innodb_lock_wait_timeout
in der MySQL-InnoDB-Engine vergleichen Verschwendung von Ressourcen;
SETNX-Schlüsselwert
Nur wenn der Schlüsselschlüssel nicht vorhanden ist, wird der Wert des Schlüsselschlüssels auf Wert gesetzt. Wenn der Schlüsselschlüssel vorhanden ist, erstellt SETNX keinen Aktion. boolean result = jedis.setnx("lock-key",true)== 1L; if (result) { try { // do something } finally { jedis.del("lock-key"); } }
innodb_lock_wait_timeout
配置,通过超时释放,防止不必要的线程等待和资源浪费;使用SETNX实现
SETNX的使用方式为:SETNX key value
,只在键key不存在的情况下,将键key的值设置为value,若键key存在,则SETNX不做任何动作。
String result = jedis.set("lock-key",true, 5); if ("OK".equals(result)) { try { // do something } finally { jedis.del("lock-key"); } }
这种方案有一个致命问题,就是某个线程在获取锁之后由于某些异常因素(比如宕机)而不能正常的执行解锁操作,那么这个锁就永远释放不掉了。
为此,我们可以为这个锁加上一个超时时间
执行 SET key value EX seconds
的效果等同于执行 SETEX key seconds value
执行 SET key value PX milliseconds
的效果等同于执行 PSETEX key milliseconds value
String velue= String.valueOf(System.currentTimeMillis()) String result = jedis.set("lock-key",velue, 5); if ("OK".equals(result)) { try { // do something } finally { //非原子操作 if(jedis.get("lock-key")==value){ jedis.del("lock-key"); } } }
方案看上去很完美,但实际上还是会有问题
试想一下,某线程A获取了锁并且设置了过期时间为10s,然后在执行业务逻辑的时候耗费了15s,此时线程A获取的锁早已被Redis的过期机制自动释放了
在线程A获取锁并经过10s之后,改锁可能已经被其它线程获取到了。当线程A执行完业务逻辑准备解锁(DEL key
)的时候,有可能删除掉的是其它线程已经获取到的锁。
所以最好的方式是在解锁时判断锁是否是自己的,我们可以在设置key
的时候将value设置为一个唯一值uniqueValue
(可以是随机值、UUID、或者机器号+线程号的组合、签名等)。
当解锁时,也就是删除key的时候先判断一下key对应的value是否等于先前设置的值,如果相等才能删除key
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
这里我们一眼就可以看出问题来:GET
和DEL
是两个分开的操作,在GET执行之后且在DEL执行之前的间隙是可能会发生异常的。
如果我们只要保证解锁的代码是原子性的就能解决问题了
这里我们引入了一种新的方式,就是Lua脚本,示例如下:
rrreee其中ARGV[1]
表示设置key时指定的唯一值。
由于Lua脚本的原子性,在Redis执行该脚本的过程中,其他客户端的命令都需要等待该Lua脚本执行完才能执行。
确保过期时间大于业务执行时间
为了防止多个线程同时执行业务代码,需要确保过期时间大于业务执行时间
增加一个boolean类型的属性isOpenExpirationRenewal
,用来标识是否开启定时刷新过期时间
在增加一个scheduleExpirationRenewal
方法用于开启刷新过期时间的线程
加锁代码在获取锁成功后将isOpenExpirationRenewal置为true,并且调用scheduleExpirationRenewal
SET key value EX seconds
entspricht der Ausführung von SETEX key seconds value
AusführenSET-Schlüsselwert PX-Millisekunden entspricht der Ausführung von PSETEX-Schlüssel-Millisekundenwert
rrreee
Die Lösung sieht perfekt aus, aber tatsächlich gibt es immer noch Probleme
🎜🎜 Stellen Sie sich vor, ein Thread A hat die Sperre erworben und die Ablaufzeit auf 10 Sekunden festgelegt. Anschließend dauerte die Ausführung der Geschäftslogik 15 Sekunden. Zu diesem Zeitpunkt wurde die von Thread A erworbene Sperre bereits automatisch durch den Ablaufmechanismus von Redis freigegeben 🎜Nachdem Thread A die Sperre erworben und bestanden hat, kann es sein, dass nach 10 Sekunden die geänderte Sperre von anderen Threads übernommen wurde. Wenn Thread A die Ausführung der Geschäftslogik abgeschlossen hat und sich auf die Entsperrung vorbereitet (DEL-Taste
), ist es möglich, die von anderen Threads erworbene Sperre zu löschen. 🎜🎜Der beste Weg ist also, beim Entsperren festzustellen, ob das Schloss Ihnen gehört. Wir können den Wert auf einen eindeutigen Wert uniqueValue
setzen, wenn Sie den key
festlegen (es kann sein). Zufälliger Wert, UUID oder Kombination aus Maschinennummer + Thread-Nummer, Signatur usw.). 🎜🎜Beim Entsperren, also beim Löschen des Schlüssels, stellen Sie zunächst fest, ob der dem Schlüssel entsprechende Wert mit dem zuvor eingestellten Wert übereinstimmt. Wenn er gleich ist, kann der Schlüssel gelöscht werden🎜rrreee🎜Hier können wir das Problem sehen Auf einen Blick: GET
und DEL
sind zwei separate Operationen in der Lücke zwischen der GET-Ausführung und vor der DEL-Ausführung. 🎜🎜Wenn wir nur sicherstellen müssen, dass der Entsperrcode atomar ist, kann das Problem gelöst werden🎜🎜Hier stellen wir eine neue Methode vor, nämlich 🎜Lua-Skript🎜. Das Beispiel lautet wie folgt: 🎜rrreee🎜wobei ARGV [1]
stellt den eindeutigen Wert dar, der beim Festlegen des Schlüssels angegeben wird. 🎜🎜Aufgrund der Atomizität des Lua-Skripts müssen andere Client-Befehle während der Ausführung des Skripts durch Redis auf die Ausführung des Lua-Skripts warten, bevor sie ausgeführt werden können. 🎜🎜🎜Stellen Sie sicher, dass die Ablaufzeit größer als die Geschäftsausführungszeit ist.🎜🎜🎜Um zu verhindern, dass mehrere Threads gleichzeitig Geschäftscode ausführen, muss sichergestellt werden, dass die Ablaufzeit größer als die Geschäftsausführungszeit ist 🎜Fügen Sie ein boolesches Attribut isOpenExpirationRenewal
hinzu, um zu ermitteln, ob die geplante Aktualisierungsablaufzeit aktiviert ist.🎜🎜Fügen Sie eine scheduleExpirationRenewal
-Methode hinzu, um den Thread zum Aktualisieren der Ablaufzeit zu öffnen.🎜🎜Die Sperre Code setzt isOpenExpirationRenewal auf true, nachdem die Sperre erfolgreich erworben wurde, und ruft die Methode ScheduleExpirationRenewal
auf. Starten Sie den Thread, der die Ablaufzeit aktualisiert. 🎜🎜 Fügen Sie dem Entsperrcode eine Codezeile hinzu und setzen Sie das Attribut isOpenExpirationRenewal auf false. Stoppen Sie die Thread-Abfrage, die die Ablaufzeit aktualisiert🎜🎜🎜Redisson-Implementierung🎜🎜🎜Es wird geöffnet, nachdem die Sperre erfolgreich erworben wurde. Eine geplante Aufgabe, die geplante Aufgabe wird regelmäßig auf Erneuerung überprüft🎜Der Zeitunterschied zwischen den einzelnen Aufrufen dieses geplanten Zeitplans beträgt internalLockLeaseTime / 3
, was 10 Sekunden entspricht. internalLockLeaseTime / 3
,也就10秒
默认情况下,加锁的时间是30秒.如果加锁的业务没有执行完,那么到 30-10 = 20
秒的时候,就会进行一次续期,把锁重置成30秒
在集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生
Redlock算法就是为了解决这个问题
使用 Redlock,需要提供多个 Redis
实例,这些实例之前相互独立没有主从关系。同很多分布式算法一样,redlock 也使用大多数机制
加锁时,它会向过半节点发送 set指令,只要过半节点 set
成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试、时钟漂移等很多细节问题,同时因为 Redlock
需要向多个节点进行读写,意味着相比单实例 Redis 性能会下降一些
Redlock 算法是在单 Redis 节点基础上引入的高可用模式,Redlock 基于 N 个完全独立的 Redis 节点,一般是大于 3 的奇数个(通常情况下 N 可以设置为 5),可以基本保证集群内各个节点不会同时宕机。
假设当前集群有 5 个节点,运行 Redlock 算法的客户端依次执行下面各个步骤,来完成获取锁的操作
也就是说,假设锁30秒过期,三个节点加锁花了31秒,自然是加锁失败了
在 Redis 官方推荐的 Java 客户端 Redisson
中,内置了对 RedLock
30-10 = 20
Sekunden, eine Erneuerung wird durchgeführt und die Sperre wird auf 30 Sekunden zurückgesetztRedlock Der Algorithmus ist ein Hochverfügbarkeitsmodus, der auf N völlig unabhängigen Redis-Knoten basiert, normalerweise eine ungerade Zahl größer als 3 N kann auf 5 gesetzt werden, wodurch grundsätzlich sichergestellt werden kann, dass nicht jeder Knoten im Cluster gleichzeitig ausfällt.RedLock
Im Cluster legt der Masterknoten auf Dieses Mal übernimmt der Slave-Knoten, aber es gibt keine offensichtliche Wahrnehmung auf dem Client. Es stellte sich heraus, dass der erste Client erfolgreich eine Sperre auf dem Master-Knoten beantragte, aber bevor die Sperre mit dem Slave-Knoten synchronisiert werden konnte, starb der Master-Knoten plötzlich. Dann wird der Slave-Knoten zum Master-Knoten. Dieser neue Knoten verfügt nicht über diese Sperre. Wenn also ein anderer Client eine Sperre anfordert, wird diese sofort genehmigt. Dies führt dazu, dass die gleiche Sperre im System gleichzeitig von zwei Clients gehalten wird, was zu Unsicherheit führt. Um dieses Problem zu lösen, müssen Sie mehrere
Redis bereitstellen Diese Instanzen waren zuvor unabhängig voneinander und hatten keine Master-Slave-Beziehung. Wie viele verteilte Algorithmen verwendet Redlock auch die meisten Mechanismen. Beim Sperren werden Set-Anweisungen an mehr als die Hälfte der Knoten gesendet. Solange <code>set
erfolgreich ist, wird die Sperre berücksichtigt erfolgreich. Beim Aufheben der Sperre muss eine Del-Anweisung an alle Knoten gesendet werden. Der Redlock-Algorithmus muss jedoch auch viele detaillierte Probleme wie Fehlerwiederholung und Taktabweichung berücksichtigen. DaRedlock
gleichzeitig mehrere Knoten lesen und schreiben muss, bedeutet dies, dass die Leistung von Redis beeinträchtigt wird
Angenommen, der aktuelle Cluster verfügt über 5 Knoten, führt der Client, der den Redlock-Algorithmus ausführt, die folgenden Schritte nacheinander aus, um den Sperrerfassungsvorgang abzuschließen.
Der Client zeichnet die aktuelle Systemzeit in Millisekunden auf. Versuchen Sie, mit zu beginnen In 5 Redis-Instanzen wird derselbe Schlüssel zum Erwerb der Sperre verwendet. Wenn der Client Redis zum Erwerb der Sperre auffordert, sollte er eine Netzwerkverbindung und ein Antwortzeitlimit festlegen. Das Zeitlimit sollte kürzer als die Ablaufzeit der Sperre sein, um Probleme aufgrund des Netzwerks zu vermeiden Fehler;
Der Client verwendet die aktuelle Zeit abzüglich der Zeit, zu der er mit dem Erwerb der Sperre begonnen hat, um die Zeit zu erhalten, die zum Erwerb der Sperre verwendet wurde. Die Sperre wird genau dann gezählt, wenn die Sperre von mehr als der Hälfte der Redis-Knoten erworben wurde und die verwendete Zeit ist kürzer als die Ablaufzeit des Schlosses.
Im von Redis offiziell empfohlenen Java-Client , integrierte Implementierung von
https://redis.io/topics/distlockRedLock
https://github.com/redisson/redisson/wiki
🎜RedLock-Problem: 🎜🎜 🎜RedLock gewährleistet nur die hohe Verfügbarkeit der Sperre, garantiert aber nicht die Korrektheit der Sperre 🎜🎜RedLock ist ein 🎜verteiltes System, das stark auf die Systemuhr angewiesen ist🎜🎜🎜Martins Kritik an RedLock: 🎜🎜🎜Für Effizienzverbesserungsszenarien, RedLock ist zu schwer. 🎜🎜Für Szenarien, die eine extrem hohe Genauigkeit erfordern, kann RedLock die Genauigkeit nicht garantieren. 🎜🎜🎜🎜Dieser Artikel wurde nachgedruckt von: https://juejin.cn/post/7018968452136173576🎜🎜Autor: With a Distant Mind🎜🎜🎜Weitere Kenntnisse zum Thema Programmierung finden Sie unter: 🎜Programmiervideo🎜! ! 🎜Das obige ist der detaillierte Inhalt vonWarum ist in Redis eine verteilte Sperre erforderlich? Wie erreichen?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!