Centrifuge набирает обороты

    Привет!

    Пару месяцев назад я опубликовал на Хабре статью, посвященную описанию open-source проекта Centrifuge. Напомню, что это сервер рассылки сообщений подключенным клиентам (в основном из веб-браузера) в реальном времени. Написан на Python.

    С тех пор я продолжал работать над проектом в свободное время и сейчас готов поделиться накопившимися мыслями и изменениями.



    Изначально, Центрифуга была самобытным проектом. Не сильно заботясь о воспроизведении функционала существующих аналогов, я писал код так, как казалось правильным мне самому. В итоге сообщения клиентам доставлялись, всё работало, но! Было ли удобно всем этим пользоваться? Нет!

    В конце июня я наткнулся на великолепную статью от Serge KovalPython and real-time Web. Удивительно, но на тот момент я не знал о существовании Faye. Статья открыла мне этот замечательный проект, как и понимание того факта, что все-таки Центрифуга в своем текущем состоянии не сильно упрощает жизнь при разработке real-time веб-приложений.

    С того времени я допиливал Центрифугу с прицелом на удобство использования и с оглядкой на pusher.com, pubnub.com и Faye.

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

    • Это интересно. В проекте на данный момент используются Tornado, ZeroMQ, Redis, SockJS, Bootstrap 3. Прекрасные инструменты, работать с которыми — безграничное счастье.
    • Pusher.com и Pubnub.com — облачные сервисы, не всегда возможно/есть_смысл/хочется полагаться на третью сторону. Невозможно внести изменения в серверную часть.
    • Аналогов на Python я так и не нашел (может быть вы знаете?), бэкенд Faye — это Ruby или NodeJS. Чтобы сделать авторизацию подписки в канал нужно писать расширения на этих языках. Я же хотел создать более независимое от языка бэкенда веб-приложения решение, предоставляющее необходимый функционал из коробки.
    • Некоторые особенности, которых нет в аналогах. Это наличие пространств имен, определяющих особенности поведения принадлежащих им каналов. Веб-интерфейс для управления проектами, их настройками и возможностью в реальном времени следить за сообщениями в каналах.


    Теперь расскажу об изменениях, произошедших с момента написания предыдущей статьи о Центрифуге.

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

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

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

    Возникает вопрос. Сейчас Центрифуга использует ZeroMQ PUB/SUB сокеты для коммуникации между несколькими своими процессами. Быть может, раз в игру вступил Redis в качестве хранилища информации о подключенных клиентах и истории сообщений, то стоит использовать его PUB/SUB возможности и для коммуникации между процессами Центрифуги вместо ZeroMQ? В том единственном сравнительном бенчмарке, который я видел, ZeroMQ по производительности опережает Redis.
    Поэтому на данный момент я оставил все как есть. Однако это спорный и важный момент.

    Еще теперь можно получать сообщения о подключении(отключении) клиента к каналу (от канала). Приятный пустячок.

    Наконец, самое, пожалуй, важное — появился javascript-клиент — обертка над протоколом Центрифуги. Он построен на основе Event Emitter, написанного Оливером Калдвеллом (Oliver Caldwell). Теперь взаимодействовать с Центрифугой из браузера очень просто. Примерно вот так:

    var centrifuge = new Centrifuge({
        // настройки аутентификации
    });
     
    centrifuge.on('connect', function() {
        // соединение с Центрифугой установлено
     
        var subscription = centrifuge.subscribe('python:django', function(message) {
            // функция, вызываемая при получении нового сообщения из канала
        });
     
        subscription.on(‘ready’, function() {
            subscription.presence(function(message) {
                // получена информация о подключенных к каналу клиентах
            });
            subscription.history(function(message) {
                // история последних сообщений канала
            });
            subscription.on('join', function(message) {
                // вызывается, когда новый клиент подключается к каналу
            });
            subscription.on('leave', function(message) {
                // вызывается когда клиент отключается от канала
            });
        });
     
    });
     
    centrifuge.on('disconnect', function(){
        // соединение с Центрифугой потеряно
    });
     
    centrifuge.connect();


    За бортом в этом примере остались настройки аутентификации (о них можно прочитать в документации). Также обратите внимание на название канала — оно состоит из имени пространства имен, которое должно быть создано в административном интерфейсе до подключения, в данном случае это python. Непосредственно имя канала указывается после — в данном случае это django. Пространство имен определяет настройки всех принадлежащих ему каналов. В настройках проекта можно выбрать пространство имен по умолчанию — тогда в javascript-коде можно не указывать явно название пространства имен. То есть, в случае если пространство имен python является дефолтным для проекта, можно писать вот так:

    centrifuge.on('connect', function() {
        var subscription = centrifuge.subscribe('django', function(message) {
            console.log(message);
        });
    });
     


    Авторизация в такого рода приложениях, пожалуй, самая сложная часть. Как я уже упоминал, в Faye нужно писать расширения на NodeJS или Ruby для защиты доступа к определенным каналам. Pusher.com для приватных каналов предлагает следующую схему:



    При попытке подписаться на приватный канал, отправляется AJAX запрос на бэкенд вашего приложения с именем канала. В случае, если доступ разрешен, вы должны вернуть подписанный ответ, который в дальнейшем вместе с именем канала отправляется непосредственно в Pusher. Преимущество здесь в том, что ваше приложение на момент получения AJAX-запроса в большинстве случаев уже содержит объект текущего пользователя (например, в Django это request.user).

    В Центрифуге применяется немного иной подход. Идентификатор текущего пользователя отправляется один раз в момент подключения — его вы указываете при конфигурации javascript-клиента вместе с ID проекта и токеном. Токен — это HMAC, сгенерированный на основе секретного ключа проекта (о котором должен знать только бэкенд вашего приложения), ID проекта и ID пользователя. Токен необходим для проверки корректности переданных ID проекта и ID юзера. В дальнейшем при подписке на приватные каналы Центрифуга будет отправлять POST запрос вашему приложению со строковыми ID юзера, именем пространства имен и именем канала. Поэтому первым делом в функции-обработчике авторизации вам нужно будет получить объект своего пользователя по ID.

    Еще один важный момент, касающийся авторизации — сейчас, чтобы подписаться на несколько каналов приходится несколько раз вызывать функцию subscribe на клиентской стороне. Если каналы приватные, то каждая такая подписка будет приводить к POST запросу к вашему приложению. Не оптимизированное поведение, которое хотелось бы улучшить. Но тот же pusher.com, признавая, что такие случаи хоть и редки, но бывают среди требований их клиентов, пока в полной мере эту проблему не решил. Здесь я пока в поиске правильного пути решения.

    Хотелось бы отметить еще один способ защиты приватных данных, который никто не отменял. Например, чтобы сделать отдельные приватные каналы для каждого пользователя приложения — можно генерировать трудно угадываемые имена каналов на основе какого-либо секретного ключа и ID пользователя. В таком случае вполне можно обходиться без дополнительной авторизации, по крайней мере до тех пор, пока вашим клиентам не выгодно делиться именами своих приватных каналов:)

    Есть возможность добавить кастомную асинхронную функцию (обрамленную tornado-декоратором @coroutine) перед публикацией сообщения в канал. Внутри этой функции можно делать с сообщением все что угодно, в том числе вернуть None и тем самым отменить публикацию сообщения. Но, пожалуй, это мало кому пригодится, как и аналогичная возможность добавить обработчик, вызываемый после публикации. Это достаточно низкоуровневое вмешательство и требует знания Python и Tornado.

    Установка Центрифуги в самом простом случае сводится к одной команде pip install centrifuge внутри virtualenv. Однако на машине должен быть установлен ZeroMQ (libzmq3) и dev-пакет для PostgreSQL (сам PostgreSQL сервер необязателен). Найденные проблемы, которые могут возникнуть при установке из PYPI, и способ их решения описаны в документации. Запуск одного процесса выполняется командой centrifuge. Однако для запуска в боевую среду потребуется конфигурационный файл, так как в нем содержатся важные настройки безопасности. Также не обойтись без использования дополнительных опций командной строки, если вы хотите запустить несколько процессов.

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

    Нагрузочное тестирование пока не проводил. Надеюсь, займусь бенчмарками в ближайшее время. Интересно сравнить с Faye, интересно запустить на PYPY. Ну и, конечно, необходимо продолжать работу над устойчивостью к всевозможным ошибкам, совершенствовать Python-код и javascript-клиент и так далее. Присоединяйтесь!

    Спасибо за внимание!
    Mail.ru Group
    Building the Internet

    Similar posts

    Comments 11

      0
      Что бы пощупать нужно запустить самому или есть где то уже развернутый, что бы потрогать по ссылке?
        +3
        К сожалению, нужно запустить самому.
        +4
        Интересный проект, сам несколько раз делал подобные штуки. Единственно, что ZeroMQ здесь совсем не к месту — то есть сильно избыточен, поверьте, в реальном проекте вы никогда не упретесь в производительность Redis Pub/Sub (а zmq создан для очень требовательных систем, здесь он явно не в своей весовой категории, хотя применить и попробовать конечно интересно)
          +2
          ZeroMQ дает некоторые интересные бонусы: brokerless, а также встроенный в библиотеку автоматический реконнект. Ну и интересно применять, конечно:) Но в целом, вы, конечно, правы. Думаю, несложно (и нужно) будет сделать Redis PUB/SUB механизм опциональным в будущих версиях.
          +3
          Пробовали эту конфигацию за Load-balancer, какой использовали?
          Еще субъективно какая-то мешанина с базами данных, зачем там вообще SqlLite, Postgres, если уже есть Redis.
          Еще вопрос — реализован ли автоматический реконнект на клиенте/сервере, когда, например, соединение отвалилось?
            +4
            Пробовал за Haproxy в начале разработки, потом появился Nginx c поддержкой проксирования вебсокетов, перешел на него. В документации есть пример конфига.

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

            Реконнект на стороне клиента предусмотрен. Реконнект PUB/SUB механизма на сервере осуществляет ZeroMQ.

            +1
            а у вас жесткое реальное время или мягкое?
              +2
              Зачем Вам куча зависимостей в виде sqlite, postgresql и zeromq? это дополнительные сервисы, которые надо устанавливать, настраивать и мониторить?
              Почему бы полностью не переехать на редис?
                +3
                Ответил чуть выше на аналогичный вопрос. Но попробую ответить еще раз, немного иначе. Существуют 2 вопроса в данном случае:

                1) Redis или ZeroMQ для PUB/SUB
                2) Зачем нужны разные бд

                Насчет первого вопроса есть упоминание в самой статье. Я тоже в данный момент размышляю, не перейти ли полностью на Redis. Сейчас зависимость на Redis не жесткая.

                Что касается второго — устанавливать и мониторить, конечно, придется. Но если вам не нужна возможность изменять динамически настройки проектов и пространств имен — можно описать структуру в конфиг-файле, в таком случае дополнительной зависимости не будет.
                +1
                Здорово!.. Но разрешите немного придираться.
                В тексте есть пункт «во-первых», а вот пункт «во-вторых» я не нашел… пока пытался понять логику изложения совсем потерял суть. Пожалуйста, дружите со структурой и логикой не только в Python, но и в «русском языке».
                  +2
                  Спасибо, поправил!:)

                Only users with full accounts can post comments. Log in, please.