Спустя почти три года после релиза Centrifugo v2 мы рады анонсировать следующий мажорный релиз Centrifugo. В течение последних нескольких месяцев, глубоко в нашей Centrifugal лаборатории, мы синтезировали улучшенную версию сервера.
Напоминаем, что Centrifugo – это сервер сообщений в реальном времени (real-time). Сервер держит постоянные соединения от пользователей приложения и предоставляет API для моментальной рассылки какого-либо уведомления активным пользователям, подписанным на канал уведомления. Можно использовать для создания чатов, "живых" комментариев, multiplayer игр, стримить данные и метрики (например, быстро меняющиеся курсы валют).
Новая Центрифуга v3 нацелена упростить использование сервера в простых real-time приложениях. Улучшена производительность сервера, существующие фичи обросли новой функциональностью. Centrifugo v3 поддерживает однонаправленные (unidirectional) real-time транспорты, клиентский JSON протокол значительно ускорен. Появилась экспериментальная поддержка высокопроизводительного движка на основе Tarantool. Помимо HTTP теперь есть возможность проксировать клиентские события используя GRPC протокол. Добавлены новые методы и поля серверного API, а также анонсирована PRO версия сервера, нацеленная на бизнес-пользователей.
Centrifugo v2: воспоминания
Прежде чем переходить к обсуждению v3 давайте оглянемся – на то что было сделано в процессе жизни Centrifugo v2.
Centrifugo v2 была большим рефакторингом первой версии. Начиная с версии 2 Центрифуга построена на библиотеке Centrifuge для языка Go. Библиотека с тех пор значительно улучшилась, и в данный момент служит, например, в качестве основного элемента real-time стриминга в Grafana.
Вот, например, красивое демо, сделанное моим коллегой Александром Зобниным. Демо содержит real-time телеметрию из игры Assetto Corsa, в реальном времени отображаемую на дашборд Grafana:
Центрифуга интегрировалась с Redis Streams, получила поддержку Redis Cluster. Может теперь работать с Nats сервером в качестве брокера.
Заметным улучшением стало появление серверных (server-side) подписок c некоторыми фичами поверх - таких как автоматическая подписка на персональный пользовательский канал при коннекте и поддержка одного соединения от юзера на всех нодах кластера.
Важным нововведением стало проксирование событий до бекенда приложения. Это позволило Центрифуге интегрироваться с приложениями, где встроенная JWT-аутентификация и механизм прав на каналы не подходили по той или иной причине.
Экосистема клиентов улучшилась. Тот факт, что клиентский протокол стал основан на Protobuf схеме, позволил Центрифуге работать с бинарными данными (в дополнение к JSON), при этом упростив процесс разработки новых клиентских коннекторов. Сейчас у нас гораздо лучшая ситуация и более полные реализации клиентских библиотек по сравнению с v1.
Появился официальный Helm Chart, Grafana дашборд для Prometheus в качестве источника данных и так далее.
Centriifugo становится более заметной фигурой в более обширном real-time сообществе. Например, она была включена в периодическую таблицу real-time продуктов, созданную Ably.com (одним из наиболее мощных real-time messaging облачных сервисов на текущий момент).
Конечно, есть масса моментов, которые могут быть улучшены в Centrifugo. И версия v3 направлена решить часть из них. Ниже мы посмотрим на самые заметные особенности и изменения нового релиза Центрифуги.
Обратная совместимость
Начнем с главного – обратной совместимости.
В Centrifugo v3 клиентский протокол практически не изменился. Мы ожидаем, что большая часть приложений сможет обновиться без единого изменения на стороне фронтенда. Это было важно для нас, так как цикл обновлений на мобильных устройствах достаточно длителен, плюс мы вынесли некоторые уроки из миграциии с v1 на v2. Клиентский протокол все же содержит одно ломающее изменение в изменении поведения History API, но мы предоставляем опцию на серверной стороне чтобы временно включить старое поведение (и в спокойном режиме обновить приложения на фронтенде).
На стороне сервера гораздо произошло больше изменений, особенно в конфигурации. Некоторые опции были переименованы, некоторые удалены. Мы предлагаем конвертер конфигурации, который призван помочь мигрировать с v2 на v3. В большинстве случаев вы сможете обновить конфигурацию Центрифуги, выкатить релиз используя v3 тег - и все будет работать как прежде. Все фичи н месте, или предоставлен их более функциональный аналог (например, для `channels` API).
Больше информации в описании миграции на v3.
Смена лицензии
Те кто следил за разработкой знают, что у нас были мысли сменить лицензию Центрифуги на AGPL v3 в новом релизе. После долгих размышлений мы решили пока этого шага не делать.
Но лицензия все же изменилась – теперь для open-source версии Centrifugo это Apache 2.0 вместо MIT. Apache 2.0 – это также разрешающая (permissive) open-source лицензия, но чуть более конкретная в некоторых аспектах:
Однонаправленные real-time транспорты
Серверные подписки, которые появились в Centrifugo v2 и недавние улучшения в библиотеке Centrifuge для Go открыли дорогу для поддержки полностью однонаправленного подхода.
Это значит, что Центрифуга v3 предлагает пользователям набор однонаправленных real-time транспортов, где сообщения могут ходить только от сервера клиенту. Почему это важно?
Centrifugo изначально концентрировалась на использовании двунаправленных (bidirectional) транспортов для клиент-серверной коммуникации. Таких как WebSocket и SockJS. Двунаправленные транспорты позволяют реализовать некоторые классные фичи, так как клиент не ограничен во взаимодействии с сервером. Однако такая возможность имеет свою стоимость – а именно рост сложности.
Пользователи Centrifugo были обязаны использовать специализированные библиотеки-коннекторы для общения с сервером. Коннектор абстрагируют сложность асинхронного двунаправленного протокола и предоставляет доступный API. Но внутри себя коннектор делает множество вещей: соединяет ответы с запросами, обрабатывает таймауты асинхронных запросов, следит за порядком обработки коллбеков, добавлением операций во временные буфферы, обрабатывает определенные ошибки сервера. Таким образом, каждый коннектор – это достаточно сложный программный код.
Но что если пользователь просто хочет получать real-time сообщения из стабильного набора каналов, известных на момент установки соединения? Можно ли упростить все и избежать использования дополнительных библиотек на стороне клиента?
С однонаправленными транспортами ответ – да. Клиенты теперь могут соединяться с Centrifugo без использования дополнительных коннекторов – просто используя нативные API браузера или код, генерируемый GRPC. И наконец-то стало возможным подключаться к Centrifugo используя CURL (пример).
Используя однонаправленные транспорты вы по-прежнему можете выиграть от встроенной в Centrifugo масштабируемости соединений, использовать существующие механизмы аутентификации с JWT или проксирование запроса до бекенда.
С помощью нового subscribe API сервера (см. подробнее ниже) вы можете подписывать клиента, использующего однонаправленный транспорт, на каналы в процессе жизни соединения. С помощью refresh API или refresh прокси можно инвалидировать долгоживущие соединения.
Centrifugo v3 поддерживает следующие unidirectional транспорты:
Мы ожидаем, что появление поддержки однонаправленных транспортов положительно скажется на увеличении применимости Centrifugo в различных сценариях.
Итерация по истории в канале
До версии 3 API Центрифуги не позволяло запрашивать историю по кусочкам, можно было запросить только полностью всю историю в канале (которая, впрочем, ограничена по размеру).
Версия 3 расширяет возможности API – теперь по истории можно итерироваться. Причем это можно делать как от более старых сообщений к новым, так и в обратном направлении, с заданным лимитом. Также, с известной позиции в стриме (stream position), если она известна.
Это в свою очередь позволяет в том числе реализовать ручное восстановление пропущенных сообщений из кэша Центрифуги, таким образом уменьшая нагрузку на основную базу данных приложения.
Вот пример программы на Go, которая бесконечно итерируется по стриму в обе стороны (используя gocent библиотеку для HTTP API) – при достижении конца стрима итерация идет в обратном направлении (не очень полезно на практике, но забавно):
// Iterate by 10.
limit := 10
// Paginate in reversed order first, then invert it.
reverse := true
// Start with nil StreamPosition, then fill it with value while paginating.
var sp *gocent.StreamPosition
for {
historyResult, err = c.History(
ctx,
channel,
gocent.WithLimit(limit),
gocent.WithReverse(reverse),
gocent.WithSince(sp),
)
if err != nil {
log.Fatalf("Error calling history: %v", err)
}
for _, pub := range historyResult.Publications {
log.Println(pub.Offset, "=>", string(pub.Data))
sp = &gocent.StreamPosition{
Offset: pub.Offset,
Epoch: historyResult.Epoch,
}
}
if len(historyResult.Publications) < limit {
// Got all pubs, invert pagination direction.
reverse = !reverse
log.Println("end of stream reached, change iteration direction")
}
}
Redis streams по умолчанию
Redis-движок (Redis Engine) в Centrifugo v3 по умолчанию использует Redis Streams в качестве структуры данных для хранения истории в канале. В v2 поддержка Redis Streams была, но далеко не все ее активировали. Это изменение важно в том числе и из-за появления в API возможности итерироваться по истории – так как Redis Streams позволяют это делать эффективно.
Tarantool Engine
Centrifugo содержит несколько встроенных движков (engines), позволяющих масштабировать ноды Центрифуги и балансировать клиентские соединения между ними. Ноды в данном случае связаны между собой механизмом PUB/SUB. Помимо PUB/SUB задача брокера – хранить историю сообщений в канале (ограниченную по времени и размеру), а также поддерживать presence информацию (список активных пользователей в канале). До релиза v3 Центрифуга имела in-memory, Redis (или любой совместимый сервер, например, KeyDB) движки, а также могла использовать Nats (без истории и presence в этом случае).
Добавить новый движок не просто - так как используемый брокер должен обладать хорошей PUB/SUB производительностью, быстрыми операциями при работе с историей и presence, возможностью за 1 RTT публиковать сообщение в PUB/SUB и сохранять его в историю. Также брокер должен уметь работать с "эфемерными", быстро меняющимися подписками. Миллионы активных пользователей, каждый со своим уникальным каналом, которые постоянно подключаются/отключаются (а значит подписываются/отписываются) – это типичная ситуация для Centrifugo.
В версии v3 мы добавили экспериментальную поддержку Тарантула как еще одну опцию движка. Тарантул неплохо подходит для задач, озвученных выше, и более того, выдает производительность до 4-10 раз выше по сравнению с Redis движком (совокупность факторов, не означает, что Тарантул в целом настолько быстрее чем Redis – прим. переводчика). При этом производительность PUB/SUB, которой мы смогли добиться от Тарантула, чуть хуже чем у Redis (примерно на 20% по количеству сообщений в секунду согласно нашим измерениям).
Для примера, давайте посмотрим на бенчмарк, в котором мы восстанавливаем сообщения после потери соединения и при этом пропущенных сообщений не было. То есть эмулируем ситуацию, когда множество соединений переподключаются через короткое время – например, из-за релоада балансеров.
Для Redis Engine:
BenchmarkRedisRecover 26883 ns/op 1204 B/op 28 allocs/op
То же самое, используя Tarantool Engine:
BenchmarkTarantoolRecover 6292 ns/op 563 B/op 10 allocs/op
Тарантул может дать новые свойства хранения данных (например, синхронную репликацию), новые применения там, где Redis не давал нужной производительности. Мы рады что теперь есть такая опция.
Причина, по которой мы рассматриваем интеграцию с Тарантулом экспериментальной – это то что в работу вовлечен еще один компонент – Centrifuge Lua модуль, который должен быть загружен и выполнен Тарантулом.
Это усложняет деплой, особенно принимая во внимание тот факт, что существует масса способов деплоя, которыми сообщество Тарантула пользуется в проде. Мы по-прежнему пытаемся найти разумный способ распространять Lua-часть. Пока что мы покрываем standalone (отдельный Тарантул инстанс), сетап Тарантула с фейловером, основанным на Raft и синхронной репликацией (см. примеры), и предлагаем готовый пакет с движком на основе Tarantool Cartridge.
Больше информации в документации о Tarantool Engine.
GRPC proxy
Центрифуга теперь может преобразовывать события, полученные от клиентских соединений в вызовы бекенда приложения по протоколу GRPC. В дополнение к HTTP прокси, существовавшему ранее.
Поддержка GRPC должна подготовить Centrifugo к текущим микросервисным реалиям, где GRPC - это частый выбор для межсервисного взаимодействия.
Таким образом, мы даем больше выбора пользователям Centrifugo. К тому же GRPC имеет ряд неоспоримых преимуществ, например, возможность сгенерировать слой RPC на бекенде приложения для большинства популярных языков программирования.
Смотрите раздел в документации.
Улучшения серверного API
Несколько важных нововведений есть и в серверном API.
Новый метод API subscribe позволяет подписать соединение на канал в любой момент времени. При этом используются серверные подписки. Ранее подписать соединение на список серверных каналов можно было только в момент установки соединения – теперь и в процессе жизни. Это может быть полезно при использовании однонаправленного подхода – чтобы эмулировать вызов подписки через клиентское API. Можно сделать запрос к бекенду, а с бекенда в серверное API Центрифуги.
Публикация сообщений в канал с включенной историей теперь возвращает позицию опубликованного сообщения в стриме (offset и epoch).
Описанная выше возможность итерироваться по истории сообщений в канале доступна как в клиентском, так и в серверном API.
Метод channels теперь научился возвращать количество активных соединений в канале, также можно фильтровать возвращаемые каналы по маске. Прежде существующее ограничение вызова (channels не работал с Redis Cluster и Nats) сняты благодаря изменению внутренней реализации.
Встроенный административный веб-интерфейс был обновлен, чтобы отразить изменения в серверном API – можно опробовать новые методы прямо из веб-интерфейса.
Улучшенная работа в кластере
В режиме кластера Центрифуга ведет себя чуть лучше. Как только нода покидает кластер она отправляет специальное уведомление другим нодам, таким образом изменение состава кластера имеет шанс быть замеченным моментально – и нода будет удалена из списков на других нодах.
Улучшения клиентских библиотек
Во время подготовки релиза v3 мы добавили ряд улучшений в существующие библиотеки-коннекторы. Все коннекторы (centrifuge-js, centrifuge-go, centrifuge-swift, centrifuge-java, centrifuge-dart) были обновлены до последней версии протокола, везде поддержаны серверные подписки, добавлена возможность итерироваться по истории.
Важная деталь – теперь при подключении к Centrifugo используя Protobuf протокол не обязательно добавлять ?format=protobuf URL параметр. Тип протокола передается в subprotocol'е WebSocket - то есть в заголовках HTTP Upgrade запроса. Чтобы это работало нужно использовать последнюю версию клиента и Centrifugo v3.
Новый сайт с документацией
Вы читаете этот пост на новом сайте проекта. Он построен на основе великолепного Docusaurus.
Множество документов было актуализировано, расширено, переписано с нуля. Новые главы документации:
Описания серверного API и прокси были значительно улучшены.
Оптимизации производительности
Centrifugo v3 получила заметные оптимизации производительности.
Клиентский JSON протокол теперь использует пару дополнительных библиотек – mailru/easyjson для сериализации и segmentio/encoding для десериализации. На самом деле мы используем слегка модифицированную версию easyjson чтобы достичь еще большей производительности при сериализации, чем библиотека позволяет "из коробки". Эти изменения позволили ускорить работу с JSON в 4-5 раз (для небольших сообщений). А для больших сообщений разница может быть более заметной. Мы наблюдали 30x ускорение для 5kb сообщений.
Например, давайте посмотри на бенчмарки сериализации 256 байтной нагрузки. До оптимизаций:
BenchmarkMarshal-12 5883 ns/op 1121 B/op 6 allocs/op
BenchmarkMarshalParallel-12 1009 ns/op 1121 B/op 6 allocs/op
BenchmarkUnmarshal-12 1717 ns/op 1328 B/op 16 allocs/op
BenchmarkUnmarshalParallel-12 492.2 ns/op 1328 B/op 16 allocs/op
После озвученных оптимизаций JSON:
BenchmarkMarshal-12 461.3 ns/op 928 B/op 3 allocs/op
BenchmarkMarshalParallel-12 250.6 ns/op 928 B/op 3 allocs/op
BenchmarkUnmarshal-12 476.5 ns/op 136 B/op 3 allocs/op
BenchmarkUnmarshalParallel-12 107.2 ns/op 136 B/op 3 allocs/op
Следует отметить, что Protobuf протокол Centrifugo по-прежнему превосходит JSON-протокол как по скорости сериализации, так и десериализации. Кстати Центрифуга отказалась от использования более неподдерживаемого gogo/protobuf, перейдя на использование planetscale/vtprotobuf. Благодаря этому производительность Protobuf осталась сравнима.
Конечно, работа с JSON в клиентском протоколе – это только одна из многих частей сервера, поэтому не стоит ожидать общего ускорения производительности сервера в 4-5 раз. Но нагруженные сетапы должны заметить разницу в потреблении ресурсов, в том числе это должно положительно повлиять на паузы GC.
Структура данных, которая хранит соединения в памяти теперь шардирована – это должно положительным образом сказаться на lock contention. На практике эффект от этого сильно зависит от характера нагрузки, в наших искусственных бенчмарках мы наблюдали увеличение пропускной способности хаба до 3 раз.
Во время отправки сообщения большому количеству пользователей Centrifugo теперь делает меньше аллокаций памяти.
Также, обновление вервии языка Go до 1.17 дало ~5% улучшение производительности в целом, спасибо новому способу передачи аргументов и параметров функции через регистры вместо стека, доступному в Go 1.17.
Centrifugo PRO
Последнее, о чем хочется упомянуть – это анонс Centrifugo PRO. Это расширенная версия Centrifugo, построенная поверх OSS версии. Centrifugo PRO содержит несколько уникальных возможностей, которые могут быть интересны бизнес-пользователям.
Те, кто следил за разработкой Centrifugo, знают, что было несколько попыток сделать разработку проекта устойчивой. Buy me a coffee и Opencollective инициативы не стали очень успешными. За год мы получили около 300$. Мы очень это ценим, но эти деньги ни в коей мере не оправдывают время, затраченное на поддержку проекта. И не дают вывести ее на следующий уровень. Так что вот еще одна попытка монетизировать проект.
Детали Centrifugo PRO описаны рядом, в документации. Посмотрим, как оно пойдет. Мы верим, что набор дополнительной функциональности может дать отличные конкурентные преимущества как небольшим, так и крупным проектам, использующим Centrifugo. PRO фичи могут дать полезные инсайды о системе, защитить клиентское API от неправильного использования, уменьшить ресурсы, потребляемые сервером, и не только.
PRO версия будет доступна вскоре после релиза Centrifugo v3 OSS.
Заключение
Centrifugo v3 содержит некоторые другие улучшения, которые не отражены здесь. Полный список можно найти в release notes и в описании миграции на v3.
Надеемся, мы шагнули в захватывающую эру Centrifugo v3 и много улучшений еще последуют. Присоединяйтесь к нашим сообществам в Telegram и Discord, если у вас есть вопросы, или вы хотите следить за разработкой Центрифуги.
Наслаждайтесь Centrifugo v3, и да пребудет с вами Центробежная Сила.
Комментарии (9)
Graid
07.09.2021 19:42Все бы ничего, но когда в PRO версии вместо дополнительных фич предлагают оптимизации перформанса это прям удручает. Такое ощущение что OpenSource перекраивают полностью в коммерческий продукт. И да, я понимаю, что все кушать хотят, просто на мой взгляд это убивает продукт.
FZambia Автор
07.09.2021 19:53+2Я бы вообще счастлив был обойтись без PRO, не только перформанса это касается. Я думаю, что идеального баланса не найти в такой модели. Я читал несколько статей про open core модель прежде чем пытаться сделать что-то подобное – и основная озвучиваемая проблема – это вечный конфликт между тем, что должно быть в OSS, и тем, что закрыто. И это так – я сам уже не один час над верным и оправданным разделением фичей думал... Но ничего другого на данном этапе не остается, кроме как попробовать что-то.
Ru6aKa
08.09.2021 18:09+1Авито, Майлру и другие крупные компании вполне могли бы поддержать разработку. А платным внедрением и поддержкой уже могли бы заниматься другие компании, типа как Altinity занимается ClickHouse.
FZambia Автор
09.09.2021 13:30Таких предложений не было. И я не представляю как сделать так, чтобы оно появилось. Должна быть ценность, которую пока большие компании не видят. Я сам работал и в mail.ru, и в Авито ранее. И в целом обеим компаниям наработки очень даже пригодились бы. В Mail.ru есть проекты (и далеко не один), использующие Centrifugo, в Авито есть мессенджер, построенный на бекенде на идентичной концепции.
Ru6aKa
14.09.2021 16:27Я бы пробовал попасть в один из фондов, чтобы иметь возможность получить грант. А от mail.ru, Авито, VK попробовать для начала получить спонсорскую поддержку для конференций.
Sadler
Примерно месяц назад смотрел Centrifugo, но почему-то не заметил на npm живых клиентов под него. Как оказалось, плохо смотрел. Попробую покрутить вашу центрифгу :) Спасибо за статью.
FZambia Автор
Если нужно только одностороннее общение, от сервера клиенту, и стабильный набор подписок, – то описанные в статье unidirectional транспорты могут быть неплохим выбором, чтобы обойтись совсем без специализированного клиента на фронте. Уберете не только зависимость, но и слой дополнительной сложности. Хотя centrifuge-js в целом проверен временем - с ним не должно быть проблем (по крайней мере для основной функциональности).