- PVSM.RU - https://www.pvsm.ru -
Привет!
В статье я опишу свой небольшой 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 прокси в отдельном процессе и запускать все процессы Центрифуги с использованием этого прокси. То есть организовать все взаимодействие вот по такой схеме:
Последняя часть головоломки — клиентская. Tornado из коробки работает с вебсокетами, но я решил пойти чуть дальше и позволить клиенту использовать еще и SockJS. Так что всё будет работать и в браузерах без поддержки вебсокетов. Хотелось бы отдельно поблагодарить Serge S. Koval (mrjoes) [23] за sockjs-tornado. Поддержка socket.io [24] не планируется.
Итак, что в итоге? А что-то вроде этого:
Как видно на диаграммке, в качестве базы данных используется MongoDB или PostgreSQL. Что нам хранить? Проекты и их настройки, категории внутри этих проектов. Подробнее об этом расскажу чуть ниже.
Мне кажется, я до сих пор так и не смог внятно объяснить, что же такое я тут всем впариваю. Итак, вот примерно так, по пунктикам:
1) Вы захотели добавить на свой сайт нечто реал-таймовое — комментарии, графики, обновляющиеся счетчики, уведомления…
2) Однако ваш сайт не на асинхронном бэкенде, или на самом что ни на есть асинхронном, но вам не хочется писать с нуля логику менеджмента каналов, подписок и т.д.
Центрифуга вполне может подойти вам в таком случае.
3) pip install centrifuge. Или чуть более подробно [25] в документации (документация пока скомканная, местами непонятная, но в будущем надеюсь изменить ее к лучшему).
4) Интегрировать все же придется… Подчеркну некоторые важные моменты.
Для начала, нужно Центрифугу запустить. Да, там много всяких параметров для запуска, но я верю, что у вас получится, а если нет — пишите мне, я помогу.
После запуска нужно зайти в административный веб-интерфейс, создать новый проект. У проекта есть несколько настроек, описывать в этой статье их не буду — текста и так получается слишком много.
После создания проекта добавьте в него категории — по сути это пространства имен в проекте, внутри которых существуют каналы. Так как каналы создаются на лету, категории играют роль хранилища настроек для каналов, а также существуют для ограничения прав на подписку к тому или иному каналу внутри категории.
Самая, пожалуй, важная опция для категории — bidirectional. Если отметить ее галочкой, то подключившиеся клиенты смогут сами посылать сообщения в канал, без задействования вашего приложения. Иначе, только однонаправленное сообщение от сервера к клиенту — когда ваше приложение отправляет с помощью POST запроса событие в Центрифугу. POST запрос содержит данные о проекте, категории, канале и непосредственно пересылаемое сообщение. Сообщение рассылается всем подписанным на канал клиентам.
Итак, Центрифуга (или несколько Центрифуг) крутит свой event-loop, проекты и категории созданы, дело за клиентской частью. Как я уже говорил, для общения можно использовать нативные вебсокеты или библиотеку SockJS. На данный момент не существует javascript-библиотек, которые упрощают взаимодействие с Центрифугой, возможно, они появятся в будущем. Пока, чтобы взаимодействовать, нужно отправлять JSON сообщения, соответствующие JSON-схемам. На данный момент существуют только 4 метода для таких команд:
Чтобы не позволять всем и каждому подключаться к каналам Центрифуги, используется симметричное шифрование на основе секретного ключа. Этот ключ можно увидеть в веб-интерфейсе в настройках проекта. Ваше веб-приложение должно уметь генерировать специальный токен (вот так [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/
Нажмите здесь для печати.