phpredisのエラーハンドラを記述する

私たちの会社では、キーに応じて特定のRedisインスタンスにリクエストを送信するプロキシ/ロードバランサーを作成することにしました。 すぐに完全に機能するものはないため、この同じバランサーを使用して(phpredisを使用して)大根を処理するphpで記述されたプロジェクトは、critical望の規則で重大なエラーでクラッシュしました。 残念ながら、プロキシは常に複雑なサーバー応答を正しく収集しませんでした...

10行ごとにコード内でRedisを操作し、すべての呼び出しをtryでラップし、わずかな望みはありませんでしたが、絶えずクラッシュするデバッグはあまり便利ではありませんでした。 それから、Redisオブジェクトを自分のものに置き換えるというアイデアが私に来ました。その内部から、実際のオブジェクトのすべてのメソッドをすでに呼び出しています...



存在しないオブジェクトメソッドを呼び出すときにアクセスされる素晴らしい__callメソッドがあるため、ソースクラスのすべてのメソッドを複製するのは非常に高価です。 入力時に、要求されたメソッドの名前と引数の配列を取得し、その後call_user_func_arrayを使用してソースオブジェクトの目的のメソッドを正常に呼び出します。 したがって、try、catchでラップし、call_user_func_arrayを1回呼び出すだけで済みます。

合計__callメソッドは次のとおりです。

public function __call($name, $arguments) { $i=0; while(true) { try{ return call_user_func_array(array($this->obj, $name), $arguments); break; } catch (Exception $e) { $this->handle_exception($e,$name,$arguments); if($i<5) $i++; else die('5 time redis lug'); } } }
      
      





エラーが発生した場合、それをハンドラーに送信し、同じメソッドを再度呼び出します。 5回の呼び出しに失敗した後、プロキシを苦しめるのをやめ、ログを吸います...



クラスの最初のバージョンは次のようになりました。

 class RedisErrHandler { private $obj; private $ip; private $port; private $timeout; public function __construct($ip,$port,$timeout=0) { $this->ip=$ip; $this->port=$port; $this->timeout=$timeout; $this->rconnect(); } private function rconnect() { $this->obj=new Redis; $this->obj->connect($this->ip,$this->port,$this->timeout) or die('Error connecting redis'); } public function __call($name, $arguments) { $i=0; while(true) { try{ return call_user_func_array(array($this->obj, $name), $arguments); break; } catch (Exception $e) { $this->handle_exception($e,$name,$arguments); if($i<5) $i++; else die('5 time redis lug'); } } } private function handle_exception($e,$name,$args) { $err=$e->getMessage(); $msg="Caught exception: ".$err."\tcall ".$name."\targs ".implode(" ",$args)."\n"; if($_SERVER['LOG']) { $handle2=fopen('redis_log.txt','a'); fwrite($handle2,date('H:i:s')."\t$msg"); fclose($handle2); } echo $msg; if(substr(trim($err),0,37)=='Caught exception: protocol error, got') die('bye'); $this->rconnect(); } }
      
      





彼は出発のたびに再接続し、「プロトコルエラー」エラーで出発時に「死亡」しました。これはまさに私たちが探していたエラーだったからです。



統合のために、すべてを置き換える必要がありました

 $r=new Redis(); $r->connect('127.0.0.1',6379,10);
      
      





 $r=new RedisErrHandler('127.0.0.1',6379,10);
      
      





このオプションは、しばらくの間、multiで作業しているときにスクリプトがクラッシュするまでは正常に機能しました。 phpredisのトランザクションには個別のオブジェクトが割り当てられているため、そのためのラッパーも記述する必要があることが明らかになりました。

最初に、上記のクラスにmultiメソッドが追加されました。

 public function multi($type) { return new RedisMultiErrHandler($this->obj,$type,$this->ip,$this->port,$this->timeout); }
      
      





さて、前のものと同様に、トランザクションオブジェクトのエラーを処理するためのクラスが作成されています。

 class RedisMultiErrHandler { private $obj; private $ip; private $port; private $timeout; private $m; private $type; private $commands; public function __construct(&$redis,$type,$ip,$port,$timeout=0) { $this->ip=$ip; $this->port=$port; $this->timeout=$timeout; $this->type=$type; $this->obj=$redis; $this->m=$this->obj->multi($type); } private function rconnect() { $this->obj=new Redis; $this->obj->connect($this->ip,$this->port,$this->timeout) or die('Error connecting redis'); $this->m=$this->obj->multi($this->type); } public function __call($name, $arguments) { $this->commands[]=array('name'=>$name, 'arguments'=>$arguments); return $this; } private function handle_exception($e) { $err=$e->getMessage(); $msg=''; foreach($this->commands as $command) { $msg.="Multi sent\tcall ".$command['name']."\targs ".implode(" ",$command['arguments'])."\n"; } $msg.="Caught exception: ".$err."\n"; if($_SERVER['LOG']) { $handle2=fopen('redis_multi_log.txt','a'); fwrite($handle2,date('H:i:s')."\t$msg"); fclose($handle2); } echo $msg; if(substr(trim($err),0,37)=='Caught exception: protocol error, got') die('bye'); $this->rconnect(); } public function exec() { $i=0; while(true) { foreach($this->commands as $command) { call_user_func_array(array($this->m, $command['name']), $command['arguments']); } try{ return $this->m->exec(); break; } catch (Exception $e) { $this->handle_exception($e); if($i<5) $i++; else die('5 time mredis lug'); } } } }
      
      







出発時にすべてのトランザクションコマンドを再送信できるようにするため、トランザクションを直接完了するexec()を除くすべての呼び出しが配列に入力され、後者が呼び出されたときにサーバーに送信されました。 破棄は、コード内では使用されません。これは、クラスでは個別に破棄できないためです。



非常にまれですが、大根の接続がプロキシを使用しなくてもフリーズすることがあることを考慮すると、ラッパーデータは今日まで正常に使用されています。



All Articles