Maison  >  Article  >  base de données  >  Résoudre les problèmes de concurrence Redis

Résoudre les problèmes de concurrence Redis

尚
avant
2019-11-26 15:06:112355parcourir

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

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);
}

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"

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"

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);
}

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";

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

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer