- PVSM.RU - https://www.pvsm.ru -

Centrifuge — брокер реал-тайм сообщений

Привет!

В статье я опишу свой небольшой open-source проект — Centrifuge [1] (далее Центрифуга). Это сервер на Python, задача которого — рассылка (broadcast) сообщений в реальном времени подключенным (в основном из браузера) клиентам.

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

Для начала, посмотрите, пожалуйста, скринкаст (не забудьте включить субтитры), если после просмотра интерес не пропадет, смело читайте дальше!


Идея сервера real-time сообщений совсем не новая, среди существующих подобных проектов могу привести в пример Pusher [2] и Pubnub [3]. Вот цитата с сайта Pusher:

Pusher is a hosted API for quickly, easily and securely adding scalable realtime functionality to web and mobile apps.

Pubnub на своей главной страницы говорит нам нечто похожее:

Thousands of mobile, web, and desktop apps rely on the PubNub Real-Time Network to deliver highly scalable real-time experiences to tens of millions of users worldwide.

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

Не сомневаюсь, что подобных серверов существует немало. Мне и самому некоторое время назад довелось написать очень похожую штуку — cyclone-sse [4]. Это демон на Twisted, который позволяет рассылать сообщения по каналам в реальном времени, используя технологию Server-Sent Events [5] (или откат (fallback) до Long-Polling для старичков вроде IE 7). Получился вполне приличный кусок кода, который мы успешно используем в бою.

Однако тот демон не решает некоторых важных проблем:

1) Отсутствие какой-либо авторизации. В нашем случае все проекты закрыты извне файрволлом компании и cyclone-sse мы используем только для публичных данных. Но чтобы добавить реал-тайм события в проект, который доступен всем пользователям интернета, нужен механизм авторизации.

2) Существует более быстрая чем Server-Sent Events реализация передачи сообщений — Websockets [6]. Технология, которая помимо увеличения производительности, предоставляет еще и возможность двустороннего обмена данными (в то время как SSE — это однонаправленный протокол, сообщения возможны только от сервера клиенту). К тому же вебсокеты поддерживают кросс-доменное общение, что не всегда справедливо для Server-Sent Events.

Однажды коллега по работе пожаловался, что ему не хватает возможности мониторить обновления пакетов для JAVA. Я подумал, что это неплохая идея для небольшого проекта — отслеживать новые пакеты, возможно не только для Java, но и для других языков программирования, в реальном времени показывать обновления в веб-интерфейсе — и приступил.

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

Звучит сильно, но спустя некоторое время я понял, что на реализацию подобной задачи нужно нечто большее, чем есть у меня… И было принято решение все упростить — на тот момент уже был написан скелет рассылки сообщений подключенным клиентам, почему бы не развивать это направление? Я понял, что смогу написать код, похожий по назначению и области применения на cyclone-sse, но более приспособленный к массовому использованию.

Итак, вы python-программист и вам нужно написать такой вот сервер — что вы будете использовать? На выбор Twisted [7], Gevent [8] и Tornado [9]. И, пока Гвидо Ван Россум стандартизирует интерфейс event-loop программ в библиотеке Tulip [10], нам нужно выбирать.

Я выбрал Торнадо. Он работает на третьем питоне, он просто классный в конце концов. Во многом этот выбор и желание того, чтобы конечный код работал на Python 3.3, предопределил выбор остальных сопутствующих технологий — ZeroMQ [11] (pyzmq [12]), SockJS [13] (sockjs-tornado [14]), MongoDB [15] (motor [16]) и PostgreSQL [17] (momoko [18]). Все библиотеки — асинхронные, исключающие блокировки при взаимодействии с сокетами.

К ZeroMQ я пришел не сразу.

Когда есть несколько процессов приложения за балансировщиком, и клиенты теоретически могут подключиться к любому из них — необходимо каким-то образом поддерживать целостность внутреннего состояния такой системы и иметь возможность коммуникации между экземплярами приложения. Изначально для этих целей я использовал Pub/Sub механизм Redis [19]. Но наткнулся на баг в реализации библиотеки Tornado-Redis [20] и посмотрел в сторону других решений.

В итоге выбор пал на ZeroMQ — сокеты на стероидах, набор паттернов для организации самых разнообразных сетевых взаимодействий. Отсутствие отдельного брокера — это просто великолепно. Если вы еще не слышали об этой библиотеке, или слышали, но не вдавались в подробности — исправляйтесь сейчас же! Прочитайте их The Guide [21], оно того стоит. Это мой первый проект с использованием данной библиотеки, надеюсь, опытные участники сообщества посмотрят на код и укажут на возможные недочеты.

Каждый процесс Центрифуги создает PUB сокет, который биндится на определенный адрес/порт. Процесс также имеет SUB сокет, который соединяется с PUB сокетом текущего процесса и PUB сокетами остальных инстансов (если таковые запущены). Минусом такой схемы [22]является необходимость вручную указывать все адреса PUB сокетов при запуске процесса. Поэтому есть возможность запустить XPUB/XSUB прокси в отдельном процессе и запускать все процессы Центрифуги с использованием этого прокси. То есть организовать все взаимодействие вот по такой схеме:

image

Последняя часть головоломки — клиентская. Tornado из коробки работает с вебсокетами, но я решил пойти чуть дальше и позволить клиенту использовать еще и SockJS. Так что всё будет работать и в браузерах без поддержки вебсокетов. Хотелось бы отдельно поблагодарить Serge S. Koval (mrjoes) [23] за sockjs-tornado. Поддержка socket.io [24] не планируется.

Итак, что в итоге? А что-то вроде этого:

image

Как видно на диаграммке, в качестве базы данных используется MongoDB или PostgreSQL. Что нам хранить? Проекты и их настройки, категории внутри этих проектов. Подробнее об этом расскажу чуть ниже.

Мне кажется, я до сих пор так и не смог внятно объяснить, что же такое я тут всем впариваю. Итак, вот примерно так, по пунктикам:

1) Вы захотели добавить на свой сайт нечто реал-таймовое — комментарии, графики, обновляющиеся счетчики, уведомления…

2) Однако ваш сайт не на асинхронном бэкенде, или на самом что ни на есть асинхронном, но вам не хочется писать с нуля логику менеджмента каналов, подписок и т.д.

Центрифуга вполне может подойти вам в таком случае.

3) pip install centrifuge. Или чуть более подробно [25] в документации (документация пока скомканная, местами непонятная, но в будущем надеюсь изменить ее к лучшему).

4) Интегрировать все же придется… Подчеркну некоторые важные моменты.

Для начала, нужно Центрифугу запустить. Да, там много всяких параметров для запуска, но я верю, что у вас получится, а если нет — пишите мне, я помогу.

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

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

Самая, пожалуй, важная опция для категории — bidirectional. Если отметить ее галочкой, то подключившиеся клиенты смогут сами посылать сообщения в канал, без задействования вашего приложения. Иначе, только однонаправленное сообщение от сервера к клиенту — когда ваше приложение отправляет с помощью POST запроса событие в Центрифугу. POST запрос содержит данные о проекте, категории, канале и непосредственно пересылаемое сообщение. Сообщение рассылается всем подписанным на канал клиентам.

Итак, Центрифуга (или несколько Центрифуг) крутит свой event-loop, проекты и категории созданы, дело за клиентской частью. Как я уже говорил, для общения можно использовать нативные вебсокеты или библиотеку SockJS. На данный момент не существует javascript-библиотек, которые упрощают взаимодействие с Центрифугой, возможно, они появятся в будущем. Пока, чтобы взаимодействовать, нужно отправлять JSON сообщения, соответствующие JSON-схемам. На данный момент существуют только 4 метода для таких команд:

  • auth — первое сообщение после установки соединения — авторизация.
  • subscribe — после успешной авторизации можно подписываться на каналы в различных категориях, на которые был получен доступ во время авторизации.
  • unsubscribe — отписаться от канала
  • broadcast — отправить сообщение в канал, работает только для каналов, принадлежащих двунаправленной (bidirectional) категории

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

При подключении нового клиента, если вы указали в параметрах проекта специальный адрес — Центрифуга отправит POST запрос на этот адрес с данными подключающегося клиента, ваше приложение должно на основе этих данных определить, имеет ли данный клиент доступ к запрашиваемым разделам, и если авторизация позволена — вернуть соответствующий ответ Центрифуге.

Я опустил очень много технических подробностей. Специально не вставил ни единого кусочка кода — проект очень молодой и, кто знает, что изменится в ближайшем будущем, хотя бы после комментариев к этой статье. За кадром остались взаимодействие с Центрифугой с помощью специального клиента Cent [27], детальное описание авторизации клиентов, разбор параметров команд для взаимодействия из браузера, описание опций проектов и категорий. Думаю, это было бы слишком утомительно. Если к проекту будет какой-либо интерес — напишу об этом в будущем.

В репозитории на Github есть пример приложения [28], использующего Центрифугу. А еще в документации есть пример конфигурации Nginx [29] для деплоя. Лицензия — BSD. Если по каким-либо причинам вы хотите, но не можете использовать Centrifuge из-за лицензии — напишите мне, я пересмотрю.

Жду ваших замечаний и предложений по улучшению.

Автор: FZambia

Источник [30]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/37212

Ссылки в тексте:

[1] Centrifuge: https://github.com/FZambia/centrifuge

[2] Pusher: http://pusher.com/

[3] Pubnub: http://www.pubnub.com/

[4] cyclone-sse: https://github.com/FZambia/cyclone-sse

[5] Server-Sent Events: http://en.wikipedia.org/wiki/Server-sent_events

[6] Websockets: http://ru.wikipedia.org/wiki/WebSocket

[7] Twisted: http://twistedmatrix.com/trac/

[8] Gevent: http://www.gevent.org/

[9] Tornado: http://www.tornadoweb.org/en/stable/

[10] Tulip: http://code.google.com/p/tulip/source/browse/README

[11] ZeroMQ: http://www.zeromq.org/

[12] pyzmq: https://github.com/zeromq/pyzmq

[13] SockJS: https://github.com/sockjs/sockjs-client

[14] sockjs-tornado: https://github.com/mrjoes/sockjs-tornado

[15] MongoDB: http://www.mongodb.org/

[16] motor: https://github.com/mongodb/motor

[17] PostgreSQL: http://www.postgresql.org/

[18] momoko: https://github.com/FSX/momoko

[19] Redis: http://redis.io/

[20] Tornado-Redis: https://github.com/leporo/tornado-redis

[21] The Guide: http://zguide.zeromq.org/page:all

[22] Минусом такой схемы : http://zguide.zeromq.org/page:all#The-Dynamic-Discovery-Problem

[23] Serge S. Koval (mrjoes): http://mrjoes.github.io/

[24] socket.io: http://socket.io/

[25] подробно: https://centrifuge.readthedocs.org/en/latest/starting/install.html

[26] вот так: https://github.com/FZambia/centrifuge/blob/master/src/centrifuge/auth.py#L15

[27] Cent: https://github.com/FZambia/cent

[28] пример приложения: https://github.com/FZambia/centrifuge/tree/master/examples/tornado_application

[29] конфигурации Nginx: https://centrifuge.readthedocs.org/en/latest/deployment/nginx.html

[30] Источник: http://habrahabr.ru/post/184262/