Чаты на вебсокетах. Теперь про бэкенд

2d190b767fe0b24d6ae201bddb359827.jpeg

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

Ниже подробности о том, что было до написания кастомных чатов и какие стояли требования к реализации, из каких компонентов они состоят, как вписываются в нашу инфраструктуру и что получилось в итоге. А в конце статьи — ссылки про особенности разработки наших чатов на вебсокетах для iOS и Android.

Почему решили писать свои чаты

iFunny — приложение с лентой мемов и возможностью флуда в комментариях, но последней опции было мало по очевидным причинам. Например, людям хочется кидать мемы в личку и создавать тематические каналы. Чтобы не вынуждать их переходить в сторонние мессенджеры, мы решили сделать чаты внутри приложения. 

Самый простой способ — использовать сторонний готовый сервис. На старте таким оказался Sendbird — платформа для создания чата внутри приложения, в которой есть все основные фичи современных мессенджеров. Основной плюс такого подхода:

  • Скорость внедрения. Как заявляет производитель, с нуля до первого сообщения — считанные минуты, так как не нужно писать бэкенд, только UI. Правда, для внутренних фич нам всё равно пришлось написать много бэкенд-кода (в основном это связано с модерацией открытых чатов и синхронизацией пользователей, а также у нас есть рейтинг открытых чатов).

Минусы, куда же без них:

  • Высокая цена.

  • Слабый контроль за происходящим внутри (пользователи доставали токен из мобильного приложения и спамили через скрипты, делали ботов).

  • Невозможность внутри сделать фичи под потребности.

  • Нельзя провести А/B-тест.

  • Ограничение на 500 пользователей в чате.

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

Основным техническим требованием было сделать всё то же самое, что у Sendbird, только без минусов. А также по возможности не использовать технологии, которые ещё не интегрированы в стек.

Подготовка

Выбор транспорта

Были мысли и предложения использовать UDP. Он обеспечивает более высокую скорость работы по сравнению с TCP, потому что не имеет накладных расходов на установку и на разрыв соединения. Но у нас не миллионы пользователей онлайн одновременно, а опыта работы с UDP у меня и у клиентских разработчиков было не так много, поэтому такой манёвр мог стоить неопределённого количества времени.

Для транспорта прикладного уровня выбрал WebSocket. Он давно везде поддерживается, и можно в будущем сделать чат в веб-версии приложения без правок на стороне бэкенда. А так как для установления соединения WebSocket клиент и сервер используют протокол, похожий на HTTP, можем использовать тот же механизм авторизации, что и в HTTP API приложения — через заголовок Authorization.

Выбор протокола

На ум сразу пришёл XMPP. Это открытый, основанный на XML, свободный для использования протокол мгновенного обмена сообщениями. Основными преимуществами являются децентрализация, открытость стандарта и расширяемость. Из недостатков — невысокая эффективность передачи данных за счёт XML-формата. А наличие большого количества открытых клиентов может привести к оттоку пользователей из приложения.

Из открытых XMPP-серверов самым продвинутым показался ejabberd, написанный на Erlang. На такой подвиг я был не готов, кроме того, сам по себе протокол старый, и его пришлось бы расширять под свои нужды.

Также смотрел в сторону MQTT — простого сетевого протокола, построенного по модели pub-sub. Есть возможность пустить поверх WebSocket-соединения.

До последнего думал взять MQTT, но потом наткнулся на WAMP. Это открытый протокол, который поддерживает два шаблона обмена сообщениями: pub-sub и routed RPC. 

Выбор инструментов

В качестве языка был выбран Kotlin JVM, а базы данных для хранения сообщений — MongoDB. Просто потому, что это наш основной стек.

Реализация

Начал с реализации WAMP-протокола. Не найдя в открытом доступе достойных реализаций роутера на Java/Kotlin, написал свою. По сути, это движок и сердце сервера.

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

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

Перенос данных

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

Отдельно можно отметить перенос медиа. Нужно было выкачать все картинки и видео, пережать их под наши стандарты и сохранить на свой S3. Процедура достаточно затратная по времени, поэтому после запуска чатов в продакшн ещё какое-то время медиа грузилось с CDN Sendbird, пока скрипты в фоне выкачивали его в наш сторадж.

Запуск

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

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

Архитектура бэкенда

Бэкенд состоит из двух сервисов — непосредственно сервис чатов, с которым клиент соединяется по вебсокет при переходе на вкладку «чаты» в приложении, и сервис хуков, у которого две роли:

  1. Принимает вебхуки от чатов и делает дополнительную логику:

    • генерирует пуши;

    • обновляет поисковый индекс открытых чатов;

    • поддерживает коллекции, необходимые для модерации;

    • удаляет старые каверы с S3;

    • считает рейтинг открытых чатов для ранжирования в приложении.

  2. Служит HTTP-proxy для внутренних RPC-вызовов по WAMP-протоколу. Этим пользуется php-монолит для:

    • получения счётчика непрочитанных сообщений и инвайтов для шторки меню в приложении;

    • получения информации о чатах и сообщениях в админке;

    • асинхронной загрузки медиа в чаты.

8d06a21f7b2cc56d4615abe19be28e82.jpeg

Вместо заключения

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

Там подробнее про поддержку старых версий операционных систем, способы декодирования (которые можно применить и в других задачах) и особенности работы с WAMP.

© Habrahabr.ru