Maison > base de données > Redis > le corps du texte

Résoudre les problèmes de concurrence Redis

Libérer: 2019-11-26 15:06:11
avant
2357 Les gens l'ont consulté

Résoudre les problèmes de concurrence Redis

Problèmes de concurrence dans Redis

Redis est utilisé comme cache depuis longtemps, et Redis est un processus unique. Lors de l'exécution, les commandes sont exécutées les unes après les autres. J'ai toujours pensé qu'il n'y aurait pas de problèmes de concurrence. Ce n'est que lorsque j'ai vu les informations pertinentes aujourd'hui que je m'en suis soudain rendu compte (recommandé : tutoriel vidéo redis<.>)

Exemple de problème spécifique

Il existe une clé, en supposant que le nom est myNum et que des chiffres arabes y sont stockés. , et plusieurs connexions fonctionnent sur myNum. À ce stade, il y aura des problèmes de concurrence. Supposons qu'il y ait deux connexions linkA et linkB. Les deux connexions effectuent les opérations suivantes, suppriment la valeur de myNum, +1, puis enregistrez-la. Jetez un œil à l'interaction suivante :

linkA get myNum => 1
linkB get myNum => 1
linkA set muNum => 2
linkB set myNum => 2
Copier après la connexion

Après avoir effectué le. opération, le résultat pourrait être 2, ce qui est incompatible avec notre 3 attendu.

Regardez un exemple spécifique :

<?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);
}
Copier après la connexion

Définissez la valeur initiale de name sur 0, puis utilisez deux terminaux pour exécuter le programme ci-dessus en même temps. La valeur finale de name peut ne pas être 2000, mais une valeur <2000, cela prouve également l'existence de notre problème de concurrence ci-dessus. Comment résoudre ce problème ?

Transactions dans redis

Il existe également des transactions dans redis, mais cette transaction n'est pas aussi complète que dans mysql, et assure seulement la cohérence et L’isolement ne satisfait pas à l’atomicité et à la durabilité.

Les transactions Redis utilisent des commandes multi et exec

Atomicity Redis exécutera toutes les commandes de la transaction une fois et n'annulera pas même s'il y a un échec d'exécution au milieu. Les signaux d'arrêt, les temps d'arrêt de l'hôte, etc. provoquent l'échec de l'exécution de la transaction et Redis ne réessayera pas ou n'annulera pas.

Persistance, la persistance des transactions redis dépend du mode de persistance utilisé par redis Malheureusement, divers modes de persistance ne sont pas persistants.

Isolement, redis est un processus unique. Après avoir démarré une transaction, toutes les commandes de la connexion actuelle seront exécutées jusqu'à ce qu'une commande exec soit rencontrée, puis les commandes des autres connexions seront traitées. . Cohérence, après avoir lu la documentation, je trouve ça assez ridicule, mais ça semble correct.

Les transactions dans Redis ne prennent pas en charge l'atomicité, le problème ci-dessus ne peut donc pas être résolu.

Bien sûr, redis a également une commande watch, qui peut résoudre ce problème. Voir l'exemple suivant, exécuter watch sur une clé, puis exécuter la transaction. En raison de l'existence de watch, il surveillera la clé a. . Lorsqu'une fois corrigée, les transactions suivantes ne s'exécuteront pas. Cela garantit que plusieurs connexions arrivent en même temps, toutes les surveillances a. Une seule peut s'exécuter avec succès et les autres renvoient toutes des échecs. Exemple d'échec de

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"
Copier après la connexion

Dès la fin, on peut voir que la valeur de test a été modifiée par d'autres connexions :

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"
Copier après la connexion

Comment résoudre mon problème<. 🎜>Les commandes dans Redis sont atomiques, donc lorsque la valeur est un chiffre arabe, je peux changer les commandes get et set en incr ou incrby pour résoudre ce problème. Le code suivant ouvre deux terminaux à. en même temps Après exécution, le résultat est 2000 qui répond à nos attentes.

<?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);
}
Copier après la connexion

La méthode mentionnée par @manzilu

La méthode mentionnée par manzilu dans les commentaires a été vérifiée après vérification des informations. Elle est effectivement réalisable et l'effet n'est pas mauvais. . Voici un exemple

<?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";
Copier après la connexion

Exécutez shell php setnx.php & php setnx.php&, et vous obtiendrez enfin le résultat :

$ 花费时间 : 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
Copier après la connexion

Bouclez de la même manière 1w fois, supprimez usleep et utilisez incr. pour augmenter directement, ce qui prend environ 2 secondes.

Lors de l'annulation de usleep lors de l'obtention du revenu, le temps non seulement ne diminue pas, mais augmente également. Le réglage de usleep doit être raisonnable pour éviter que le processus ne fasse des boucles inutiles


Résumé

Après avoir tant lu, pour résumer brièvement, en fait, redis n'a pas de problèmes de concurrence car il s'agit d'un processus unique, et peu importe le nombre de commandes exécutées, elles sont exécutées une par une. Lorsque nous l'utilisons, des problèmes de concurrence peuvent survenir, comme la paire get et set.

Pour plus d'articles liés à Redis, veuillez prêter attention à la colonne

Tutoriel de base de données Redis

.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:cnblogs.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal