Parallelitätsprobleme in Redis
Redis wird seit langem als Cache verwendet, und Redis ist ein Beim Ausführen werden die Befehle nacheinander ausgeführt. Ich dachte immer, dass es keine Parallelitätsprobleme geben würde, als mir das plötzlich klar wurde (empfohlen: Redis-Video-Tutorial)
Spezifisches Problembeispiel
Es gibt einen Schlüssel, vorausgesetzt, der Name ist myNum und darin sind arabische Ziffern gespeichert. Angenommen, der aktuelle Wert ist 1 , und es gibt mehrere Verbindungen, die auf myNum laufen. Zu diesem Zeitpunkt wird es Probleme mit der Parallelität geben. Angenommen, es gibt zwei Verbindungen, linkA und linkB. Beide Verbindungen führen die folgenden Vorgänge aus, nehmen den Wert von myNum, +1, heraus und speichern ihn dann wieder:linkA get myNum => 1 linkB get myNum => 1 linkA set muNum => 2 linkB set myNum => 2Nach dem Ausführen Operation, Das Ergebnis könnte 2 sein, was nicht mit unserer erwarteten 3 übereinstimmt.
Sehen Sie sich ein konkretes Beispiel an:
<?php require "vendor/autoload.php"; $client = new Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]); for ($i = 0; $i < 1000; $i++) { $num = intval($client->get("name")); $num = $num + 1; $client->setex("name", $num, 10080); usleep(10000); }Setzen Sie den Anfangswert von name auf 0 und verwenden Sie dann zwei Terminals, um das obige Programm gleichzeitig auszuführen. Der Endwert von name darf nicht 2000 sein. aber ein Wert von <2000, das beweist auch die Existenz unseres oben genannten Parallelitätsproblems. Wie kann man das lösen?
Transaktionen in Redis
Es gibt auch Transaktionen in Redis, aber diese Transaktion ist nicht so vollständig wie in MySQL und gewährleistet nur Konsistenz und Isolation erfüllt weder Atomizität noch Haltbarkeit.Redis-Transaktionen verwenden Multi- und Exec-Befehle
Atomicity Redis führt alle Befehle in der Transaktion einmal aus und führt kein Rollback durch, selbst wenn in der Mitte ein Ausführungsfehler auftritt. Kill-Signale, Host-Ausfallzeiten usw. führen dazu, dass die Transaktionsausführung fehlschlägt und Redis keinen erneuten Versuch oder Rollback durchführt.
Persistenz, die Persistenz von Redis-Transaktionen hängt vom von Redis verwendeten Persistenzmodus ab. Leider sind verschiedene Persistenzmodi nicht persistent.
Isolation, Redis ist ein einzelner Prozess. Nach dem Starten einer Transaktion werden alle Befehle der aktuellen Verbindung ausgeführt, bis ein Exec-Befehl auftritt, und dann werden Befehle anderer Verbindungen verarbeitet . Konsistenz, nachdem ich die Dokumentation gelesen habe, finde ich es ziemlich lächerlich, aber es scheint korrekt zu sein.
127.0.0.1:6379> set a 1 OK 127.0.0.1:6379> watch a OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr a QUEUED 127.0.0.1:6379> exec 1) (integer) 2 127.0.0.1:6379> get a "2"fehlgeschlagen ist. Am Ende ist ersichtlich, dass der Wert von test durch andere Verbindungen geändert wurde:
127.0.0.1:6379> set test 1 OK 127.0.0.1:6379> watch test OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby test 11 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get test "100"
So löse ich mein Problem
Befehle in Redis sind atomar. Wenn der Wert also eine arabische Zahl ist, kann ich die Get- und Set-Befehle in incr oder incrby ändern, um dieses Problem zu lösen. Der folgende Code öffnet zwei Terminals Gleichzeitig Nach der Ausführung ist das Ergebnis 2000, das unseren Erwartungen entspricht.<?php require "vendor/autoload.php"; $client = new Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]); for ($i = 0; $i < 1000; $i++) { $client->incr("name"); $client->expire("name", 10800); usleep(10000); }
Die von @manzilu erwähnte Methode
Die von manzilu in den Kommentaren erwähnte Methode wurde nach Überprüfung der Informationen überprüft. Sie ist tatsächlich machbar und die Wirkung ist nicht schlecht . Hier ist ein Beispiel<?phprequire "vendor/autoload.php"; $client = new Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]);class RedisLock{ public $objRedis = null; public $timeout = 3; /** * @desc 设置redis实例 * * @param obj object | redis实例 */ public function __construct($obj) { $this->objRedis = $obj; } /** * @desc 获取锁键名 */ public function getLockCacheKey($key) { return "lock_{$key}"; } /** * @desc 获取锁 * * @param key string | 要上锁的键名 * @param timeout int | 上锁时间 */ public function getLock($key, $timeout = NULL) { $timeout = $timeout ? $timeout : $this->timeout; $lockCacheKey = $this->getLockCacheKey($key); $expireAt = time() + $timeout; $isGet = (bool)$this->objRedis->setnx($lockCacheKey, $expireAt); if ($isGet) { return $expireAt; } while (1) { usleep(10); $time = time(); $oldExpire = $this->objRedis->get($lockCacheKey); if ($oldExpire >= $time) { continue; } $newExpire = $time + $timeout; $expireAt = $this->objRedis->getset($lockCacheKey, $newExpire); if ($oldExpire != $expireAt) { continue; } $isGet = $newExpire; break; } return $isGet; } /** * @desc 释放锁 * * @param key string | 加锁的字段 * @param newExpire int | 加锁的截止时间 * * @return bool | 是否释放成功 */ public function releaseLock($key, $newExpire) { $lockCacheKey = $this->getLockCacheKey($key); if ($newExpire >= time()) { return $this->objRedis->del($lockCacheKey); } return true; } } $start_time = microtime(true); $lock = new RedisLock($client); $key = "name";for ($i = 0; $i < 10000; $i++) { $newExpire = $lock->getLock($key); $num = $client->get($key); $num++; $client->set($key, $num); $lock->releaseLock($key, $newExpire); } $end_time = microtime(true);echo "花费时间 : ". ($end_time - $start_time) . "\n";Führen Sie die Shell php setnx.php & php setnx.php& aus und Sie erhalten schließlich das Ergebnis:
$ 花费时间 : 4.3004920482635 [2] + 72356 done php setnx.php # root @ ritoyan-virtual-pc in ~/PHP/redis-high-concurrency [20:23:41] $ 花费时间 : 4.4319710731506 [1] + 72355 done php setnx.phpÄhnliche Schleife 1w-mal, entfernen Sie usleep und verwenden Sie incr direkt zu erhöhen, was etwa 2 Sekunden dauert.
Beim Abbrechen von usleep beim Erhalt des Einkommens verringert sich die Zeit nicht nur, sondern erhöht sich auch. Die Einstellung von usleep muss angemessen sein, um zu verhindern, dass der Prozess unnötige Schleifen erzeugt
Redis-Datenbank-Tutorial.
Das obige ist der detaillierte Inhalt vonLösen von Redis-Parallelitätsproblemen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!