Heim  >  Artikel  >  Datenbank  >  Lösen von Redis-Parallelitätsproblemen

Lösen von Redis-Parallelitätsproblemen

尚
nach vorne
2019-11-26 15:06:112355Durchsuche

Lösen von Redis-Parallelitätsproblemen

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

Nach 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([
    &#39;scheme&#39; => &#39;tcp&#39;,
    &#39;host&#39; => &#39;127.0.0.1&#39;,
    &#39;port&#39; => 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.

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: Führen Sie Watch für einen Schlüssel aus und führen Sie dann die Transaktion aus . Wenn a Nach der Korrektur werden nachfolgende Transaktionen nicht ausgeführt. Dadurch wird sichergestellt, dass mehrere Verbindungen gleichzeitig eingehen. Nur eine kann erfolgreich ausgeführt werden und die anderen geben alle Fehler zurück. Beispiel für den Fall, dass

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([
    &#39;scheme&#39; => &#39;tcp&#39;,
    &#39;host&#39;   => &#39;127.0.0.1&#39;,
    &#39;port&#39;   => 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([    &#39;scheme&#39; => &#39;tcp&#39;,    &#39;host&#39;   => &#39;127.0.0.1&#39;,    &#39;port&#39;   => 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

Zusammenfassung

Nachdem ich so viel gelesen habe, möchte ich kurz zusammenfassen, dass Redis tatsächlich keine Parallelitätsprobleme hat, 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.

Weitere Artikel zum Thema Redis finden Sie in der Spalte

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!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen