Профайлинг Twisted-приложений

/galleries/kcachegrind.png

Часто сам забываю, как профилировать легко и быстро Twisted-приложения (с некоторым изменениями подойдет для любых Python-приложений). Кроме Twisted нам понадобится еще KCachegrind.

Запускаем наше приложение с включенным профайлингом:

twistd -n --savestats --profile=myprog.hotshot myprog

Подаем нагрузку, профайл собирается. Теперь с помощью утилиты hotshot2cg из поставки KCachegrind превращаем hotshot-профайл в calltree-профайл, который уже умеет KCachegrind "кушать".

hotshot2cg myprog.hotshot > myprog.calltree

Запускаем KCachegrind, открываем в нем полученный профайл:

kcachegrind myprog.calltree

MySQL, ROW/STATEMENT/MIXED-репликация и триггеры

Описанная особенность MySQL попалась мне на глаза слишком поздно, пишу, чтобы кто-то не напоролся на те же грабли. Начнем с начала. Итак, необходимо было отслеживать изменения MySQL-базы данных и складывать эти изменения в очередь (не в БД) для дальнейшей обработки внешней системой. Для отслеживания изменений подходят триггеры, но они активируются в процессе выполнения запросов транзакции и в случае последующего "rollback" не будут откатываться (что совершенно нормально для триггеров, влияющих только на состояние БД, т.к. состояние БД будет корректно откатываться). Поэтому необходимо выполнять триггеры только для успешных транзакций: проще всего это достигнуть с помощью репликации - на слейв передаются только запросы зафиксированных транзакций. Таким образом, мастер-БД не содержит триггеров, после репликации данные попадают на слейв, таблицы на котором обвешаны триггерами, те активируются и данные попадают в очередь. Казалось бы, все замечательно?

Однако MySQL не был бы MySQL, если бы не какие-нибудь приколы на пути. Читаем раздел 16.3.1.29 документации: триггеры на слейве выполняются только при использовании STATEMENT-based репликации, при использовании ROW-based репликации они выполняются на мастере. ROW-based репликация - одно из нововведений 5.1, при котором на слейв передаются не запросы (текст запроса), измененный в БД записи (подробнее см. раздел 16.1.2). Но это было бы еще полбеды - попробовал, увидел, что триггеры не выполняются, и пошел разбираться. Однако в MySQL придумали еще режим репликации MIXED: при этом сервер сам переключается между STATEMENT и ROW-based репликацией (я не нашел, по какому условию). Далее, в версиях MySQL от 5.1.12 до 5.1.29 режимом репликации по умолчанию выбирается как раз MIXED. Как вы уже догадываетесь, в MIXED-режиме триггеры на слейве то выполняются, то нет, при этом внешне это совершенно незаметно. Так прекрасно работавший слейв в один прекрасный день вдруг перестает поставлять обновления в очередь, хотя сам по себе слейв не отстал и содержит все данные.

Итого, несколько часов на поиск проблемы, работа по пересинхронизации данных во внешней системе... Спасибо, MySQL!

Кратко: чтобы триггеры на слейве всегда выполнялись, надо добавить в my.cnf (на мастере):

[mysqld]

binlog-format=STATEMENT

HL++ (2009): Twisted Framework

Сегодня выступал на HighLoad++ с докладом Twisted Framework - фреймворк для написания сетевых приложений в Python.

Введение

Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и сотен килобайт HTML-кода в ответ на запрос к передаче изменений в данных, запакованных в JSON и измеряемых сотнями байт. В качестве примеров таких сервисов можно привести Gmail, FriendFeed, Twitter Live Search и т.п.

Для обеспечения минимальной задержки для пользователя необходимо либо поддерживать постоянное соединение (например, Adobe Flash, RTMP) или использовать технику HTTP long polling в сочетании с keep alive. Так или иначе на стороне сервера это приводит к появлению большого количества одновременных соединений (тысячи, десятки тысяч), по каждому из которых передается не такой большой объем данных. Эту ситуацию называют обычно проблемой C10k.

Для обработки соединений архитектурный выбор на стороне сервера не такой большой: процесс на соединение, нить на соединение, комбинированный вариант процесс-нити или асинхронный ввод-вывод (возможно, в сочетании с дополнительными процессами или нитями). При наличии более 10 тысяч одновременных соединений с точки зрения расхода ресурсов совершенно невозможно представить создание 10 тысяч процессов; 10 тысяч нитей также вряд ли будет разумным решением. Необходимо дополнительно учесть, что при наличии такого большого числа соединений объем работы по каждому из них относительно невелик, большинство их них простаивают в ожидании поступления новых данных. Поэтому бóльшая часть процессов или нитей будет просто находиться в состоянии ожидания, расходуя впустую системные ресурсы.

Асинхронный ввод-вывод позволяет осуществлять неблокирующийся сетевой ввод-вывод по тысячам открытых сокетов в рамках одной нити выполнения (одного процесса). Механизмы реализации в разных ОС разные, например: select(), poll(), epoll(), kqueue() и т.п. Примеры приложений, использующих асинхронный ввод-вывод:

  • nginx (используются дополнительные процессы для обслуживания задач, требующих большего объема CPU);
  • haproxy;
  • memcached;
  • и другие.

Тем не менее, асинхронный ввод-вывод не является универсальным решением: для сервера БД это вряд ли было бы хорошим способом организации обслуживания соединений, так как для обработки каждого запроса требуется большой объем дискового ввода-вывода и процессорного времени, что не позволяет это сделать в рамках одного процесса.

Twisted Framework — это обширный набор классов и модулей для реализации асинхронных сетевых приложений. Twisted Framework — это:

  • ядро, абстрагирующее все операции асинхронного ввода-вывода и использующее соответствующий механизм конкретной ОС;
  • концепция Deferred, которая позволяет реализовать в простой форме обслуживание запроса: асинхронные сетевые обращения (например, к БД, memcached), обработку ошибочных ситуаций; Deferred является аналогом обычных конструкций последовательного программирования для асинхронной модели программирования;
  • обширный набор уже реализованных сетевых протоколов: HTTP, DNS, SMTP, IMAP, memcached, Jabber, ICQ и т.д.; еще большее количество протоколов доступно в виде дополнительных модулей;
  • дополнительная инфраструктура: unit-testы с поддержкой Deferred, пулы нитей, процессов и т.д.;
  • качественная концепция разработки — полное покрытие unit-testами, строгий review любого изменения.

Основная часть доклада будет посвящена конкретным примерам приложений, реализованными с помощью Twisted — с архитектурой, конкретными параметрами производительности, приемами оптимизации, преимуществами и недостатками Twisted для решения данной задачи:

  • RTMP-сервер pyFMS, сервер вещаний сервиса Smotri.Com (сотни трансляций, десятки тысяч зрителей);
  • backend-сервер проекта MDC - хранение и обработка истории общения пользователей, хранение настроек и т.п.;
  • Qik Push Engine - сервер немедленной доставки изменений информации о видео, созданных пользователями сервиса, в том числе push-нотификация о появившихся live-стримах, масштабирование, обработка больших объемов информации.

Дополнительная информация:

Презентация

Mongrel vs. Phusion Passenger: выбор очевиден

Предыдущая конфигурация:

  • nginx (главный proxy), который раздает трафик в
  • haproxy (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам
  • с 16-ю mongrelами на каждом

Проблемы:

  1. "Утекающая" память, периодический out of memory на серверах, лечится только перезапуском mongrelов.
  2. Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный mongrel все-таки попадает несколько "тяжелых" запросов).
  3. Сложность управления кластером монгрелов - постоянные проблемы при перезапуске, "не стартующие" mongrelы и т.п.

Новая конфигурация:

Результат:

webapp01-passenger-mongrel

Комментарий: переход на Phusion Passenger на Week 39, объем занятой памяти - это белая область на графике, растущая сверху вниз. До перехода на Passenger объем свободной памяти стремительно уменьшался, иногда доходя до нуля, после перехода остается более-менее стабильным. Использование CPU осталось на прежнем уровне (как и ожидалось).

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

Так что если вы еще не переключились, мы идем к вам :)

P.S. Отдельное спасибо glebpom за подсказку.

HL++2009: Twisted Framework - фреймворк для написания сетевых приложений в Python

highload

На HighLoad++-2009 буду выступать с докладом Twisted Framework - фреймворк для написания сетевых приложений в Python. Конференция будет проходит 12-13 октября 2009 г. в Инфопространстве. Приглашаю всех желающих!

Тезисы доклада:

  1. Архитектура сетевых сервисов, нити, процессы, асинхронный ввод-вывод.
  2. Тенденции в изменении структуры нагрузки на сетевые сервисы: AJAX, Comet/BOSH, клиент-серверная архитектура, проблема 10k.
  3. Асинхронный ввод-вывод и параллельное программирование: достоинства и недостатки. Поддержка локального контекста, deadlock, lock contention, starvation, масштабирование на многоядерную архитектуру и т.д.
  4. Twisted Framework с высоты птичьего полета.
  5. Аналоги Twisted в других языках программирования: Ruby — EventMachine, Perl — POE.
  6. Центральная концепция Twisted: Deferred — как сохранить контекст выполнения в однопоточном коде с асинхронным вводом-выводом.
  7. Аналогии между последовательным кодом и асинхронным кодом с использованием Deferred.
  8. Twisted и использование нитей: модель worker, «оборачивание» legacy кода.
  9. Реальные примеры Twisted-приложений, цифры, факты, архитектурные решения, преимущества и недостатки:
    • pyFMS — сервер RTMP-вещаний, нагрузка, оптимизация Python-кода;
    • MDC-сервер, масштабирование;
    • Qik Push Engine, обслуживание тысяч клиентов, тестирование клиентов.
  10. Качество кода Twisted, модель разработки, перспективы развития проекта, экосистема Twisted. Что может Twisted дать моему проекту?

Qik Push Engine API: приглашаем разработчиков

qik_logo Qik - это сервис стриминга (вещания) и загрузки видео с мобильных телефонов. Загруженное видео можно посмотреть на сайте или на его специальной версии с мобильного телефона. Доступна интеграция с другими сервисами, такими как Twitter, Facebook и другие. Клиенты для практически всех современных моделей телефонов: iPhone, Windows Mobile, Symbian, Android, Blackberry и другие.

Qik Push Engine - это механизм, который позволяет получать мгновенные оповещения о новых/изменившихся Qik-видео. Например, можно посмотреть постоянно обновляющийся список live-видео, все видео из района Новопеределкино или все видео со словом "кошка". На основе Qik Push Engine API можно построить интересные приложения, интегрированные с Qik, или добавить функциональность в уже существующие. Можно написать собственную систему нотификации, desktop-widget

или что-то еще.

Сегодня мы открываем API для работы c Qik Push Engine. Это первая ласточка в большом списке API, открывающих доступ к платформе стриминга Qik. Если вам интересно посмотреть Qik Push Engine в действии, заходите на одну из страниц примеров.

Основы работы с API

Qik Push Engine API доступно в виде набора удаленных процедур по протоколу JSON-RPC (over HTTP). В ближайшее время будет открыт REST-подобный интерфейс. Точкой входа для JSON-RPC является http://engine.qik.com/api/jsonrpc. Пока проект находится в закрытом бета-тестировании для доступа к API необходимо указать ключ разработчика, добавив параметр apikey: http://engine.qik.com/api/jsonrpc?apikey=xxxxxxx. Ключ разработчика можно получить, отправив письмо с запросом на api@qik.com.

Пример HTTP-сессии:


POST /api/jsonrpc

Host : engine.stage.qik.com

Content-Type:  application/json; charset=UTF-8

Content-Length: 46



{"method": "qik.session.create", "params": []}

Ответ:


Content-Type: text/json 



["a56c1603-1fbb-4140-8b35-8c36abbd8b27"]

Пример подписки на поток событий

Здесь и далее я буду использовать простую запись вызова команд, которая похожа на вызов обычных функций, опуская детали JSON-RPC взаимодействия. Итак, пусть мы хотим подписаться на список всех live-стримов, которые есть данный момент.

Первым делом создадим сессию:


qik.session.create() -> "a56c1603-1fbb-4140-8b35-8c36abbd8b27"

Подписываемся в рамках сессии на view всех публичных live-стримов, при этом указываем максимальное количество элементов во view (limit). Элементы view будут упорядочены в порядке убывания даты начала стрима (самые новые первыми):


qik.stream.subscribe\_public_live("a56c1603-1fbb-4140-8b35-8c36abbd8b27", 10) -> 
 [ "174/('PublicLiveStreamView', 'Stream', [('stoptime', None)], 'alllive;limit=10')", [ ... ] ]

В ответ на запрос мы получаем массив из двух элементов: первым является ключ подписки (длинная строка, начинающаяся с "174/.."). Не ищите смысла в ключе подписки, его необходимо просто сохранить, он потребуется в дальнейшем.

Вторым элементом является начальное состояние view, то есть текущий список live-стримов в нашем случае. Мы получим не больше 10 элементов (так как указали limit 10 при вызове метода qik.stream.subscribe_public_live). Состояние view выглядит примерно следующим образом:


[
   {"url": "http://qik.com/video/2158468", 
    "live": true, 
    "user_id": 340699, 
    "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/c8ad8fe065ad4ad7ac8491874c043eac.jpg", 
    "title": "Untitled", "duration": 0,
    "created_at": "2009-07-11 15:56:03", 
    "views": 0, 
    "id": 2158468}, 

   {"url": "http://qik.com/video/2158466", 
    "live": true, 
    "user_id": 340119, 
    "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/984c33e1ead441038f315e1fff109fc5.jpg", 
     "title": "Testando!", 
     "duration": 0, "created_at": "2009-07-11 15:55:34", 
     "views": 0, 
     "id": 2158466}
]

Смысл большинства полей очевиден, приведу лишь некоторые комментарии:

  • все даты приведены в UTC;
  • поле duration хранит длительность видео в секундах.

Так как мы подписались на live-стримы, у всех видео live == true.

Теперь нам необходимо получать новые события для тех view, на которые мы подписались. Для этого используются HTTP long polling-запросы. Клиент отправляет запрос серверу, а сервер отправляет ответ, когда появляются события или когда истечет таймаут, если событий не появилось.

Для реализации long polling в цикле вызываем метод qik.session.get_events, указывая желаемый таймаут (не рекомендуется использовать таймаут более 90 секунд).


qik.session.get_events("a56c1603-1fbb-4140-8b35-8c36abbd8b27", 60) -> 
  [ {...} ,{...} ]

В ответ мы можем получить пустой массив событий, если истек таймаут, или некоторый набор событий следующего вида:


[
   {
     "action": "update", 
     "old": {"id": 104252, "title": "Untitled", "views": 0, ...}, 
     "obj": {"id": 104252, "title": "driving to key largo", "views": 5, ...}, 
     "key": "175/('PublicRecentStreamView', 'Stream', [], 'allrecent;limit=50')"
    },
] 

В каждом событии обязательно передается следующая инормация:

  • key - ключ подписки, это то же самое значение, которое мы получили при подписке на view, ключ позволяет отличать события для разных view в рамках одной сессии;
  • action - действие (изменение), произошедшее с view:
    • ping - ничего не произошло, просто view сообщает, что все хорошо :)
    • update - элемент view изменился (как в примере выше), передается старое и новое состояние объекта в полях old и obj
    • insert - во view добавился новый элемент, его состояние будет записано в поле obj
    • delete - элемент исчез из view (может быть, видео было удалено, или просто перестало удовлетворять критериям view, или ушло за границу "limit"), последнее состояние объекта будет записано в поле obj.

В ответ на события мы можем обновить информацию на экране для пользователя, выполнить какие-либо еще действия. Но обязательно надо отправить новый запрос qik.session.get_events, чтобы получить новые события.

В конце работы не забываем "убраться" за собой, убиваем сессию:


qik.session.destroy("a56c1603-1fbb-4140-8b35-8c36abbd8b27") 

Замечания:

  • В рамках одной сессии можно подписаться на произвольное количество view.
  • Если это необходимо, можно авторизовать сессию от имени пользователя с помощью qik.session.authorize.
  • Существует большое количество вспомогательных запросов, которым сессия вообще не требуется, например qik.user.public_profile.
  • Можно отписаться от view при помощи конмадыqik.session.unsubscribe.

Reference JS-клиент

Для демонстрации возможностей Qik Push Engine и проверки его работы был реализован легкий JavaScript-клиент. Его можно увидеть в работе на примерах, полный список которых приведен ниже.

Если вы запустите пример в Firefox с включенным Firebug, в консоли Firebug будет появляться подробная информация о выполняемых запросах к Qik Push Engine API, полученных ответах и т.п.

Исходный код клиента доступен в необфусцированном виде, со всеми комментариями. Код предоставляет по принципам public domain, то есть можете делать с ним все, что захотите - использовать в своих приложениях, модифицировать, переписывать на другие языкаи программирования и т.п.

Пробежимся быстро по компонентам JS-клиента.

prototype.js

Совершенно стандартный Prototype, ничего интересного.

deferred.js

Deferred - это одна из основных концепций Twisted Framework (http://twistedmatrix.com). Deferred не является обязательным для взаимодействия с Qik Push Engine API, но позволяет сильно упростить сложные взаимодействия в асинхронном коде.

В качестве дополнительного материала о Deferred можно почитать про основы работы с Deferred, более сложные случаи использования Deferred, данную реализацию Deferred для Prototype, а также взглянуть на Deferred в других JS-фреймворках: MochiKit, Dojo.

DeferredManager - простая конструкция на основе Deferred, позволяет запускать не более N асинхронных действий в один момент времени.

core.js

Ядро клиента, объявляется namespace QikEngine, отладочные функции, определение конфигурации, создания объекта для доступа к API и т.п.

api.js

QikEngine.API - простая обертка JSON-RPC API Qik Push Engine, возвращающая Deferred в качестве результата любого обращения к API. Через Deferred же отправляются ошибки и результаты выполнения методов.

QikEngine.Session - класс, оборачивающий понятие сессии, создание, уничтожение, авторизация в рамках сессии.

events.js

QikEngine.Events - получение новых событий с помощью HTTP long polling, технология, похожая на BOSH/Comet. Распределение событий подписчикам (конкретным view).

view.js

QikEngine.View - абстрактный класс, представляющий view на стороне клиента. Обслуживание обновлений, отображение в элементах HTML, обработка событий и т.п. Списки элементов хранятся прямо в DOM-контейнере в виде HTML-узлов.

Потомки QikEngine.View для реализации конкретных view: QikEngine.PublicUserStreamView, QikEngine.PublicRecentStreamsView и т.д.

updater.js

Очень простой интерфейс между данными, полученными из Qik Push Engine (например, информацией о стримах) и HTML-элементами. Адаптация данных, например, перевод дат из UTC в локальный формат, форматирование значений и т.п.

usercache.js

QikEngine.UserCache - механизм загрузки информации о пользователях (по их ID). Использует DeferredManager для ограничения количества параллельных запросов.

Что делать дальше?

  • смотрите описание API;
  • пишите письмо на api@qik.com, расскажите о своем проекте и получайте ключ для доступа к API.

Полный список примеров

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

AMQP по-русски

AMQP logo Сегодня довольно мало информации о протоколе AMQP (Advanced Message Queueing Protocol) и его применении, особенно русском языке. А вообще это - замечательный, уже достаточно широко поддерживаемый открытый протокол для передачи сообщений между компонентами системы с низкой задержкой и на высокой скорости. При этом семантика обмена сообщениями настраивается под нужды конкретного проекта. Такие решения существовали и ранее, но это первый стандарт, для которого существует большое количество свободных реализаций.

Основная идея состоит в том, что отдельные подсистемы (или независимые приложения) могут обмениваться произвольным образом сообщениями через AMQP-брокер, который осуществляет маршрутизацию, возможно гарантирует доставку, распределение потоков данных, подписку на нужные типы сообщений. В качестве классических примеров обычно приводятся финансовые приложения, связанные, например, с доставкой потребителям информации о курсах ценных бумаг в режиме реального времени, также возможно RPC-взаимодействие двух подсистем, которые не имеют связи друг с другом (взаимодействие через общий протокол AMQP) и так далее и тому подобное.

Сегодня тема доставки информации в реальном времени является крайне актуальной (достаточно вспомнить хотя бы Twitter, Google Wave). И здесь системы передачи сообщений могут служить внутренним механизмом обмена данными, который обеспечивает доставку данных (изменений данных) клиентам.

Я не ставлю своей целью сегодня рассказать о том, как писать приложения для AMQP. Хочу лишь немного рассказать о том, что это совсем не страшно, не очень сложно, и действительно работает, хотя стандарт находится еще в развитии, выходят новые версии протокола, брокеров и т.п. Но это уже вполне production-quality. Расскажу лишь базовые советы, чтобы помочь "въехать" в протокол.

Для начала, маленькая коллекция ссылок (в основном, на английском): что такое вообще обмен сообщениями и почему AMQP такой (Messaging in general and AMQP design); сравнение различных реализаций обмена сообщениями, в частности основанных на AMQP (Message Queue Comparison); клиентская библиотека AMQP для Twisted Framework (Python) с поддержкой Thrift (Thrift, AMQP in Twisted); руководство от Red Hat о том, что такое messaging и как работать с AMQP, описывает их "коробочный" продукт на основе AMQP, но подходит и для любых AMQP-брокеров (AMQP Programming Tutorial for C++, Java, and Python); достаточно много документации, описаний архитектурных решений на сайте ZeroMQ, который не совсем AMQP-брокер, но общая архитектура, детали реализации представляют отдельный интерес; обзорная статья от Duncan McGregor о txAMQP и AMQP в общем (A Simfonia on Messaging with txAMQP, II, III)

Далее необходимо выбрать AMQP-брокер, который вы будете использовать. При выборе необходимо рассматривать как собственно характеристики сервера: скорость работы, надежность, легкость установки и поддержки, но также внимательно смотреть на версию AMQP-протокола, которая поддерживается брокером, - она должна совпадать с версией клиентской AMQP-библиотеки. Из брокеров я бы посоветовал RabbitMQ, написанный на Erlang, и Qpid, версии на C++ (AMQP 0-10) и Java (0-8, 0-9).

Сам протокол AMQP устроен достаточно интересно: на самом нижнем уровне определяется формат кодирования данных в бинарный вид для передачи по TCP-соединению, выше лежит формат передачи RPC-запросов между сервером и клиентом. Сама семантика работы с сообщениями, создания очередей и т.п. описывается в XML-спецификации, которая по сути задает RPC-интерфейс сервера и клиента (примеры таких XML-файлов для версий 0-8 и 0-10). Этот XML является последней и конечной спецификацией протокола. Более того, версии протокола 0-8 и 0-10 отличаются настолько сильно, что поддерживать их одновременно вряд ли возможно в одной программе. Что еще более интересно, иногда такие spec-файлы для разных брокеров AMQP, формально поддерживающих одну и ту же версию протокола, отличаются настолько, что не являются взаимозаменяемыми. Но это скорее небольшие технические проблемы.

AMQP основан на трех понятиях:

  1. Сообщение (message) - единица передаваемых данных, основная его часть (содержание) никак не интерпретируется сервером, к сообщению могут быть прицеплены структурированные заголовки.
  2. Точка обмена (exchange) - в нее отправляются сообщения. Точка обмена распределяет сообщения в одну или несколько очередей. При этом в точке обмена сообщения не хранятся. Точки обмена бывают трех типов: fanout - сообщение передается во все прицепленные к ней очереди; direct - сообщение передается в очередь с именем, совпадающим с ключом маршрутизации (routing key) (ключ маршрутизации указывается при отправке сообщения); topic - нечто среднее между fanout и exchange, сообщение передается в очереди, для которых совпадает маска на ключ маршрутизации, например, app.notification.sms.* - в очередь будут доставлены все сообщения, отправленные с ключами, начинающимися на app.notification.sms.
  3. Очередь (queue) - здесь хранятся сообщения до тех пор, пока не будет забраны клиентом. Клиент всегда забирает сообщения из одной или нескольких очередей.

Меня в AMQP привлек эффективный бинарный протокол, ориентированность протокола на минимальные задержки и гибкость настройки. Я его использовал для рассылки сообщений всем серверам кластера (fanout exchange) и организации аналогов "почтовых ящиков", в которые доставляются сообщения, адресованные определенным клиентам (direct exchange). Для получения сообщений нет необходимости опрашивать сервер, достаточно подписаться на сообщения из очереди, и сервер передаст их в тот момент, когда они появятся.

В качестве клиентской библиотеки я выбрал библиотеку txAMQP для Twisted Framework (Python). В общем и целом все работает, но где-то требуются небольшие "доделки" и "подкрутки", которые я планирую опубликовать на launchpad. В AMQP и вокруг AMQP есть много интересного и перспективного. К примеру, брокер RabbitMQ умеет масштабироваться и работать в едином кластере. Мне кажется, это очень полезная и перспективная технология.

FMSPy, релиз Alpha (0.1)

[fmspy]: http://fmspy.org/ "FMSPy"

FMSPy [Flash Media Server written in Python][fmspy] ([FMSPy][fmspy]) - это еще один RTMP-сервер для приложений на Adobe Flash/Flex/Air. FMSPy является аналогом Adobe Flash Media Server, с гораздо меньшими возможностями, однако FMSPy - совершенно бесплатный проект с открытым исходным кодом. Проект находится на ранней стадии развития, но в активной разработке.

Итак, что есть на сегодняшний день:

  • Реализация RTMP-протокола: кодирование/декодирование пакетов, разрезание и склеивание из chunks и т.п.
  • Поддержка базового RPC (Invoke) клиент-сервер и сервер-клиент. То есть из Flash-приложения можно вызывать с помощью класса NetConnection методы приложения на стороне сервера, и наоборот со стороны сервера вызывать методы приложения.
  • Инфраструктура для написания приложений (в качестве плагинов к FMSPy) со своим API на Python.

В ближайших релизах:

  • Стриминг (вещание) с вебкамеры, стриминг видео/аудио с сервера (FLV, MP4, MP3).
  • Поддержка серверных Shared Object.
  • Анализ загрузки, полуавтоматическая кластеризация для распределения нагрузки.

[FMSPy][fmspy] написан на Python с использованием Twisted Framework, приложения на FMSPy реализуются также на Python и им доступны все возможности, которые есть в Twisted: асинхронная сетевая модель, соединения с БД, memcached, различные сервисы и т.п.

Запуск и установка

Если у Вас уже установлен Python и setuptools (чаще всего на Unix/Linux это так), достаточно выполнить от имени root:

easy_install fmspy

Easy_install автоматически установит все необходимые зависимости (если они еще не установлены). Более подробно об установке можно почитать в документации.

После установки запуск в отладочном режиме (на консоли) осуществляется следующим образом:

twistd -n fmspy

Для окончания работы сервера достаточно нажать Ctrl+C.

Примеры

Вместе с FMSPy устанавливается два примера: эхотест и простенький чат. После запуска откройте страницу http://localhost:3000/examples/ и выберите интересующий вас.

echotest chat

Вместо заключения

Пробуйте, тестируйте, присоединяйтесь к разработке. Любая помощь приветствуется: написание документации, патчи, идеи новых фич, графические материалы! Все это лучше всего отправить в [трекер][fmspy].

Впереди новые релизы, также в ближайшее время статья о написании приложений для FMSPy.

Ссылки:

Deferred для JavaScript (Prototype)

Prototype and Twisted

Продолжая тему Deferred для JavaScript предлагаю еще одно переписывание Deferred, теперь в терминах Prototype. Подробнее о самом Deferred можно почитать в двух моих прошлых заметках: Асинхронное программирование: концепция Deferred и Deferred: все подробности. Если кратко, самое распространенное и полезное применение Deferred в JavaScript - это работа с AJAX или другими RPC-over-HTTP вызовами, когда необходимо совершить цепочку логически связанных вызовов, корректно обрабатывать возникающие ошибки и т.п. С моей точки зрения, Deferred крайне необходим в таких ситуациях.

Перейдем к примерам: обращение к некоторому JSON-RPC API на основе Prototype'овского Ajax.Request можеть быть обернуто в Deferred следующим образом:


var Api = Class.create({
    initialize : function(url)
    {
        this.url = url;
    },

    call : function(method, params)
    {
        var requestBody = $H({ 'method' : method, 'params' : params }).toJSON();
        var d = new Deferred();

        var onSuccess = function(transport)
        {
            result = transport.responseText.evalJSON();
            if ('faultCode' in result && 'faultString' in result)
            {
                var err = new Error(result.faultString);
                err.faultCode = result.faultCode;
                err.faultString = result.faultString;
                d.errback(err);
            }
            else
            {
                result = result[0];
                console.log("Got result: ", result);
                d.callback(result);
            }
        };

        var onFailure = function(transport)
        {
            d.errback(new Error("API transport error: " + transport.status))
        };

        var onException = function(error)
        {
            d.errback(error);
        }

        new Ajax.Request(this.url, {
                method : 'post',
                postBody : requestBody,
                requestHeaders : { 'Content-Type' : 'application/json' },
                onSuccess : onSuccess,
                onFailure : onFailure,
                onException : onException,
            });

        return d;
    },
});

Здесь любое обращение к Api.call будет возвращать новый Deferred, который будет содержать результат удаленного вызова либо исключение (транспортное или от сервера, к которому мы обращаемся). Пусть есть RPC-вызовы sum и mult, которые, соответственно, складывают и перемножают свои аргументы. Тогда вычисление выражения (2+3)*7 с использованием нашего класса Api будет выглядеть так:

  var api = new Api;
  api.call('sum', [2, 3])
     .addCallback(
               function (sum_of_2_and_3) 
               { 
                     return api.call('mult', [sum_of_2_and_3, 7]); 
               })
     .addCallback(
               function(result)
               { 
                     alert('(2+3)*7 = ' + result); 
               })
     .addErrback(
              function (error) 
              { 
                     alert('Mmm.. something wrong happened: ' + error); 
              });

Ну и самое главное:

Пожар на газопроводе и информационный вакуум

В ночь с 9-е на 10-е мая произошел разрыв газопровода, об этом знают все. Мы живем в Ново-Переделкино, примерно в шести километрах от места аварии. Как раз в момент взрыва (около 0:40) мы вышли на балкон, услышали сильный хлопок и небо осветилось. Повернув голову, мы увидели огромный гриб пламени, поднимающийся в небо. Раздался сильный гул, который не прекращался. Определить расстояние на глаз было невозможно, казалось, что пожар совсем близко, километрах в двух за Чоботовским лесом. Первая реакция - позвонить 01, но ни с городского телефона, ни с сотового двух разных операторов это не удавалось - сразу короткие гудки. Может быть, очень много позвонило в тот момент, может быть, уже какие-то кабели были повреждены на месте пожара. Сегодня я уже знаю, где место пожара, и что пожарная часть располагается буквально в 200 метрах, звонить было вряд ли полезно - все знали о произошедшем. Но если бы, не дай Бог, был где-то еще пожар, как дозвониться? Мне кажется, что телефоны оперативных служб должны быть организованы так, чтобы возможность дозвониться оставалась.

Через еще несколько минут возник вакуум информации, где горит? Что горит? По виду столба пламени можно было догадаться, что горит газ, но знать наверняка вряд ли было возможно. Есть ли угроза для нас, для нашего дома? По моим первым предположениям, что горит прямо за лесом, в случае, если огонь перекинется на лес, нам надо было будить нашу маленькую дочку и уезжать отсюда. По телевидению в новостных каналах тишина, никакой информации. Помогает поиск по блогам, уже через несколько минут первая информация - пожар где-то внутри МКАД, в районе Мичуринского проспекта. Становится чуточку легче. Спасибо вам, блоггеры!

Еще через несколько минут около часа ночи прекращает работать интернет (Стрим), с ним погибает и телевидение №1 (Стрим-ТВ). Тут же умирает и телевидение №2 (кабельное в доме). Интересно, как они связаны? По радио тишина. Остается последний источник информации, сотовый телефон, ленты новостных агентств. Через их сайты удается узнать какую-то информацию, действительно пожар около поста ДПС на пересечении Мичуринского и МКАД. Всё, можно спать.

Contents © 2015 Andrey - Powered by Nikola