Возможно, некоторые из читателей уже слышали про Centrifugo раньше. В данной статье речь пойдет о разработке второй версии сервера и новой real-time библиотеке для языка Go, лежащей в его основе.


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



В двух словах — это сервер, который берет на себя задачу держать постоянные соединения от пользователей вашего приложения. В качестве транспорта используется Websocket или полифилл SockJS, умеющий, при невозможности установить Websocket-соединение, работать через Еventsource, XHR-streaming, long-polling и другие основанные на HTTP транспорты. Клиенты подписываются на каналы, в которые бекенд через API Центрифуги публикует новые сообщения по мере их возникновения – после чего сообщения доставляются подписанным на канал пользователям. Другими словами – это PUB/SUB сервер.



На текущий момент сервер используется в достаточно большом количестве проектов. Среди них, например, некоторые проекты Mail.Ru (интранет, обучающие платформы Технопарк/Техносфера, центр Сертификации и др.), с помощью Centrifugo работает красивейший дашборд на ресепшн в московском офисе Badoo, а в сервисе spot.im 350 тысяч пользователей одновременно подключены к Центрифуге.


Несколько ссылок на предыдущие статьи, посвященные серверу и его применению, для тех, кто первый раз слышит про проект:



Работу над второй версией я начал в декабре прошлого года и продолжаю по сей день. Давайте посмотрим, что из этого получается. Я пишу эту статью не только чтобы как-то популяризировать проект, но и получить чуть больше конструктивного фидбека до релиза Centrifugo v2 – сейчас есть простор для маневра и обратно несовместимых изменений.


Real-time библиотека для Go


В Go-сообществе время от времени встает вопрос — а есть ли альтернативы socket.io на Go? Иногда я замечал, как разработчики в ответ на это советуют посмотреть в сторону Centrifugo. Однако Centrifugo это self-hosted сервер, а не библиотека — сравнение не справедливое. Также несколько раз меня спрашивали, можно ли переиспользовать код Centrifugo для того, чтобы писать real-time приложения на языке Go. И ответ был: теоретически можно, но на свой страх и риск — обратную совместимость API внутренних пакетов я гарантировать не мог. Понятно, что рисковать так никому причин особых нет, а форкать тоже вариант так себе. Плюс я бы не сказал, что API внутренних пакетов вообще было подготовлено к такому использованию.


Поэтому одна из амбициозных задач, которые я хотел решить в процессе работы над второй версией сервера — попытаться выделить ядро сервера в отдельную библиотеку на Go. Я верю, что это имеет смысл, принимая во внимание, сколько фич имеет Центрифуга для того, чтобы быть приспособленной к production. Есть много доступных из коробки особенностей, призванных помочь с построением масштабируемых real-time приложений, снимая с разработчика необходимость писать собственное решение. Об этих особенностях я писал ранее и еще обозначу некоторые из них ниже.


Попробую обосновать еще один плюс существования такой библиотеки. Большинство пользователей Centrifugo — это разработчики, которые пишут бекенд на языках/фреймворках со слабой поддержкой concurrency (например, Django/Flask/Laravel/...): работать с большим количеством постоянных соединений если и можно, то неочевидным или неэффективным способом. Соответственно, помочь с разработкой сервера, написанного на Go, могут далеко не все пользователи (банально из-за незнания языка). Поэтому даже совсем небольшое community Go-разработчиков вокруг библиотеки сможет помочь и в развитии использующего ее сервера Centrifugo.


В итоге получилась библиотека Centrifuge. Это все еще WIP, но абсолютно все заявленные в описании на Github фичи реализованы и работают. Поскольку библиотека предоставляет достаточно богатое API, прежде чем гарантировать обратную совместимость, хотелось бы услышать о нескольких успешных примерах использования в реальных проектах на Go. Таких пока нет. Равно как и неуспешных:). Никаких нет.


Я понимаю, что, назвав библиотеку практически так же как сервер, буду вечно иметь дело с путаницей. Но я считаю это правильный выбор, так как клиенты (такие как centrifuge-js, centrifuge-go) работают и с библиотекой Centrifuge, и с сервером Centrifugo. Плюс название уже достаточно прочно закрепилось в умах пользователей, и не хочется эти ассоциации терять. И все же для чуть большей ясности уточню еще раз:


  • Centrifuge — библиотека для языка Go,
  • Centrifugo — готовое решение, отдельный сервис, который в версии 2 будет построен на библиотеке Centrifuge.

Centrifugo из-за своего дизайна (отдельно стоящий сервис, не знающий о вашем бекенде ничего) предполагает, что поток сообщений по real-time транспорту будет идти от сервера клиенту. Что имеется в виду? Если, например, пользователь пишет сообщение в чат, то это сообщение нужно сначала отправить на бекенд приложения (например, AJAX-ом в браузере), на стороне бекенда его провалидировать, сохранить в базу данных при необходимости, а затем отправить в API Центрифуги. Библиотека это ограничение снимает, позволяя организовать двунаправленный обмен асинхронными сообщениями между сервером и клиентом, а также RPC-вызовы.



Давайте посмотрим на простой пример: реализуем небольшой сервер на Go с использованием библиотеки Centrifuge. Сервер будет принимать сообщения от браузерных клиентов по Websocket, на клиенте будет текстовое поле, в которое можно вбить сообщение, нажать Enter — и сообщение отправится всем подписанным на канал пользователям. То есть максимально упрощенный вариант чата. Мне показалось, что удобнее всего будет разместить это в виде gist.


Запустить можно как обычно:


git clone https://gist.github.com/2f1a38ae2dcb21e2c5937328253c29bf.git
cd 2f1a38ae2dcb21e2c5937328253c29bf
go get -u github.com/centrifugal/centrifuge
go run main.go

И затем переходите по адресу http://localhost:8000, откройте несколько вкладок браузера.


Как вы можете заметить, точка входа в бизнес-логику приложения происходит при навешивании On().Connect() коллбек-функции:


node.On().Connect(func(ctx context.Context, client *centrifuge.Client, e centrifuge.ConnectEvent) centrifuge.ConnectReply {

    client.On().Disconnect(func(e centrifuge.DisconnectEvent) centrifuge.DisconnectReply {
        log.Printf("client disconnected")
        return centrifuge.DisconnectReply{}
    })

    log.Printf("client connected via %s", client.Transport().Name())
    return centrifuge.ConnectReply{}
})

Подход на основе callback-функций мне показался наиболее удобным для взаимодействия с библиотекой. Плюс похожий, только слабо типизированный, подход применяется в реализации socket-io сервера на Go. Если вдруг у вас есть мысли, как API можно было бы сделать более идиоматично — буду рад услышать.


Это очень простой пример, который не демонстрирует всех возможностей библиотеки. Кто-то может отметить, что для таких целей проще взять библиотеку для работы с Websocket. Например, Gorilla Websocket. Это на самом деле так. Правда, даже в таком случае вам придется скопировать приличный кусок кода сервера из примера в репозитории Gorilla Websocket. А что если:


  • вам нужно масштабировать приложение на несколько машин,
  • или вам нужен не один общий канал, а несколько – причем пользователи могут динамически подписываться и отписываться от них по мере навигации по вашему приложению,
  • или вам нужно работать тогда, когда Websocket-соединение установить не получилось (нет поддержки в браузере клиента, стоит браузерное расширение, какой-то прокси на пути между клиентом и сервером режет соединение),
  • или нужно восстанавливать сообщения, пропущенные клиентом во время коротких разрывов интернет-соединения не нагружая основную бд,
  • или нужен контроль авторизации пользователя в канале,
  • или нужно отключать постоянное подключение от пользователей, которых деактивировали в приложении,
  • или нужна информация о том, кто в данный момент присутствует в канале или события о том, что кто-то подписался/отписался от канала,
  • или нужны метрики и мониторинг?

Библиотека Centrifuge может вам с этим помочь — по сути она унаследовала все основные возможности, которые раньше были доступны в Centrifugo. Больше примеров, демонстрирующих заявленные выше пункты, можно найти на Github.


Сильное наследие Centrifugo может быть и минусом, так как библиотека переняла и всю механику сервера, которая достаточно самобытна и, возможно, кому-то может показаться неочевидной или перегруженной ненужными возможностями. Я старался организовать код таким образом, чтобы неиспользуемые фичи никак не сказывались на общей производительности.


В библиотеке есть некоторые оптимизации, которые позволяют более эффективно использовать ресурсы. Это объединение нескольких сообщений в один Websocket frame для экономии на системных вызовах Write или, например, использование Gogoprotobuf для сериализации Protobuf сообщений и другие. Кстати о Protobuf.


Бинарный Protobuf протокол


Я очень хотел, чтобы Centrifugo могла работать с бинарными данными (и не только я), поэтому в новой версии хотелось добавить бинарный протокол помимо имеющегося на основе JSON. Теперь весь протокол описан в виде Protobuf-схемы. Это позволило сделать его более структурированным, переосмыслить некоторые неочевидные решения в протоколе первой версии.


Думаю, не нужно долго рассказывать какие есть преимущества у Protobuf над JSON — компактность, скорость сериализации, строгость схемы. Есть и недостаток в виде нечитаемости, однако теперь у пользователей есть возможность решить, что им важнее в той или иной ситуации.


В целом трафик, генерируемый протоколом Centrifugo при использовании Protobuf вместо JSON, должен уменьшиться в ~2 раза (без учета данных приложения). В те же ~2 раза уменьшилось и потребление CPU в моих синтетических нагрузочных тестах по сравнению с JSON. Эти цифры на самом деле мало о чем говорят, на практике все будет зависеть от профиля нагрузки конкретного приложения.


Интереса ради я запустил на машине с Debian 9.4 и 32-мя Intel® Xeon® Platinum 8168 CPU @ 2.70GHz vCPU бенчмарк, который позволил сравнить пропускную способность клиент-серверного взаимодействия в случае использования JSON-протокола и Protobuf-протокола. Было 1000 подписчиков на 1 канал. В этот канал в 4 потока публиковались сообщения и доставлялись всем подписчикам. Размер каждого сообщения составлял 128 байт.


Результаты для JSON:


$ go run main.go -s ws://localhost:8000/connection/websocket -n 1000 -ns 1000 -np 4 channel
Starting benchmark [msgs=1000, msgsize=128, pubs=4, subs=1000]
Centrifuge Pub/Sub stats: 265,900 msgs/sec ~ 32.46 MB/sec
 Pub stats: 278 msgs/sec ~ 34.85 KB/sec
  [1] 73 msgs/sec ~ 9.22 KB/sec (250 msgs)
  [2] 71 msgs/sec ~ 9.00 KB/sec (250 msgs)
  [3] 71 msgs/sec ~ 8.90 KB/sec (250 msgs)
  [4] 69 msgs/sec ~ 8.71 KB/sec (250 msgs)
  min 69 | avg 71 | max 73 | stddev 1 msgs
 Sub stats: 265,635 msgs/sec ~ 32.43 MB/sec
  [1] 273 msgs/sec ~ 34.16 KB/sec (1000 msgs)
  ...
  [1000] 277 msgs/sec ~ 34.67 KB/sec (1000 msgs)
  min 265 | avg 275 | max 278 | stddev 2 msgs

Результаты для Protobuf случая:


$ go run main.go -s ws://localhost:8000/connection/websocket?format=protobuf -n 100000 -ns 1000 -np 4 channel
Starting benchmark [msgs=100000, msgsize=128, pubs=4, subs=1000]

Centrifuge Pub/Sub stats: 681,212 msgs/sec ~ 83.16 MB/sec
 Pub stats: 685 msgs/sec ~ 85.69 KB/sec
  [1] 172 msgs/sec ~ 21.57 KB/sec (25000 msgs)
  [2] 171 msgs/sec ~ 21.47 KB/sec (25000 msgs)
  [3] 171 msgs/sec ~ 21.42 KB/sec (25000 msgs)
  [4] 171 msgs/sec ~ 21.42 KB/sec (25000 msgs)
  min 171 | avg 171 | max 172 | stddev 0 msgs
 Sub stats: 680,531 msgs/sec ~ 83.07 MB/sec
  [1] 681 msgs/sec ~ 85.14 KB/sec (100000 msgs)
  ...
  [1000] 681 msgs/sec ~ 85.13 KB/sec (100000 msgs)
  min 680 | avg 680 | max 685 | stddev 1 msgs

Можно заметить что пропускная способность такой установки в 2 с лишним раза больше в случае Protobuf. Клиентский скрипт можно найти вот тут — это адаптированный под реалии Centrifuge бенчмарк-скрипт Nats.


Стоит также отметить, что производительность сериализации JSON на сервере можно «прокачать» используя тот же самый подход, что и в gogoprotobuf — пул буферов и генерацию кода — в данный момент JSON сериализуется пакетом из стандартной библиотеки Go, построенном на reflect. Например, в Centrifugo первой версии JSON сериализуется вручную с использованием библиотеки, предоставляющей пул буферов. Что-то подобное можно будет в будущем сделать и в рамках второй версии.


Стоит подчеркнуть, что protobuf можно использовать и при общении с сервером из браузера. Javascript клиент использует для этого библиотеку protobuf.js. Так как библиотека protobufjs достаточно тяжелая, а количество пользователей бинарного формата будет невелико, с помощью webpack и его tree shaking алгоритма мы генерируем две версии клиента — одна только с поддержкой JSON протокола, а другая с поддержкой и JSON, и protobuf. Для других сред, где размер ресурсов не играет столь критичной роли, клиенты могут о таком разделении не беспокоиться.


JSON Web Token (JWT)


Одна из проблем в использовании такого standalone сервера, как Centrifugo, состоит в том, что он ничего не знает о ваших юзерах и методе их аутентификации, о том, какой механизм сессий использует ваш бекенд. А аутентифицировать подключения каким-то образом нужно.


Для этого в Центрифуге первой версии при подключении использовалась SHA-256 HMAC подпись, основанная на секретном ключе, известном только бекенду и Центрифуге. Это гарантировало то, что передаваемый клиентом User ID действительно принадлежит ему.


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


Когда Центрифуга появилась, стандарт JWT еще не был столь популярен. Сейчас, несколько лет спустя, библиотеки для генерации JWT есть для большинства популярных языков. Основная идея JWT — именно та, что нужна Центрифуге: подтверждение подлинности передаваемых данных. Во второй версии HMAC подпись, генерируемая вручную, уступила место использованию JWT. Это позволило убрать необходимость поддержки функций-хелперов для правильной генерации токена в библиотеках для разных языков.


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


import jwt
import time

token = jwt.encode({"user": "42", "exp": int(time.time()) + 10*60}, "secret").decode()

print(token)

Важно отметить, что в случае использования библиотеки Centrifuge аутентифицировать пользователя можно нативным для языка Go способом — внутри middleware. Примеры есть в репозитории.


GRPC


В процессе разработки я попробовал GRPC bidirectional streaming в качестве транспорта для общения между клиентом и сервером (помимо Websocket и основанных на HTTP фоллбеков SockJS). Что можно сказать? Он работал. Однако я не нашел ни одного сценария, где двунаправленный стриминг GRPC был бы лучше, чем Websocket. Я смотрел в основном на метрики сервера: на генерируемый трафик через сетевой интерфейс, на потребление CPU сервером при наличии большого кол-ва входящих соединений, на потребление памяти на соединение.


GRPC уступил Websocket по всем статьям:


  • GRPC генерирует на 20% больше трафика в аналогичных сценариях,
  • GRPC потребляет в 2-3 раза больше CPU (в зависимости от конфигурации подключений – все подписаны на разные каналы или все подписаны на один канал),
  • GRPC потребляет в 4 раза больше оперативной памяти на соединение. Например, на 10k подключений Websocket-сервер отъел 500Mb памяти, а GRPC — 2Gb.

Результаты оказались достаточно… ожидаемы. В общем, в GRPC в качестве клиентского транспорта я большого смысла не увидел — и удалил код с чистой совестью до, возможно, лучших времен.


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


Сложности с клиентами


Изменениями, сделанными во второй версии, я убрал обязательность поддержки библиотек для серверного API — интегрироваться на серверной стороне стало проще, однако, клиентский протокол в проекте свой, изменился и имеет достаточное количество особенностей. Это делает достаточно сложной реализацию клиентов. Для второй версии у нас сейчас есть клиент для Javascript, который работает в браузерах, должен работать с NodeJS и React-Native. Есть клиент на Go и построенные на его основе и на основе проекта gomobile биндинги под iOS и Android.


Для полного счастья не хватает нативных библиотек под iOS и Android. Для первой версии Centrifugo их законтрибьютили ребята из open-source сообщества. Хочется верить, примерно так случится и теперь.


Недавно я попытал счастья, отправив заявку на MOSS грант от Mozilla, собираясь вложить деньги в разработку клиентов, но получил отказ. Причина — недостаточно активное сообщество на Github. К сожалению, это правда, но, как видите, какие-то шаги я предпринимаю, чтобы ситуацию улучшить.



Заключение


Я не озвучил все фичи, которые появятся в Centrifugo v2 — чуть больше информации есть в issue на Github. Релиз сервера пока не состоялся, но он в скором времени случится. Есть еще незаконченные моменты, в том числе нужно дописать документацию. Прототип документации можно посмотреть по ссылке. Если вы пользователь Centrifugo, то сейчас правильное время, чтобы повлиять на вторую версию сервера. Время, когда не так страшно что-то сломать, чтобы впоследствии сделать лучше. Для заинтересовавшихся: разработка сосредоточена в ветке c2.


Мне сложно судить, насколько будет востребована библиотека Centrifuge, лежащая в основе Centrifugo v2. На данный момент я доволен, что смог довести ее до текущего состояния. Самый важный показатель для меня сейчас это ответ на вопрос «а стал бы я сам использовать эту библиотеку в личном проекте?». Мой ответ — да. На работе? Да. Поэтому я верю, что и другие разработчики оценят.


P.S. Хотелось бы поблагодарить ребят, которые помогали делом и советами — Дмитрия Королькова, Артемия Рябинкова, Олега Кузьмина. Без вас было бы туго.

Комментарии (37)


  1. ainu
    12.07.2018 14:33
    +1

    Спасибо большое! Центрифуга — огонь как полезна, если будет (легко) встраиваемая, то вообще красота.
    Вопросы: redis будет? Раньше был, в статье ни слова, а тег стоит.
    Меняется ли логика работы (токены и все такое)? Например, чтобы делать рассылки вообще всем, всем из некой группы, одному клиенту — вот такие кейсы переигрываются или остаются? JWT понял — но он отменяет всё или дополняет?

    Нет ли желания делать лаунчер? Чтобы запускать не ./centrigugo в screen/tmux, а как то поумнее? Например service centrifugo start или ./centrifugo run.

    На странице проекта centrifuge в README нет какого нибудь минимального main.go (видимо, этот гист gist.github.com/FZambia/2f1a38ae2dcb21e2c5937328253c29bf и нужен).


    1. FZambia Автор
      12.07.2018 14:58

      Спасибо:)

      Редис будет в том же виде за исключением публикации новых сообщений через очередь в нем – эта возможность была не совсем верна в разрезе обработки ошибок при публикации и недетерминированной задержки перед отправкой клиентам, поэтому решил убрать – для публикации теперь HTTP или GRPC API. В библиотеке кстати вот так Redis можно использовать.

      Меняется ли логика работы (токены и все такое)?
      Меняется механизм генерации токена подключения и токена приватных подписок с ручной генерации токена (то есть использования вот такого кода) на JWT. В остальном все то же самое. JWT в том числе позволяет избавиться от болей связанных с тем как правильно info в виде JSON-строки прокинуть в Центрифугу через шаблонизаторы.

      Нет ли желания делать лаунчер?
      Пакеты для дистрибутивов Linux, которые используют systemd уже давно есть. Плюс docker-image тоже есть.

      У библиотеки совсем мало описания и документации сейчас – еще буду дописывать. Ну и пример в README библиотеки согласен нужен.


  1. apapacy
    12.07.2018 15:41

    Для полного счастья не хватает нативных библиотек под iOS и Android

    Мне кажется переносить websocket на мобильне девайсы не очень хорошая идея. Большинство известных реализаций неэкономичны в смысле расзода батареи. Я думаю если у клиента в отчете по расходу батареи приложение будет попадать на первые места то шанс на то что это приложение не будет удалено очень маленький.


    1. FZambia Автор
      12.07.2018 16:05

      В таком виде это достаточно голословное утверждение. Можете привести доказательства? О каких известных реализациях идет речь? Что на ваш взгляд нужно использовать вместо?


  1. apapacy
    12.07.2018 16:39

    Доакзательство — отчет на смартфоне по расходу батареи. От реализаций конкретных скорее всего мало что зависит т.к. websocket по сути это тот же модернизирванный longpooling. И это уже недостаток самого протокола. Использовать я бы стал на мобидных девайсах то что ейчас уже все практически использует (facebook, telegram etc.)


    1. FZambia Автор
      12.07.2018 17:02

      Ощущение, что вы намеренно обошли стороной все мои достаточно конкретные вопросы. Websocket — это протокол поверх TCP с минимальным оверхедом. Какие-то дополнительные затраты по сравнению с чистым TCP есть на начальный upgrade по HTTP — но далее это TCP-сессия. Ничего магического facebook и telegram не используют — точно также в качестве транспорта используют TCP.


    1. Aquahawk
      12.07.2018 17:03
      +1

      websocket по сути это тот же модернизирванный longpooling.

      нет от слова совсем. Это обычный tcp сокет, поверх которого прикрутили понятие сообщения, его заголовка и всё. Ничего более. Ах да, если вас пугает http соединение в начале, так это тоже просто tcp соединение с небольшим количеством текста там. Можете рассматривать это как приветственный хендшейк в любом tcp соединении которым (нендшейком) обменялись стороны обмена данными.


      1. apapacy
        12.07.2018 19:46

        См. stackoverflow.com/questions/29282070/websockets-energy-consumption
        Оверхед выражаеттся в проверке и повторном открытии соединения что не уитывают приведенные источники


        1. FZambia Автор
          12.07.2018 22:04
          +2

          Это касается любого TCP соединения, упомянутые вами телеграм и фейсбук точно также отправляют ping/pong фреймы, переустанавливают закрытое соединение.


  1. beho1der
    13.07.2018 09:52

    Ждем на Вики добавление по 2-й версии или статью на хабре. Особенно интересует про JWT.


    1. FZambia Автор
      13.07.2018 10:23

      Добрый день! Не очень понял, что за Вики? И что именно про JWT интересует?


      1. beho1der
        13.07.2018 10:52

        Вики имею ввиду: fzambia.gitbooks.io/centrifugal/content/server/tokens_and_signatures.html
        Насколько понимаю теперь вместо HMAC авторизации используется JWT стандарт?


        1. beho1der
          13.07.2018 11:07

          Возможно ли проверять библиотекой, токены выданные Centrifuge(o)?


          1. FZambia Автор
            13.07.2018 12:30

            Насколько понимаю теперь вместо HMAC авторизации используется JWT стандарт?


            Да, все так. JWT так же под капотом использует HMAC (на самом деле JWT умеет на основе других алгоритмов генерировать подписи, напр. AES, но мы пока HMAC SHA-256 поддерживаем только). Благодаря тому, что библиотеки JWT есть для большинства языков — вся генерация токенов скрыта от пользователя, хотя под капотом там очень похожий механизм.

            Документация будет новая — уже есть прототип, который еще будет доработываться, но вот например описание JWT там — centrifugal.github.io/centrifugo/server/authentication

            Не понял вопрос про проверку библиотекой токены выданные Centrifuge(o).


  1. recompileme
    13.07.2018 10:04

    Не смог понять функционал по описанию(
    Допустим есть канал, правильно ли я понял:
    — если емкость канала равна 50 сообщениям, в канале будут храниться 50 последних сообщений, упорядоченных по времени
    — хранится ли где ть uid последнего прочитанного сообщения подписчиком, можно ли его получить и есть ли возможность считать unread count
    — Как помечать сообщения как прочитанные на клиенте, есть ли какой то апи метод для этого
    — Хранится ли эта инфа на сервере или отдается на откуп клиенту/бекенду
    По описанию, понятно что есть вебсокеты для рилтайм режима и события/уведомления о подключении/отключении и новом сообщении. Но совершенно не понял что происходит между сессиями и какие возможности дает библиотека. Простите если глупые вопросы.


    1. FZambia Автор
      13.07.2018 10:35

      Добрый день!

      Судя по всему вы рассматриваете Centrifuge(o) только как решение для построения чатов – это не так, это скорее просто real-time транспорт с некоторыми вспомогательными примитивами вроде подписок на каналы, восстановления истории в канале после короткого дисконнекта, масштабирования на несколько инстансов. На основе Центрифуги можно написать много разных вещей, в том числе и чат, но для этого нужно поверх того что есть еще на бекенде реализовать много бизнес-логики конкретного приложения — в том числе unread count и т.д. В случае использования библиотеки вам, например, придется на бекенде реализовать методы подтверждения о прочтении сообщений, метод получения счетчика непрочитанных — и вызывать их когда нужно (можно через RPC-вызов через вебсокет, можно как-то иначе).

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


      1. recompileme
        13.07.2018 10:54

        а почему вы не дали никаких методов в апи для построения на базе центрифуги чего то большего чем рилтайм транспорт доставки? Это принципиальная позиция или просто не было подобных запросов?

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


      1. recompileme
        13.07.2018 11:13

        Я завел ишью на github с конструктивным описанием функционала, думаю там удобнее будет обсудить целесообразность и возможность реализации метода для чтения истории непрочитанных сообщений: github.com/centrifugal/centrifugo/issues/228

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


        1. FZambia Автор
          13.07.2018 12:20

          Позиция тут достаточно принципиальная в данный момент – если нужны методы восстановления истории для дисконнектов больше чем время жизни кеша, то это должно быть вне библиотеки.

          История спроектирована именно под короткие дисконнекты на N секунд. Центрифуга держит кеш сообщений в памяти или в Redis – это информация, которая может быть потеряна (например если используется in-memory движок и нода перезагружается, в случае Redis история конечно сохранится, но семантика та же). В протоколе заложена возможность восстановить историю с бекенда в таком случае, так как Центрифуга дает клиенту понять были ли восстановлены сообщения.

          Имхо под такой функционал история должна храниться персистентно и индексироваться — я не против это обсуждать, но перспектив особых не вижу в данный момент.

          Весь это функционал как бы есть (для кратковременных дисконектов, кстати насколько кратковременных? Секунда, сутки, это как то параметризируется?)


          Параметризуется на уровне канала — godoc.org/github.com/centrifugal/centrifuge#ChannelOptions


          1. recompileme
            13.07.2018 12:30

            Я пони. Тем не менее сделать это кмк просто. Конечно речь не идет о том, чтобы хранить всю историю. Но так как она уже хранится за некий период возмоность ее чтения ничем не навредит. Тем более как Вы справедливо заметили в том же редисе сообщения персистятся.
            Пожелание: было бы здорово выделить библиотеку в отдельный репозиторий и навести порядок. Какая то полная каша с зависимостями, именованиями и бренчами центрифуг( Просто непонятно какие бренчи каких проектов клонировать в каой последовательности github.com/centrifugal/centrifuge/issues/8


            1. FZambia Автор
              13.07.2018 13:26

              Судя по всему вы репу не склонировали в $GOPATH — это же Go. У меня все отрабатывает как нужно.


          1. beho1der
            13.07.2018 12:38

            Я бы тоже проголосовал зато чтобы иметь доступ не ко всей истории, а только к количеству, которое прописано в конфиге: «history_size»


          1. recompileme
            13.07.2018 13:01
            -1

            Смотрю код, отличная работа!
            Но если Вы хотите получить грант от Мозилы — надо научиться работать с комьюнити. Нельзя просто выкладывать код и ждать когда его начнут развивать
            Люди — ленивы. Если вы юзаете dep -надо написать что вы его юзаете где его качать и как собирать. Если уж заюзали его — в toml файлы надо прописывать все зависимости, включая интернал с нужными путями. Либо выделять либы в отдельные репы. Если это либа и она не юзает прометеус какой ть — не надо заставлять его качать. Если вы в бренче работаете — надо написать в каком. Если вы хотите чтобы вам слали PR — встаньте на место того, кто захочет его слать. Клонируйте сами свою репу и попробуйте послать себе PR. Поймете что сделано не так. Не обижайтесь на критику я добра вам желаю и развития. Но пока это выглядит как пет проект не до конца доделанный (я про версию 2) Кругом путаница
            Просто посмотрите на другие популярные проекты, полно шаблонов и рекомендаций как организовать репозиторий и как все надо описывать. Пока, к сожалению — удобней спулить ваш код, разобраться и собрать под себя нужное — не вернув вам PR
            Удачи!


            1. FZambia Автор
              13.07.2018 13:20

              Спасибо за фидбек, путаница есть из-за того, что я пишу про то, что еще не добралось до релиза – мне было важно собрать фидбек. Замечания постараюсь учесть.

              Toml-файлы генерятся автоматически dep-ом, ничего туда руками прописывать не нужно — этот момент не понял.


              1. recompileme
                13.07.2018 14:12

                Если склонировать репозиторий то все internal зависимости, не прописанные в toml — не подтянутся, если я правильно понимаю dep. Но я не фанат dep, могу неправильно им пользоваться. Хорошей идеей как мне кажется было бы просто пройти этот путь и описать, как собирать, какой бренч девовский, какой рабочий c2/master/c2-published и тп Ну я думаю как до релиза доберетесь — рассосется. Только после релиза обычно уже поздно — и новый релиз, а процесс описывать лень и так по кругу)


  1. bikutoru
    13.07.2018 10:46
    +5

    с помощью Centrifugo работает красивейший дашборд на ресепшн в московском офисе Badoo

    Не только московском, но и лондонском. Спасибо за работу над Центрифугой!


    1. FZambia Автор
      13.07.2018 11:50
      +1

      О, супер:) Спасибо!


  1. igordata
    13.07.2018 12:35

    Люблю Центрифугу. Хорошо работает. И чат норм на ней работает, и риалтаймовые оповещения по сайту, и любые изменения/апдейты контента тоже.


    1. FZambia Автор
      13.07.2018 13:28

      Спасибо за добрые слова. Приятно видеть, что время идет — а вы все еще используете Центрифугу:)


  1. pavlik
    13.07.2018 13:41

    Привет! В v2 будет api для подключения других engine? Или как в 1 версии будет только два движка из коробки?


    1. FZambia Автор
      13.07.2018 13:55

      Привет, пока Engine интерфейс скрыт внутри библиотеки, но мой план его в конечном итоге открыть. Не хочется делать это раньше времени (чтобы потом не менять внешнее API engine-а) пока кто-то не придет с реальным юзкейсом, какой-то реализацией и обоснованием преимуществ нового engine-a для юзкейса. Из коробки — те же два остаются, да.


      1. recompileme
        13.07.2018 14:16

        Чтобы другие могли написать другой Engine — обычно дают интерфейс, а не ждут чего то
        Вот, смотрите пример как это делается: github.com/ipfs/go-datastore
        Или в эфириум движке пример. У вас очень странный подход к разработке, я уже понял


        1. FZambia Автор
          13.07.2018 15:03

          Проекту 5 лет, он давно развивается — в разработке принимали участие ребята, которые сейчас работают в Hashicorp, плюс один из очень уважаемых разработчиков в Go-сообществе Klaus Post внес свой вклад. Это на случай если вы считаете, что я один все это делал. Дизайнерские решения по возможностям были приняты давно. Вы пришли — вбросили не разобравшись issue, не смогли склонировать библиотеку правильно — и учите меня Go.

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


          1. pavlik
            13.07.2018 15:12

            … без реальной необходимости со стороны пользователей…

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


          1. recompileme
            13.07.2018 16:09
            -2

            Респект! Но возможности:
            прочитать сообщения из хистори — начиная от заданного — нет
            персистить сообщения — в свою бд — нет
            апи — не продумано — ибо нет реальной необходимости со стороны пользователей
            А я тогда кто? В чем вообще цель поста если вы на фидбек так реагируете? Похвастаться пришли? Ну молодец!


      1. pavlik
        13.07.2018 15:00

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


        1. FZambia Автор
          13.07.2018 15:17

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

          Если вы готовы помочь с этим — пишите мне в ЛС, или в Gitter-чат gitter.im/centrifugal/centrifugo — можно обсудить, я расскажу подробнее.