Problèmes de concurrence dans Redis
Redis est utilisé comme cache depuis longtemps Redis s'exécute comme un processus unique et les commandes sont exécutées les unes après les autres. Je pensais qu'il n'y aurait pas de problèmes de concurrence, mais ce n'est que lorsque j'ai vu les informations pertinentes aujourd'hui que je m'en suis soudain rendu compte.
Exemple de problème spécifique
Il existe une clé, en supposant que le nom est myNum et que les chiffres arabes qui y sont stockés sont, en supposant que la valeur actuelle est 1. Si plusieurs connexions fonctionnent sur myNum, 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 => 1linkB get myNum => 1linkA set muNum => 2linkB 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 :
<?phprequire "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);}
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 l'être. être 2000, mais un < ;La valeur de 2000 prouve 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 Elle garantit seulement la cohérence et l'isolement et ne satisfait pas à l'atomicité. .sexe et durabilité.
la transaction redis utilise multi, exec commande
atomicité, redis exécutera toutes les commandes de la transaction, même s'il y a une exécution au milieu Il n’y aura pas de retour en arrière en cas d’échec. 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 lecture du document, je trouve que c'est 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 ci-dessous pour exécuter watch sur une clé puis exécuter la transaction. En raison de l'existence de watch, il surveillera la clé a. Lorsque a est réparé, les transactions suivantes ne pourront pas s'exécuter. 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 renverront un échec. Exemple d'échec de
127.0.0.1:6379> set a 1OK127.0.0.1:6379> watch aOK127.0.0.1:6379> multi OK127.0.0.1:6379> incr aQUEUED127.0.0.1:6379> exec1) (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 1OK127.0.0.1:6379> watch testOK127.0.0.1:6379> multiOK127.0.0.1:6379> incrby test 11QUEUED127.0.0.1:6379> exec(nil) 127.0.0.1:6379> get test"100"
Comment résoudre le problème
redis La commande in est atomique, 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 démarre deux terminaux pour une exécution en même temps. , et le résultat est que nous attendions 2000.
<?phprequire "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);}
C'est en effet réalisable et l'effet est plutôt bon 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 le 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 l'usleep lors de l'obtention du revenu, le temps non seulement ne diminue pas, mais augmente. Le réglage de cet usleep doit être raisonnable pour éviter que le processus ne fasse des boucles inutiles
Résumé
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.
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!