(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
СУБД Линтер Бастион. Серверная лицензия. 5 клиентских подключений
IBM DOMINO ENTERPRISE CLIENT ACCESS LICENSE AUTHORIZED USER LICENSE + SW SUBSCRIPTION & SUPPORT 12 MONTHS
Zend Guard 1 Year Subscription
ABViewer Enterprise пользовательская
SAP Crystal Reports XI R2 Dev 2006 INTL WIN NUL License (Version 11)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Один день системного администратора
Каждый день новые драйверы для вашего компьютера!
Мастерская программиста
Краткие описания программ и ссылки на них
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100