1. Parallelitätsprobleme
Jeder weiß, was Parallelität ist. Hier geht es um mehrere gleichzeitige Anfragen zur Belegung derselben Ressource.
Anfrage: index.php?mod =a&action =b&taskid=6 Verarbeitung:
$key = "a_b::".$uid.'_'.$taskid; $v = $redis->get($key); if($v == 1){ $redis->setex($key,10,1); //处理逻辑省略 }
2. Analyse
Es stellt sich heraus, dass zwei identische Anfrageergebnisse in die Datenbank geschrieben wurden Die aufgezeichnete Zeit. Oh mein Gott! Ich habe Microtime (true) verwendet, um den Zeitunterschied zwischen den beiden Anfragen zu protokollieren. Das heißt, $redis ->setex($key,10,1); wurde nicht erfolgreich ausgeführt. Die zweite Anfrage hat das gleiche Ergebnis wie die erste Anfrage erhalten. Ist das nicht die legendäre gleichzeitige Ressourcenpräemption? Ich habe schon oft von dieser Situation gehört und sie während des Entwicklungsprozesses nicht absichtlich simuliert.
3. Lösung
Lösung 1: Die erste Reaktion besteht darin, dem Verarbeitungsprozess eine Transaktion hinzuzufügen (die Datenbank ist MySQL innoDB). Das Ergebnis des Hinzufügens der Transaktion ist, dass die erste Anfrage erfolgreich ist und die zweite Anfrage ist erfolgreich. Sie wird erst später ausgeführt und wenn festgestellt wird, dass sie schwer ist, wird sie zurückgesetzt. Tatsächlich sind MySQL-Transaktionen sehr gut darin, die Datenkonsistenz sicherzustellen, aber es ist zu teuer, die ausschließliche Nutzung eindeutiger Ressourcen durch Rollback sicherzustellen. Schüler, die MySQL-Transaktionstests durchgeführt haben, wissen, dass die Einfügung in die Transaktion bereits eingefügt wurde und ein Rollback durchgeführt wird Es wurde später gelöscht.
Option 2: Eine weitere Option ist die exklusive Dateisperre in PHP. Das heißt, in diesem Fall muss ich eine neue Datei mit der Anzahl der Benutzer * der Anzahl der Aufgaben erstellen, um die Exklusivität zu erreichen Jede angeforderte Ressource. Wenn die exklusive Ressource größer ist. Wenn es weniger gibt, optionale Lösungen:
/** * 加锁 */ public function file_lock($filename){ $fp_key = sha1($filename); $this->fps[$fp_key] = fopen($filename, 'w+'); if($this->fps[$fp_key]){ return flock($this->fps[$fp_key], LOCK_EX|LOCK_NB); } return false; } /** * 解锁 */ public function file_unlock($filename){ $fp_key = sha1($filename); if($this->fps[$fp_key] ){ flock($this->fps[$fp_key] , LOCK_UN); fclose($this->fps[$fp_key] ); } }
Option 3: Es wurde festgestellt, dass $redis->setnx() den Status atomarer Operationen bereitstellen kann: derselbe Schlüssel ist nach der Ausführung von setnx nicht abgelaufen oder gelöscht und wird erneut ausgeführt. Geben Sie false zurück. Dadurch können mehr als zwei gleichzeitige Anforderungen gesteuert werden und die Sperre muss erfolgreich erworben werden, bevor sie fortgesetzt werden können.
/** * 加锁 */ public function task_lock($taskid){ $expire = 2; $lock_key ='task_get_reward_'.$this->uid.'_'.$taskid; $lock = $this->redis->setNX($lock_key , time());//设当前时间 if($lock){ $this->redis->expire($lock_key, $expire); //如果没执行完 2s锁失效 } if(!$lock){//如果获取锁失败 检查时间 $time = $this->redis->get($lock_key); if(time() - $time >= $expire){//添加时间戳判断为了避免expire执行失败导致死锁 当然可以用redis自带的事务来保证 $this->redis->rm($lock_key); } $lock = $this->redis->setNX($lock_key , time()); if($lock){ $this->redis->expire($lock_key, $expire); //如果没执行完 2s锁失效 } } return $lock; } /** * 解锁 */ public function task_unlock($taskid){ $this->set_redis(); $lock_key = 'task_get_reward_'.$this->uid.'_'.$taskid; $this->redis->rm($lock_key); }
Erklären Sie, dass die beiden Vorgänge setNX und Expire tatsächlich Redis-Transaktionen verwenden können, um Konsistenz sicherzustellen