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
Regardez un exemple spécifique :
<?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); }
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.
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"
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([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]); for ($i = 0; $i < 1000; $i++) { $client->incr("name"); $client->expire("name", 10800); usleep(10000); }
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([ '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";
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é
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!