Parallelitätsprobleme in Redis
Redis wird seit langem als einzelner Prozess ausgeführt und Befehle werden nacheinander ausgeführt Ich dachte, es würde keine Parallelitätsprobleme geben, aber erst als ich heute die relevanten Informationen sah, wurde mir das plötzlich klar.
Spezifisches Problembeispiel
Es gibt einen Schlüssel, vorausgesetzt, der Name ist myNum und die darin gespeicherten arabischen Ziffern lauten: unter der Annahme, dass der aktuelle Wert 1 ist. Wenn auf myNum mehrere Verbindungen laufen, kommt es zu Parallelitätsproblemen. 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 => 1linkB get myNum => 1linkA set muNum => 2linkB set myNum => 2
Nach dem Ausführen Operation, Das Ergebnis kann 2 sein, was nicht mit unserer erwarteten 3 übereinstimmt.
Sehen Sie sich ein anderes konkretes Beispiel an:
<?phprequire "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 ist möglicherweise nicht vorhanden sei 2000, aber ein < Der Wert von ;2000 beweist die Existenz unseres oben genannten Parallelitätsproblems.
Transaktionen in Redis
Es gibt auch Transaktionen in Redis, aber diese Transaktion ist nicht so vollständig wie in MySQL. Sie garantiert nur Konsistenz und Isolation und erfüllt nicht die Atomizität . Sex und Haltbarkeit.
Redis-Transaktion verwendet Multi, exec Befehl
Atomarität, Redis führt alle Befehle in der Transaktion aus, auch wenn in der Mitte eine Ausführung erfolgt Bei einem Fehler erfolgt kein Rollback. 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.
Isolierung, Redis ist ein einzelner Prozess. Nach dem Starten einer Transaktion werden alle Befehle der aktuellen Verbindung ausgeführt, bis der Befehl exec 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.
Transaktionen in Redis unterstützen keine Atomizität, daher kann das oben genannte Problem nicht gelöst werden.
Natürlich verfügt Redis auch über einen Watch-Befehl, der dieses Problem lösen kann. Sehen Sie sich das folgende Beispiel an, um Watch auf einem Schlüssel auszuführen und dann die Transaktion auszuführen. Er wird den Überwachungsschlüssel a reparieren. Dadurch wird sichergestellt, dass mehrere Verbindungen gleichzeitig hergestellt werden. Nur eine kann erfolgreich ausgeführt werden und die anderen werden fehlschlagen. Beispiel für den Fall, dass
127.0.0.1:6379> set a 1OK127.0.0.1:6379> watch aOK127.0.0.1:6379> multi OK127.0.0.1:6379> incr aQUEUED127.0.0.1:6379> exec1) (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 1OK127.0.0.1:6379> watch testOK127.0.0.1:6379> multiOK127.0.0.1:6379> incrby test 11QUEUED127.0.0.1:6379> exec(nil) 127.0.0.1:6379> get test"100"
So lösen Sie das Problem
redis Der Befehl in ist atomar. Wenn der Wert also eine arabische Zahl ist, kann ich die Befehle get und set in incr oder incrby ändern, um dieses Problem zu lösen. Der folgende Code ermöglicht die gleichzeitige Ausführung zweier Terminals Das Ergebnis ist, dass wir 2000 erwartet haben.
<?phprequire "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);}
Es ist tatsächlich machbar und der Effekt ist ziemlich gut
<?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 werden endlich das bekommen 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
In ähnlicher Weise 1w-mal wiederholen, usleep entfernen und mit incr direkt erhöhen, was etwa 2 Sekunden dauert.
Beim Abbrechen von usleep beim Erhalt des Einkommens verringert sich die Zeit nicht nur, sondern verlängert sich. Die Einstellung von usleep muss angemessen sein, um zu verhindern, dass der Prozess unnötige Schleifen erzeugt
Zusammenfassung
Um es kurz zusammenzufassen: Tatsächlich gibt es bei Redis keine Parallelitätsprobleme, da es sich um einen einzelnen Prozess handelt und egal wie viele Befehle ausgeführt werden, sie werden einzeln ausgeführt. Wenn wir es verwenden, können Parallelitätsprobleme auftreten, z. B. das Paar aus Get und Set.
Das obige ist der detaillierte Inhalt vonWas ist die maximale Parallelität von Redis?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!