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 => 2
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); }
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"
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";
$ 花费时间 : 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
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!