Не откладывайте в почтовый ящик: b2c-мессенджер 2ГИС

178d4d90f12b41d9ba4b9e7e359a5086.png

В сентябре на 2gis.ru появилась новая фича — b2c-мессенджер для общения с организациями. Чат очень удобен при поиске товара или услуги: можно написать сразу в несколько компаний, не нужно слушать голоса роботов-автоответчиков или ожидать на линии, пока оператор уточнит цену или остаток нужного товара. Выберите компанию, нажмите на иконку сообщения на карточке компании, и откроется чат.

Чтобы сделать мессенджер, нам пришлось немного поразбираться с тем, как вообще работают чаты и что под капотом у «больших братьев» типа WhatsApp или Telegram. Оказалось, всё не так страшно.

Зачем 2ГИС мессенджер


Начать, пожалуй, стоит не с мессенджера, а с того, как мы пришли к этой идее. Раньше мы показывали в справочнике email компании. Затем добавили форму связи с компанией прямо из справочника. Пишут меньше, чем звонят, но длина цепочки писем — в среднем от 2 до 10 сообщений. То есть мессенджер — логичное развитие уже существующей возможности отправлять письма в компании.

Как это работает сейчас


51ba7fc8d1b749abafd0fd9ec9909316.gif
А ещё лучше — просто попробуйте задать нам вопрос. Мы в Новосибирске, и когда в Москве 18:00, у нас уже 22:00.

Беглый обзор: выбираем велосипед


Сперва мы посмотрели на BaaS (Backend as a Service), типа quickblox.com, backendless.com, sendbird.com и т.д. Все они предоставляют инструменты для добавления стандартной чатовой функциональности в мобильные или веб-приложения.

Реализация основного (отправка email-уведомлений в компании) и более продвинутых сценариев на основе существующего BaaS-решения потребует от нас как разработки собственных бекендов для интеграции через вебхуки, так и сверхусилий по интеграции с API вендора. Поэтому BaaS нам не подошёл. Кроме того, мы хотели иметь полный контроль над фичей, то есть не зависеть от сторонних решений. В конце концов, мы ведь инженеры и решились на эксперимент, чтобы получить новый для нас опыт.

А как сообщения-то доставлять?


Чтобы сделать свой бэкенд для мессенджера, сначала надо разобраться с кучей вопросов: как вообще передавать сообщения с клиента на сервер и обратно, где их хранить, как справиться с кучей одновременных соединений, как масштабировать сервис. Подумали, погуглили, собрали способы доставки сообщений в табличку для сравнения.
Таблица
Простите, но мы не смогли оформить таблицы в Хабраредакторе красиво.
Способ Описание Транспорт Протокол Поддержка в браузерах
WebSockets Клиент открывает постоянное соединение с сервером, используя API WebSockets. API Websockets TCP (необходим HTTPhandshake для открытия соединения) caniuse.com/#feat=websockets
Streaming Клиент открывает постоянное соединение с сервером. XMLHttpRequest (multipart onload), XMLHttpRequest (onprogress), Iframe tag HTTP caniuse.com/#feat=streaming
Server Sent Events Клиент открывает постоянное соединение с сервером, используя API Server Sent Events. API Server Sent Events HTTP Нет поддержки в IE, caniuse.com/#feat=eventsource
Polling Клиент периодически опрашивает сервер на предмет наличия новых сообщений. Любой доступный HTTP
Long Polling Клиент открывает долгоживущее соединение с сервером, которое не закрывается до появления нового сообщения или истечения таймаута. Сразу же после закрытия соединения клиент открывает новое. XMLHttpRequest
Script tag
HTTP
Browser Plugins (Java / Flash) Клиент открывает постоянное соединение с сервером, используя API браузерного плагина (Java или Flash). API плагина TCP/UDP


Большинство современных чат-приложений используют один из двух способов: long polling или websockets. Websockets обладает рядом преимуществ перед long polling (полнодуплексное соединение, отсутствие лишнего трафика при переконнектах), широко поддержан в браузерах и opensource-библиотеках, поэтому представляется оптимальным выбором для нашей задачи. Есть и недостатки: отличия имплементации в разных браузерах, более сложная логика на сервере.

Наиболее распространённый открытый general-purpose протокол для передачи данных — XMPP.

Положительные стороны Отрицательные стороны
Открытый стандарт IETF Избыточность передаваемой информации
Большое количество opensource-реализаций XML
Широкие возможности для кастомизации Потребует доработки под специфику нашей задачи

Обзор больших мессенджеров
Мессенджер Способ доставки Протокол Сервер Система хранения
WhatsApp Websockets Начинали с XMPP, но потом перешли на in-house протокол Вся серверная инфраструктура построена на Erlang + FreeBS, это позволяет держать до 2kk TCP-соединений на одном сервере. Начинали на ejabberd, но очень сильно его дорабатывали под себя впоследствии. Yaws, lighttpd Не хранят историю сообщений на сервере, сообщения удаляются с сервера после получения клиентом, mnesia
Line Нет веб-версии, только Chrome app Thrift для мобильных клиентов Java, С++, Nginx Начали с кластера из трёх инстансов Redis, после взрывного роста пользовательской базы смигрировали в Hbase «холодные» данные: историю сообщений и историю изменений юзеров / групп / контактов, MySQL для бэкапов и аналитики
Viber Нет веб-версии In-house С++, хостятся в AWS Начали с самописного in-memory хранилища написанного на C++, потом перешили на Mongo и Redis в качестве кеша. Mongo не устроила по производительности, в итоге мигрировали на Couchbase
Facebook Messenger Long polling In-house json-based для веба, Thrift для мобильных приложений Erlang — очереди сообщений. C++ — сервис информации о присутствии, хранилище истории сообщений. PHP — фронтенд, обрабатывает все пользовательские запросы, кроме long polling. Сервисы между собой общаются через Thrift
Очередь на MySQL, HBase
Slack Websockets In-house json-based Java messaging server, LAMP for core app/APIs, хостятся в AWS Redis, MySql, Apache Solr

Однако, изучив решения различных «больших» мессенджеров, мы не обнаружили ни одной значимой истории успеха использования XMPP без его последующей доработки и кастомизации. Поразмыслив, решили разработать простой json-based протокол, изначально учитывающий специфику нашей задачи.

Технологический стек


Так исторически сложилось, что наш основной бэкенд-сервис — АПИ 2ГИС — был написан на PHP5. Два года назад мы приняли решение уйти от PHP, мигрировать на Scala и Go. Go позволяет очень легко строить довольно сложные concurrent-программы. Это стало решающим фактором при выборе его как основной технологии реализации мессенджера. Да и кое-какой опыт разработки на Go у нас уже был.

Итак, бэкенд пишем на Go, а фронтенд — на React + Redux. В качестве системы обмена сообщениями мы выбрали RabbitMQ; для хранения данных используем Redis и PostgeSQL. Приложение пакуем в Docker-контейнер и деплоим через Gitlab-CI в платформу Deis.

Как я сказал выше, мы хотели использовать вебсокеты, но когда дело дошло до реализации, чуть переосмыслили решение. Дело в том, что мы хотели выпустить фичу как можно быстрее, чтобы проверить гипотезу (пресловутый MVP). Чтобы ускориться, решили разделить логику на использование вебсокетов для отправки данных с клиента на сервер и простейшего REST API в другую сторону.

Что с уведомлениями?


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

Есть решение, которое называют каскадной системой нотификаций. Например, у нас есть три основных канала: уведомления на 2gis.ru, письма и пуши на телефон.

В этом случае система нотификаций принимает вид:
 — проверяем, нет ли пользователя онлайн → если да, то шлём ему уведомление в браузере;
 — если пользователя нет онлайн, проверяем, привязан ли телефон → если да, пытаемся отправить пуш на мобильный телефон, показываем уведомление в диалогах в браузере, письмо не отсылаем;
 — если не сработало и это, тогда шлём письмо;
 — если уведомление касается компании — ещё некоторое время наблюдаем за реакцией, а если ответа после уведомления в браузере и пуша нет, то всё равно шлём письмо.

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

Планы


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

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

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

  • 18 октября 2016 в 11:57

    +1

    Господи, ну КДПВ вызывает желание взвопить «вы вообще для кого это делали»…
    2 гис, пишем с карты ГОРОДА. Ну зачем, ЗАЧЕМ уточнять город?
    Потому что как обычно, саппорт общий (или вообще сгружен в «индию»).
    Ну почему нельзя для саппорта показывать ОТКУДА запрос пришел, с какого города?

    Потому что каждый раз, когда я вижу у саппорта «КЫСА ТЫ С КАКОВА ГОРАДА», я закрываю этот саппорт нафиг.

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

    • 18 октября 2016 в 12:02

      0

      Как увидел сей пост зашел оставить ровно такой же комментарий, присоединяюсь к каждому слову. Первый старт приложения он запрашивает/определяет город, но это слишком просто…
    • 18 октября 2016 в 12:02

      0

      Потому что как обычно, саппорт общий (или вообще сгружен в «индию»).

      В общем, вот и ответ на ваш вопрос. Мы, конечно, показываем в личном кабинете, откуда запрос, только отвечает-то на него сотрудник магазина, который может это упустить, потому что он один, например, разбирает запросы из 19 филиалов по разным городам.

      Что на КДПВ это не учли — тут да, посыпаем голову пеплом, можно было и получше пример привести.

© Habrahabr.ru