Джино: хостинг и веб-сервисы

Система Orphus
Russian version
Добавить на Del.icio.us
English version
Добавить на Digg.com

 dkLab | Конструктор | Dklab_SoapClient: параллельные запросы, реконнект, обработка тайм-аутов 

Карта сайта :: Форум «Лаборатории» :: Проект «Денвер»
Проект «Orphus» :: Куроводство: наблы :: Конструктор


2008-02-04
Обсудить на форуме

Принять участие в разработке библиотеки/утилиты можно на GitHub.

Dklab_SoapClient — это расширенная версия стандартного PHP-класса SoapClient, предназначенная для параллельного удаленного вызова процедур в высоконагруженных проектах.

Чайник 

Удаленная процедура — функция, расположенная на другой машине кластера. SOAP — протокол обмена структурированными сообщениями в распределённой вычислительной среде, стандартизированный Консорциумом W3C.

По сравнению со встроенным в PHP SoapClient, поддерживаются дополнительные возможности:

  • Одновременное, параллельное выполнение запросов к нескольким удаленным процедурам — ключевая особенность библиотеки. Если страница на вашем сайте собирается из 5 удаленных блоков, каждый из которых генерируется по 100ms, их можно запустить параллельно и получить всю страницу целиком не за 500ms, а за те же самые 100ms.
  • Реконнект при невозможности установления связи. К сожалению, мир несовершенен, и из-за случайной потери пакетов первая попытка соединения с SOAP-сервером может закончиться тайм-аутом. Это особенно часто происходит, когда проект располагается в нескольких датацентрах. Dklab_SoapClient позволяет задать тайм-аут на время открытия соединения (например, 1 секунду) и, в случае неудачи, повторить попытку указанное число раз. На практике это снижает вероятность итогового сбоя в тысячи раз, т.к. реконнект почти всегда помогает при утере пакета.
  • Поддержка тайм-аута на получение данных. Если страница собирается из удаленных блоков, то в случае "подвисания" одного из них "зависает" и вся страница. В то же время, отсутствие одного из блоков при наличии остальных — не такая большая беда. Вы можете указать, сколько времени Dklab_SoapClient должен ждать ответа от удаленной процедуры; если время превышено, возникает исключение PHP, которое вы можете обработать по своему усмотрению, не прерывая загрузку остальных блоков.

Код Dklab_SoapClient содержится в одном файле размером 20К. В нем поддерживаются также все возможности встроенного SoapClient (см. документацию):

  • Работа с Cookies. Одна процедура может вызвать setcookie(), а другая — прочитать значение этой cookie позже.
  • Работа с PHP-сессиями. Одна процедура пишет данные в сессию, вторая — читает.
  • Поддержка WSDL-схем и передача сложных бизнес-объектов.
  • Обработка исключений, возникающих в удаленной процедуре.

Совместимость и производительность

Говоря кратко, SOAP и класс SoapClient — наиболее удобный и в то же время производительный инструмент вызова удаленных процедур в PHP. Почему?

Чайник 

Если в вашей версии PHP нет классов SoapClient и SoapServer, проверьте, подключены ли у вас стандартные расширения soap и curl в php.ini.

SOAP — стандартный и популярный XML-протокол удаленного вызова процедур в Web. Для него имеется поддержка во всех языках программирования, поэтому вы можете писать SOAP-сервер на любом языке (например, C++, Java или, конечно же, на PHP), а в качестве клиента использовать Dklab_SoapClient.

Другое преимущество SOAP в PHP — возможность передачи объектов любой структуры. Например, если удаленная процедура вернула массив массивов объектов некоторого класса, то в клиентском коде вы получите точно такой же массив массивов объектов.

Класс PHP SoapClient написан на Си, а значит, имеет хорошие показатели производительности, несмотря на использование XML-протокола обмена данными.

Чайник 

К минусам протокола можно отнести его многословность. Однако, если вы пользуетесь стандартными библиотеками SoapClient и SoapServer (а Dklab_SoapClient основана как раз на встроенном в PHP классе SoapClient), вы эту многословность не ощутите.

Примеры использования

Код SOAP-сервера весьма прост

Написать на PHP SOAP-сервер в типичной ситуации очень просто: достаточно создать объект класса SoapServer и запустить у него метод обработки.

Листинг 1: SOAP-сервер: файл http://example.com/soapserver.php
<?php
// SOAP class to be used for request handling.
class MyServer
{
    public function getComplexData($some)
    {
        return array("obj" => (object)array("prop" => $some), "some" => "thing");
    }
    public function slowMethod($sleep)
    {
        sleep($sleep);
        return "slept for $sleep seconds";
    }
}
// Create and run the server.
$soapServer = new SoapServer(null, array('uri' => 'urn:myschema'));
$soapServer->setObject(new MyServer());
$soapServer->handle();

Чайник 

SOAP может еще работать в режиме использования WSDL. В нем вы должны явно специфицировать имена методов и типы параметров в специальном WSDL-файле. Этот режим более сложен в использовании, но в некоторых случаях дает определенные преимущества. См. документацию.

Пример: простой запрос через SOAP-клиент

Листинг 3: Простой запрос к SOAP-серверу {en: Simple query to SOAP server
<?php
require_once "../../lib/Dklab/SoapClient.php";
$client = new Dklab_SoapClient(null, array(
    'location' => "http://dklab.ru/lib/Dklab_SoapClient/demo/test/Dklab_SoapClient/soapserver.php",
    'uri' => 'urn:myschema',
    'timeout' => 3,
));
$data = $client->getComplexData(array("abc")); // call MyServer::getComplexData()
$text = $client->slowMethod(1);                // call MyServer::slowMethod()

echo "<pre>";
print_r($data);
print_r($text);

Листинг 4: Результат: видно, что структура параметра и результата сохранилась.
Array
(
    [obj] => stdClass Object
        (
            [prop] => Array
                (
                    [0] => abc
                )

        )

    [some] => thing
)
slept for 1 seconds

Пример: параллельные запросы (client->async->method())

Листинг 5: Параллельные запросы, каждый по 1 секунде
<?php
require_once "../../lib/Dklab/SoapClient.php";
$client = new Dklab_SoapClient(null, array(
    'location' => "http://dklab.ru/lib/Dklab_SoapClient/demo/test/Dklab_SoapClient/soapserver.php",
    'uri' => 'urn:myschema',
    'timeout' => 3,
));
// Send all the requests in parallel (note the "async" property).
$requests = array();
for ($i = 0; $i < 4; $i++) {
    $requests[] = $client->async->slowMethod(1);
}
// Now - print all results in 1 second, not in 4 seconds.
$t0 = microtime(true);
echo "<pre>";
foreach ($requests as $request) {
    echo $request->getResult() . "\n";
}
echo sprintf("Total time: %.2f seconds", microtime(true) - $t0);

Листинг 6: Результат: суммарное время - около 1 секунды, а не 4
slept for 1 seconds
slept for 1 seconds
slept for 1 seconds
slept for 1 seconds
Total time: 1.09 seconds

Пример: реконнект

Листинг 7: Три попытки реконнекта
<?php
require_once "../../lib/Dklab/SoapClient.php";
$client = new Dklab_SoapClient(null, array(
    'location' => "http://microsoft.com:8876", // non-existed address
    'uri' => 'urn:myschema',
    'response_validator' => 'responseValidator',
    'timeout' => 1,
));
echo "<pre>";
try {
    $client->someMethod();
} catch (Exception $e) {
    echo $e->getMessage() . "\n";
}

/**
 * Must return true if the response is valid, false if not and we need 
 * to reconnect, or throw an exception if attemts limit is reached.
 */
function responseValidator($response, $numberOfAttempt)
{
    if ($response['http_code'] != 200 || !strlen($response['body'])) {
        if ($numberOfAttempt < 3) {
            echo date("r") . ": Failed after $numberOfAttempt attempts, retrying...\n";
            return false;
        } else {
            throw new SoapFault("Client", date("r") . ": Exception: failed after $numberOfAttempt attempts!");
        }
    }
    return true;
}

Т.к. мы использовали несуществующий адрес http://microsoft.com:8876, даже 3 попытки соединения не увенчаются успехом. О чем и сообщается в приведенном ниже листинге с результатами работы скрипта:

Листинг 8: Результат: так и не соединились. Но если бы соединились, было бы хорошо.
Wed, 03 Feb 2009 23:49:55 +0300: Failed after 1 attempts, retrying...
Wed, 03 Feb 2009 23:49:56 +0300: Failed after 2 attempts, retrying...
Wed, 03 Feb 2009 23:49:57 +0300: Exception: failed after 3 attempts!

Пример: тайм-аут в данных

Листинг 9: Вызываем процедуру, работающую 4 секунды
<?php
require_once "../../lib/Dklab/SoapClient.php";
$client = new Dklab_SoapClient(null, array(
    'location' => "http://dklab.ru/lib/Dklab_SoapClient/demo/test/Dklab_SoapClient/soapserver.php",
    'uri' => 'urn:myschema',
    'timeout' => 1,
));
try {
    // 4 is greater than timeout, so an exception will happen.
    $t0 = microtime(true);
    $client->slowMethod(3);
} catch (Exception $e) {
    echo $e->getMessage() . sprintf(" in %.2fs", microtime(true) - $t0) . "\n";
}

Листинг 10: Результат: тайм-аут после 1 секунды.
Response is timed out in 1.03s

Аналоги и похожие технологии

Apache Thrift

Apache Thrift используется, например, социальной сетью Facebook. Преимущества: версионирование структуры передаваемых данных, поддержка реконнектов и пула соединений с учетом "умерших" серверов. Недостатки:

  1. Нет поддержки параллельных запросов в PHP-клиенте.
  2. Весьма "тяжелый" клиент, написанный на чистом PHP (в частности, реализация протокола написана на PHP, а не на Си).
  3. Общая сложность системы. Например, требуется явно задавать схему данных и генерировать по ней PHP-код. (Правда, во многих случаях это преимущество, но в части — лишние накладные расходы.)

cURL-multi

cURL-multi — это инструмент для выполнения параллельных HTTP-запросов, который используется в Dklab_SoapClient в качестве транспорта для SoapClient. Передавать с его помощью объекты напрямую нельзя, но можно их сериализовать и обмениваться упакованными данными (конечно, это не очень удобно).

XML-RPС

XML-RPC — еще один протокол удаленного вызова процедур. К сожалению, он не так богат возможностями, как SOAP. Например, вы не можете с его помощью отличить ассоциативный массив PHP от объекта PHP при передаче данных. Зато XML-представление данных в этом протоколе проще, чем в SOAP.

Резюме

При помощи библиотеки Dklab_SoapClient вы можете строить страницу вашего сайта из блоков, как из конструктора. Каждый блок запрашивается отдельно и независимо от других, при этом все запросы происходят параллельно. Если один из блоков не уложился в отведенное ему время (тайм-аут), то его можно не отображать на странице. (Кстати, по такому принципу изначально работает система XScript, разработанная в Яндексе. Однако она использует протокол CORBA.)

В отличие от Apache Thrift, класс Dklab_SoapClient не поддерживает возможность работы с пулом SOAP-серверов. Почему? Все очень просто: я верю, что организация пула серверов должна быть решена не на уровне клиента, а на уровне балансировщика запросов (например, HAProxy). В этом случае отключение "умерших" машин производит балансировщик, а не библиотека; это более надежно.

В настоящий момент библиотека имеет статус "бета" и еще недостаточно обкатана, хотя весь ее функционал (включая работу с параллельными запросами и тайм-аутами) покрыт тестами (около 20 сложных тестов). Имеется также плохо воспроизводимая проблема в Windows-версии стандартных PHP-модулей cURL-multi и SoapClient, приводящая иногда к падению PHP (в Unix-версии проблем не наблюдается).

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







Дмитрий Котеров, Лаборатория dk. ©1999-2016
GZip
Добавить на Del.icio.us   Добавить на Digg.com   Добавить на reddit.com