Генеральный спонсор: Хостинг «Джино»

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

 dkLab | Конструктор | Dklab_Realplexor: Comet-сервер промышленного масштаба с API для PHP и JavaScript 

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


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

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

Dklab Realplexor — это Comet-сервер, позволяющий держать одновремено сотни тысяч долгоживущих открытых HTTP-соединений с браузерами пользователей. JavaScript-код, запущенный в браузере, подписывается на один или несколько каналов Realplexor-а и вешает обработчик на поступление данных. Сервер может в любой момент записать сообщение в один из таких каналов, и оно будет моментально передано всем подписчикам (хоть одному, хоть тысяче), в режиме реального времени и с минимальной нагрузкой для сервера.

Хотя идейным вдохновителем Realplexor-а был предыдущий проект, dklab_multiplexor, код Realplexor-а не имеет с ним практически ничего общего. Поэтому я и решил сменить название. Несопоставимы также возможности продуктов (см. ниже), да и размер кода увеличился в 7 раз.

Чайник 

Realtime-направление сейчас довольно активно развивается на Западе, и в нем особенно выделяется продукт Tornado — событийно-ориентированный веб-сервер на языке Python. Правда, Tornado — это не столько Comet-сервер, сколько инструмент, с помощью которого можно запрограммировать "в том числе" и Comet-сервер. Ключевые слова: Comet, Push Server, Long polling, JavaScript, XMLHttpRequest.

Главные преимущества Realplexor-а:

  • простота использования: наличие API для JavaScript, API для PHP (в будущем — и для других языков);
  • простота конфигурирования;
  • широкий функционал (либо отстутствующий, либо недоступный напрямую в аналогах).

Лучше один раз увидеть...

Я сделал отдельную онлайн-песочницу, чтобы продемонстрировать функционал нового Realplexor-а и то, для чего вообще нужны Comet-серверы (кстати, это физически тот же самый демон Realplexor-а, что использует мой новый стартап РуТвит). Песочница реализует что-то типа многоканального чата: зайдя, вы получите как будто бы 2 независимых "браузера", запущенных на разных компьютерах.

  • Верхний "браузер" отображает каналы — в них моментально появляются новые сообщения, как только кто-то их туда отправляет на стороне сервера. Конечно же, эту страницу могут просматривать одновременно сотни тысяч пользователей, и они все будут видеть одно и то же (реализовано с использованием Realplexor JavaScript API). Можно "на лету" добавлять новые каналы (подписка) или скрывать уже имеющиеся (отписка).
  • Нижний браузер содержит формы, позволяющие добавлять сообщение в произвольный канал, указав его имя. Форма AJAX-ом отправляется на сервер, и уже там PHP-скрипт записывает в Realplexor полученный текст через PHP API. (И да, так можно чатиться.)

По умолчанию на странице открыто 3 канала с именами Alpha, Beta и RuTvit. Но, конечно, вы можете закрыть эти каналы и открыть новые. Вот, например, страница с единственным открытым каналом по имени Habrahabr: /redir?http://rutvit.ru/realplexor/demo?ids=Habrahabr.

Песочница демонстрирует следующие функции Realplexor-а:

  • Одновременное прослушивание нескольких каналов с динамическим добавлением/удалением каналов.
  • Одновременную отправку сообщения в несколько каналов (фактически, получился чат).
  • Обновление "на лету" списка онлайн-каналов (каналов, которые слушает хотя бы один пользователь).
  • Искользование JavaScript API и PHP API, которые идут в поставке Realplexor-а.

Ниже приведены выдержки из кода этой песочницы (с точностью до верстки и с адаптацией под данную статью), иллюстрирующие применение API Realplexor-а.

Листинг 1: Интересные выдержки из кода песочницы: JavaScript
// Create Dklab_Realplexor client.
var realplexor = new Dklab_Realplexor(
    "http://rpl.YourSite.com/",  // Realplexor's engine URL; must be a sub-domain
    "demo_" // namespace
);

// Subscribe a callback to channel Alpha.
realplexor.subscribe("Alpha", function (result, id) {
    alert(result);
});

// Subscribe a callback to channel Beta.
realplexor.subscribe("Beta", function (result, id) {
    div.innerHTML = result;
});

// Apply subscriptions. Сallbacks are called asynchronously on data arrival.
realplexor.execute();

Листинг 2: Интересные выдержки из кода песочницы: PHP
// Create new API object to access Realplexor server.
require_once "Dklab/Realplexor.php";
$rpl = new Dklab_Realplexor("127.0.0.1", "10010", "demo_");
...
// Send data to channels. 
$rpl->send(array("Alpha", "Beta"), $_POST['message']);

Установка Realplexor-а

После скачивания dklab_realplexor.tar.gz его можно установить как автозапускаемый сервис Linux:

cd /opt
wget http://github.com/DmitryKoterov/dklab_realplexor/tarball/master
tar zxf *realplexor*.tar.gz
mv *realplexor*/ dklab_realplexor

# Now deal with an init-script.
ln -s /opt/dklab_realplexor/dklab_realplexor.init /etc/init.d/dklab_realplexor
chkconfig --add dklab_realplexor
chkconfig dklab_realplexor on
service dklab_realplexor start

Как мы используем Realplexor в РуТвите

Realtime-поиск и отслеживание онлайн-статуса канала. При просмотре результатов поиска твиты, удовлетворяющие запросу, появляются сверху в реальном времени (как в твиттер-поисках Google и Bing, а также в FriendFeed). Чтобы обеспечить этот функционал, сервер в каждый момент должен иметь информацию об "активных поисках", что обеспечивает соответствующая функция PHP API.

Подписка на канал. Когда вы смотрите на любую ленту твитов (свою, публичную и т. д. — по всему сайту), для нее используется канал Realplexor-а. Поэтому новый твит появляется у всех пользователей одновременно и без перезагрузки страницы.

Подписка на много каналов одновременно. В режиме просмотра а-ля FriendFeed, когда сообщения группируются в ветки, каждая ветка — это подписка на отдельный канал Realplexor-а (т.е. браузер подписан на десятки и иногда даже сотни каналов одновременно в одном соединении). Множественная подписка — это ключевой функционал Realplexor-а: через него реализованы, например, Like/Unlike (или ретвит), личные сообщения и т. д.

Отправка сообщения в несколько каналов сразу. Когда пользователь написал твит, сообщение отправляется одновременно в его персональный канал, во все home-каналы его друзей и в public-канал за один Realplexor-запрос.

Передача сообщений произвольной структуры. PHP-скрипт РуТвита формирует сообщения Realplexor-а в виде вложенных ассоциативных массивов, которые прозрачно преобразуются в JavaScript-объекты на стороне браузера.

Запуск Realplexor-а на поддомене

Лирическое отступление 
Вы наверняка захотите в будущем менять конфигурацию Realplexor-а. Можно и напрямую редактировать файл dklab_realplexor.conf, однако в этом случае вам будет трудно обновлять версии сервера. Рекомендованный способ — не трогать dklab_realplexor.conf в директории Realplexor-а, а создать отдельный файл, /etc/dklab_realplexor.conf, и в него записать директивы, перекрывающие стандартные настройки. При старте Realplexor считывает сначала конфиг-файл из своей директории, а затем — из /etc.

Итак, Realplexor запущен. Чтобы к нему могли подсоединяться клиенты, нужно сделать, чтобы он стал доступен на поддомене вашего сайта — например, по адресу вида http://rpl.YourSite.com.

Чайник 

Расположение на поддомене является обязательным. Дело в том, что популярны браузеры позволяют держать не более 2 одновременных соединений с каждым доменом. Если мы запустим Realplexor на том же домене, что и наш сайт, то одно соединение будет постоянно "занято", и на подгрузку картинок, JS- и CSS-файлов останется всего одно соединение, что замедлит работу сайта. Если же Realplexor расположен на отдельном домене, этой проблемы нет.

По умолчанию Realplexor слушает адрес *:8088 (параметр WAIT_ADDR конфигурации). Существует 2 способа вывести его на поддомен.

  1. Выделить в DNS этому поддомену отдельный IP-адрес и "повесить" Realplexor на его порт 80 (кстати, если у вас число онлайн-клиентов будет превышать примерно 60000, то можно указать несколько IP-адресов, чтобы увеличить число соединений):
    cat > /etc/dklab_realplexor.conf
       $CONFIG{WAIT_ADDR} = [ 'rpl.YourSite.com:80' ];  # IP address of rpl.YourSite.com
       return 1;
    ^D
  2. Проксировать обращения к Realplexor-у через nginx (если у вас нет nginx, выбирайте п. 1):
    server {
      server_name rpl.YourSite.com;
      listen rpl.YourSite.com:80;
      location / {
        proxy_pass http://127.0.0.1:8088;
      }
    }

Готово? Даем команду на перезапуск Realplexor-а:

service dklab_realplexor reload

# Проверяем, что Realplexor доступен: если соединене 
# перешло в режим ожидания, то все OK.
wget http://rpl.YourSite.com

Где искать логи

По умолчанию init-скрипт Realplexor-а пишет логи при помощи syslog, используя facility = local3.info. В типичном Linux-дистрибутиве строчки лога попадут в файл /var/log/messages (или /var/log/syslog в Debian), но вы можете перенастроить демон syslogd так так, чтобы для Realplexor-а был выделен отдельный файл: man syslogd.

Кроме того, можно регулировать детализацию сообщений (параметр VERBOSITY в dklab_realplexor.conf). По умолчанию включен самый подробный режим логирования — отладочный; в нем лог может легко занять несколько гигабайтов за день.

Ну и есть еще один способ запуска Realplexor-а (при остановленном сервисе). Вот так:

perl ./dklab_realplexor.pl /etc/dklab_realplexor.conf

Realplexor считает дополнительную конфигурацию из /etc/dklab_realplexor.conf и будет выводить логи прямо в STDOUT.

Возможности Dklab Realplexor

В списке ниже значком (M) помечен функционал, который имелся в dklab_multiplexor ранее; все остальное — новые возможности Realplexor-а.

  • (M) Поддержка сотен тысяч одновременных соединений из JavaScript: однопоточная событийная модель на основе libev.
  • Готовые реализации API для разных языков:
    • API для JavaScript: dklab_realplexor.js. Подписка. Защита от ошибок в обработчиках. "Умный" реконнект. Отладочные возможности.
    • API для PHP: класс Dklab_Realplexor — высокоуровневая реализация протокола.
    • Передача с сервера в браузер объектов любой структуры с автоматическими преобразованиями типов (протокол JSON).
    • (В будущем добавятся реализации API для других серверных языков.)
  • Работа с группой подписок на клиенте:
    • Буферизация сообщений и их гарантированная доставка в браузер (метод репликации).
    • Подписка одновременно на несколько каналов в одном соединении.
    • Скрытие "чужих" ID каналов при получении сообщения: каждый клиент изолирован и не имеет информации о других.
    • Упаковка нескольких сообщений в один HTTP-запрос (ускоряет работу при интенсивном потоке данных).
  • Работа с группой сообщений при отправке:
    • (M) Отправка сообщения сразу в несколько каналов за одну операцию.
    • Ручное ограничение списка получателей сообщения (для реализация закрытых подписок).
    • Поддержка пространств имен ID каналов для организации независимых подсервисов.
  • Служебные команды для сервера:
    • (M) Получение списка online-каналов.
    • Слежение на сервере за событиями "вышел в online / ушел в offline" в режиме реального времени (репликация).
    • Выборка результатов только из набора каналов, определяемого префиксами имен каналов.
  • Работа с демоном Realplexor в Linux:
    • (M) Встроенный watchdog демона, рестартующий сервер при аварии.
    • Переключение на непривилегированного пользователя.
    • Graceful Reload при изменении конфигурации.
    • Легкая инсталляция в Linux: прилагается скрипт для /etc/init.d.
    • Возможность работы сервера напрямую, без проксирования nginx-ом или lighttpd.
  • Прочее:
    • Более 40 автотестов на серверную часть (на сам сервер и на PHP API).
    • Более 10 автотестов на JavaScript API.
    • Демонстрационный скрипт, иллюстрирующий работу с несколькими каналами одновременно.
    • Лицензия GPL v2.

Серверный API: PHP

Realplexor поставляется с готовым API для языков JavaScript и PHP, что позволяет подключить его к проекту, написав всего несколько строчек кода. Это API покрывает большинство функционала, предоставляемого Realplexor-ом.

Серверный API позволяет отправлять сообщения в каналы Realplexor-а из скриптов на PHP.

constructor($host, $port, $namespace): API

Для начала нужно создать объект Dklab_Realplexor, через который будет происходить отправка данных в Realplexor. Использование пространств имен не обязательно, однако с их применением вы сможете организовать на одном и том же Realplexor-сервере непересекающиеся наборы каналов (как это сделано, например, на демо-странице — там используется пространство имен "demo_").

Листинг 3: Подключение API
require_once "Dklab/Realplexor.php";
$rpl = new Dklab_Realplexor(
    "127.0.0.1", // host at which Realplexor listens for incoming data
    "10010",     // incoming port (see IN_ADDR in dklab_realplexor.conf)
    "nsp"        // namespace to use (optional)
);

send($ids, $data): Отправка данных в канал

Листинг 4: Отправка данных в канал
// Send data to one channel.
$rpl->send("Alpha", array("here" => "is", "any" => array("structured", "data"));

// Send data to multiple channels at once.
$rpl->send(array("Alpha", "Beta"), "any data");

Данные могут иметь произвольную структуру: когда они придут клиенту, Realplexor создаст для них соответствующее JavaScript-представление. Так что вы сможете работать с тем, что исходно было PHP-массивом, как с JavaScript-объектом.

send($ids, $data, $limiterIds): ограничение получателей

При отправке данных можно указать, что их имеет право получить не любой клиент, а только один из перечисленных. Вот как это делается:

Листинг 5: Отправка данных в канал только для указанных клиентов
// Send data limiting receivers.
$rpl->send("Alpha", "any data", array($id1, $id2, ...));

Здесь $id1, $id2 и т. д. — это "идентификаторы клиентов", которым будет "видно" данное сообщение. Остальные клиенты его не получат.

Идентификатор клиента — обычное имя, которое по совместительству же является и именем "персонального канала" этого клиента. Чтобы сообщить о том, что соединение имеет тот или иной идентификатор, нужно просто подписать его на соответствующий канал. Забегая вперед, вот какой код нужно выполнить в JavaScript, чтобы клиент начал слушать канал Alpha и при этом сообщил, что его идентификатор — id_fe2d7578b8ac760:

realplexor.subscribe("id_fe2d7578b8ac760", function() {});
realplexor.subscribe("Alpha", function(data) { alert(data) });

Чайник 

Конечно, идентификатор (в нашем случае — id_fe2d7578b8ac760) нужно делать случайным
и уникальным для каждого клиента, чтобы никто не смог его подобрать.

send($idsAndCursors, $data): работа с курсором

Каждое сообщение, попадающее в Realplexor, должно быть снабжено уникальным и монотонно возрастающим во времени маркером — значением курсора. Если вы не указали вручную это значение при добавлении данных:

Листинг 6: Отправка данных с явным указанием значения курсора
// Send data with manual cursor specification (10 and 20).
$rpl->send(array("Alpha" => 10, "Beta" => 20), "any data");

то сервер Realplexor самостоятельно присваивает значение курсора блоку данных, исходя из текущего системного времени с микросекундами.

Лирическое отступление 
Значение курсора — вещестенное число высокой разрядности, как минимум с 7 знаками после запятой. Возможно, вам будет интересно узнать, что для хранения такого числа недостаточно точности типа double, "родного" для языков Perl и PHP (из-за того, что число секунд, прошедших с 1 января 1970 года, приближается к 2 миллиардам). Поэтому курсоры представлены в коде в виде строковых значений; не пытайтесь конвертировать их в double, иначе образуются коллизии.

Для чего же нужны курсоры? Для нескольких вещей.

  • При отправке данных клиенту Realplexor сортирует сообщения по возрастанию курсоров (т.е. в хронологическом порядке).
  • JavaScript-код организовывает гарантированный прием данных (по методу репликации), запоминая позицию курсора после приема и делая следующий запрос, начиная только с этой позиции. Об этом мы подробно поговорим позже, при рассмотрении JavaScript API.

Чайник 

Лучше всегда явно указывать значение курсора при отправке. Для этого можно выбрать любую монотонно возрастающую последовательность. Например, возьмите ID сообщения в базе данных (если сообщения хранятся в БД) или же время, возвращаемое мастер-сервером СУБД. Текущее системное время в качестве курсора брать опасно, потому что, если ваш сайт работает сразу на нескольких серверах, время на них может идти не "секунда в секунду".

cmdOnline($prefixes): список онлайн-каналов

Листинг 7: Получение списка онлайн-каналов
// Get the list of all listened channels.
$list = $rpl->cmdOnline();

// Get the list of online channels which names are started with "id_" only.
$list = $rpl->cmdOnline(array("id_"));

Канал называется "онлайн-каналом", если:

  1. его сейчас слушает хотя бы один клиент;
  2. в настоящее время соединений нет, однако последний клиент отсоединился "совсем недавно" и, вполне возможно, просто сейчас загружает другую страницу сайта (это "совсем недавно" задается в секундах параметром конфигурации OFFLINE_TIMEOUT)

В примере выше мы получаем список онлайн-каналов с именами вида id_***. Помните, мы выше договорились использовать такие имена каналов для уникальной идентификации клиентов?

cmdWatch($pos, $prefixes): слежение за онлайн-каналами

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

Листинг 8: Слежение за изменением онлайн-статусов
$pos = 0;
while (1) {
    echo "Watching status changes for channels 'id_***' from $pos...\n";
    foreach ($rpl->cmdWatch($pos, "id_") as $event) {
        echo "Received: {$event['event']} - {$event['id']}\n";
        $pos = $event['pos'];
    }
    usleep(300000);
}

При очередном запуске cmdWatch($pos) возвращает массив троек (event, pos, id), где event — это строки "online" или "offline" (в будущих версиях могут добавиться и другие события), id — имя канала, а pos — позиция, с которой нужно в следующий раз запускать cmdWatch(), чтобы получить новый блок данных.

В качестве второго параметра cmdWatch($pos, $ids) можно указать массив префиксов имен каналов, которые будут отслеживаться этим вызовом.

Клиентский API: JavaScript

JavaScript API позволяет подключить Realplexor к сайту, написав всего несколько строчек кода.

constructor(url, namespace): Подключение API

Листинг 9: Подключение JS API Realplexor-а
<script type="text/javascript" src="/path/to/dklab_realplexor.js"></script>
...
// Create Dklab_Realplexor client.
var realplexor = new Dklab_Realplexor(
    "http://rpl.YourSite.com/",  // Realplexor's engine URL
    "demo_" // namespace (optional)
);

Лирическое отступление 
Если вы не хотите хранить файл dklab_realplexor.js (входит в дистрибутив) у себя в директории документов, то обратитесь напрямую к серверу Realplexor-а: он выдаст этот JS-файл самостоятельно (но это менее надежно, т.к. в случае, если сервер Realplexor-а по каким-то причинам будет остановлен, вы получите JavaScript Error на странице): <script type="text/javascript" src="http://rpl.YourSite.com/?identifier=SCRIPT"></script>

subscribe(id, callback): Подписка на канал

Листинг 10: Подписка на каналы
<div id="first" style="border:1px solid black; width:30%; float:left"></div>
<div id="second" style="border:1px solid black; width:30%; float:left"></div>
...
// Subsctibe to channels.
realplexor.subscribe("Alpha", function(data, id) {
    $('#first').innerHTML += data + "<br>";
});
realplexor.subscribe("Beta", function(data, id) {
    $('#second').innerHTML += data + "<br>";
});
realplexor.execute();

Этот пример регистрирует callback-обработчики, чтобы они запускались при поступлении данных в каналы Alpha и Beta. (См. живой пример на странице-песочнице.)

Лирическое отступление 
Если в какой-то канал поступает много сообщений, то Realplexor отправляет их клиентам "пачками", в одном и том же соединении. Но это происходит "прозрачно" для обработчиков, слушающих канал: они в любом случа вызываются по одному разу для каждого сообщения.

Обратите внимание, что после регистрации всех "слушателей" нужно явно сообщить об этом Realplexor-у, вызвав метод realplexor.execute(). Это же нужно делать при любом изменении подписок.

unsubscribe(id, callback): Отмена подписки на канал

Листинг 11: Подписка на каналы
// Remove one specified callback from a channel subscriprion.
realplexor.unsubscribe("Alpha", ourCallback);
realplexor.execute();

// Remove all callbacks from a channel subscription.
realplexor.unsubscribe("Alpha", null);
realplexor.execute();

Подписку на канал можно отменить — либо персонально для какого-то обработчика, либо для всех слушателей этого канала. Не забудьте вызвать execute(), когда закончите все отмены.

setCursor(id, cursor): Установить позицию курсора

Прежде чем говорить о курсорах, рассмотрим одну неприятную проблему, которая часто возникает при работе с Comet-серверами. Пользователь имеет обыкновених ходить по страницам сайта. И в промежуток времени, когда старая страница уже закрылась, а новая еще не успела загрузиться, в канал вполне могут прийти сообщения. Как пользователь о них узнает, когда страница дозагрузится, а Realplexor-клиент установит новое "долгоиграющее" соединение с сервером?

Очевидно, что для получения сообщений "без потерь" должно выполняться два условия:

  1. Сервер должен "буферизовать" сообщения, отправляемые в тот или иной канал. Даже если у канала сейчас нет ни одного слушателя, они вполне могут появиться в следующую секунду.
  2. Клиент при отключении должен каким-то образом сохранять ту "позицию" в канале, на которой он слушал в последний раз. А при подключении — сообщать серверу, что его интересуют все сообщения с ранее сохраненной позиции. В этом случае клиент при коннекте сразу же получит сообщения, которые поступили в канал, пока он был "в отключке" (и, кстати, в случае Realplexor-а, он получит все эти сообщения в одной пачке, за одно соединение).

Буферизация встроена в Realplexor: в каждом канале хранится до 30 последних сообщений (параметр конфигурации MAX_DATA_FOR_ID), плюс, если канал долгое время не обновлялся, то он очищается через 1 час (параметр CLEAN_ID_AFTER).

Что касается сохранения позиции, то это как раз и есть курсоры. Выше мы говорили о том, что каждое сообщение в Realplexor-сервере имеет ассоциированное значение курсора, которое используется для ранжирования и обеспечения отказоустойчивости.

Листинг 12: Явная установка позиции курсора
// Listen only messages with cursor values > 10.
realplexor.setCursor("Alpha", 10);
// Subsctibe to channel.
realplexor.subscribe("Alpha", function(data, id, cursor) {
    $('#first').innerHTML += data + " at cursor " + cursor + "<br>";
});
// Apply subscriptions.
realplexor.execute();

Откуда взять это значение 10, которое мы передаем в setCursor()? Существуют 2 способа.

  1. Если Realplexor используется для организации realtime-обновления загруженной страницы (как на РуТвите или FriendFeed), то значение курсора нужно брать синхронным с моментом генерации тела страницы. Ведь клиент должен получить обновления, которые произошли именно после генерации HTML (ведь те, что произошли до, уже и так у него отображены на странице). Соответственно, удобно использовать какую-либо монотонно возрастающую последовательность чисел, жестко связанную с хранящимися в БД данными (либо первичный ключ сообщений, либо TIMESTAMP на мастер-сервере).
  2. Если Realplexor применяется для передачи личных сообщений и мгновенных уведомлений, которые напрямую на страницах сайта не отображаются, то можно запоминать предыдушее значение курсора в Cookies при получении очередной порции данных.

Наверное, случай (1) встречается все-таки чаще, поэтому ваш код использования Realplexor-а будет, скорее всего, состоять из нескольких пар вызовов setCursor() + subscribe(), по одной паре на каждый прослушиваемый канал.

Лирика: как работают Comet-серверы?

Realplexor может использоваться для создания "сайтов реального времени", которых сейчас появляется все больше и больше. Отличительная особенность таких сайтов в том, что человеку нет необходимости перезагружать страницу для получения свежей информации. Вместо этого все действия, производимые другими пользователями, сразу же становятся видны в его браузере. Примеры таких действий: новое сообщение в онлайн-чате, новая запись в результатах поиска, новый комментарий к посту или мгновенное уведомление о личном сообщении.

Рассмотрим пример проекта, использующего Dklab Realplexor, — РуТвит. Это русский аналог Твиттера с сильным уклоном в сторону realtime, качественной поддержкой русской морфологии и другими возможностями, ориентированными на российского массового пользователя. Когда пользователь видит некоторый список твитов (например, свою френд-ленту или результаты поискового запроса), ему не нужно перезагружать страницу, чтобы получить свежие данные. Вместо этого новые твиты появляются в верхней части страницы в режиме реального времени, сразу же, как только их добавляют на сайт.

Также твиты в ленте могут быть сгруппированы по принципу "пост + комментарий к нему". Каждый раз, когда к одному из таких постов добавляют комментарий, он сразу же появляется на странице. В этом случае пользователь оказывается подписан не на одну ленту, а на набор из 10-20 постов одновременно. (Конечно, на один и тот же пост могут быть подписаны тысячи пользователей.)

Как можно реализовать такой функционал?

  1. Неправильный способ (Polling) Раз в 10 секунд делать из JavaScript запрос на сервер для проверки, не появилось ли новых сообщений. Этот метод не работает, если на сайте одновременно находится большое количество пользователей, т.к. нагрузка на сервер растет слишком быстро. Кроме того, потребление трафика пользователем также оказывается крайне высоким.
  2. Правильный способ (Long Polling) Устанавливать постоянное и длительное соединение с сервером, ожидая поступления данных через него. Если сообщений нет, соединение просто держится открытым на протяжение нескольких минут. Если соединение по каким-либо причинам закрылось, оно вновь открывается. В итоге и трафика потребляется мало, и нагрузка на сервер оказывается невелика.

На принципе (2) и построен Realplexor, а также другие Comet-серверы.

Разработчикам

Если вам не хватает какой-то функциональности от Realplexor-а — смело вносите изменения в код и публикуйте их на GitHub в своем аккаунте — я сразу же увижу эти изменения и смогу внести их в основную ветку. Это по-настоящему гениальная система для "социального программирования", и даже если единственное, что вы вынесите из этой статьи, — знакомство с GitHub, я уже буду считать свою миссию выполненной.

Автотесты на серверную часть

Автоматические тесты позволяют гарантировать, что при внесении очередного изменения в код ничего не поломается. (И наоборот, если вносится любое изменение, будь то багфикс или новый функционал, сразу же пишется тест, который успешно завершается с этим изменением и неуспешно — без него.) Это очень удобная на практике техника. В коде Realplexor-а автотесты занимают примерно половину места.

Тесты расположены в директориях t/ и api/*/t/. Для их запуска нужно под пользователем root запустить команды:

cd t/servertest
sh ./run_all.sh

Чайник 

Тест-фреймворк — PHPT (см. файлы t/servertest/*.phpt). Он очень простой, но написан на PHP, поэтому вам потребуется PHP-интерпретатор и установленный PEAR (по умолчанию PEAR устанавливается вместе с PHP).

Чтобы запустить индивидуальный тест, используйте команду:

pear run-tests your-test-file.phpt

Автотесты на JavaScript

JavaScript-код Realplexor-а также покрыт автотестами. Для их запуска настройте Apache так, чтобы его директория документов указывала на папку с дистрибутивом Realplexor-а и проверьте настройку Options +Indexes в httpd.conf (чтобы Apache выводил список файлов в директории). Затем откройте в браузере URL http://your-test-host/t/jstest/. Для запуска всех тестов щелкните по файлу 0000_all.php. (Да, автотесты для JavaScript также используют PHP; вы должны подключить PHP-интерпретатор к Apache.)

Можно запускать и индивидуальные JavaScript-тесты, просто открыв соответствующий файл с именем вида *.jst.

Лирическое отступление 
В отличие от серверных тестов, которые запускают полноценный демон Realplexor-а при работе, тесты на JavaScript полностью отделены от серверной части и используют "заглушки", которые "прикидываются" сервером при запуске.

Резюме

Realplexor позволяет с минимальными усилиями добавить на ваш проект realtime-функционал. Он прост в установке и конфигурировании, а также имеет API для языков JavaScript и PHP.

Приведу список аналогов и похожих технологий, которые могут быть вам интересны:

  • Tornado Web Server. Это полноценный веб-сервер, написанный на Python и работающий на сайте FriendFeed — флагмане современных realtime-разработок. Нельзя сказать, чтобы это был именно Comet-сервер — это скорее фреймворк, в котором можно, помимо прочего, создать собственный Comet-сервер (для тех, кто знает Python).
  • NginxHttpPushModule: модуль для nginx, добавляющий в него поддержку Comet и обладающий достаточно простым интерфейсом.
  • CometD: scalable HTTP-based event routing bus that uses a Ajax Push. Поддерживает подписку на несколько каналов одновременно.
  • APE: это скорее фреймворк для построения Comet-систем, нежели готовый продукт.
  • Stardust - the simple COMET server in perl (комментарий от автора — "the simplest COMET server I could imagine").
  • Orbited: эмуляция TCP-сокетов в JavaScript.

Обсудить статью в форуме
Скачать dklab_realplexor
Просмотреть GIT-репозиторий

На странице:
Лучше один раз увидеть...
Установка Realplexor-а
Как мы используем Realplexor в РуТвите
Что дальше?
     Запуск Realplexor-а на поддомене
     Где искать логи
     Возможности Dklab Realplexor
Серверный API: PHP
     constructor($host, $port, $namespace): API
     send($ids, $data): Отправка данных в канал
     send($ids, $data, $limiterIds): ограничение получателей
     send($idsAndCursors, $data): работа с курсором
     cmdOnline($prefixes): список онлайн-каналов
     cmdWatch($pos, $prefixes): слежение за онлайн-каналами
Клиентский API: JavaScript
     constructor(url, namespace): Подключение API
     subscribe(id, callback): Подписка на канал
     unsubscribe(id, callback): Отмена подписки на канал
     setCursor(id, cursor): Установить позицию курсора
Лирика: как работают Comet-серверы?
Разработчикам
     Автотесты на серверную часть
     Автотесты на JavaScript
Резюме





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