(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Пишем обработчик ошибок для phpredis

Источник: habrahabr
habrahabr

Пишем обработчик ошибок для phpredis

Началось все с того, что у нас в компании решили сделать прокси/балансировщик нагрузки который бы, в зависимости от ключа, отправлял запрос на тот или иной инстанс Redis'а. Так как идеально сразу ничего не работает, то написанный на php проект, работающий с редисом(с помощью phpredis) через этот самый балансировщик, с завидной регулярности вылетал с критическими ошибками. Увы прокси не всегда правильно собирал сложные ответы сервера…
Работа с Redis'ом в коде через каждых 10 строк, и оборачивать каждый вызов в try, catch не было ни малейшего желания, но и с постоянными вылетами дебажить было сильно не удобно. Тут мне и пришла в голову идея подменить объект Redis'a своим, изнутри которого я бы уже вызывал все методы настоящего объекта…

Естественно дублировать все методы исходного класса сильно накладно, да и не зачем, ибо существует замечательный метод __call, к которому идет обращение, при вызове несуществующего метода объекта. На вход мы получаем имя запрашиваемого метода и массив аргументов, после чего успешно вызываем с помощью call_user_func_array, нужный метод исходного объекта. Таким образом оборачивать в try, catch нам надо лишь один вызов call_user_func_array.
Итого метод __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();
    }
    
}

Он реконнектился при каждом вылете и "умирал" при вылете с ошибкой "protocol error", ибо именно на такие ошибки мы и охотились.

Для его интеграции надо было всего то заменить

$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(), которая непосредственно завершает транзакцию, заносились в массив и отправлялись на сервер при вызове последней. Discard у нас в коде не используется потому в классе его отдельно не выносил.

Учитывая, что иногда, хоть и крайне редко, коннект с редисом зависает даже без использования прокси, то данные обертки успешно используются и по сей день.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 09.02.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Комплект Dr.Web «Универсальный», 1 год, 5 ПК
Quest Software. SQL Navigator for Oracle
NERO 2016 Classic ESD. Электронный ключ
Microsoft 365 Business Standard (corporate)
ABBYY Lingvo x6 Европейская Профессиональная версия, электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Компьютерные книги. Рецензии и отзывы
Один день системного администратора
Мастерская программиста
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100